背景
开发了一个 Jest 的自定义 reporter,开发的时候是在 Jest 29 版本下的一个仓库测试的。最近在另一个仓库使用的时候,出现报错,这个仓库的 Jest 版本是 27.x:
使用方式如下:
这个仓库有大量的存量case,无法直接升级 Jest 版本解决,所以我打算先从找到报错原因入手。
排查过程
首先问题的可能原因有以下:
-
Jest 版本问题
-
Jest 27 的 Bug
-
Jest 27 尚不支持自定义repoter
-
Jest 27 支持自定义reporter,但不支持通过module包的形式使用
-
-
我的自定义repoter有问题
我首先是将问题仓库的Jest版本提升到了 Jest29 试了一下,repoter 确实是可以运行了,但是case大量报错(毕竟是两个大版本,有Break Changge可以理解)。所以问题原因是我的自定义reporter有问题这种情况排除了,确实是 Jest 版本的问题。
然后我去看了下 Jest 27 的自定义reporter文档,并没有看见其对于 node module 形式自定义 reporter 的实例代码。所以我有点怀疑这个版本是不是还不支持这种形式,只能通过路径引用JS文件的形式去配置。
于是我去搜罗了Jest 的变更日志,但是并没有在相关版本变更节点找到类似“新增自定义repoter支持node module形式”这种。而且找了很多很早就发布的 Jest repoter 包。所以显然也不是 1.c 这个原因。
同样的,1.b 也是不可能的,Jest 很早就支持了自定义repoter。
最后只剩下 1.a 了,我先是在Jest 的变更日志了找了下,没有找到相关关键字(repoter)的Fix。最后手段只剩下调试源码了。
源码查根因
开启vscode debug terminal,开干。
通过报错信息,可以轻松定位到源码位置。
/jest-config/build/normalize.js:558:15
通过对应报错位置的代码分析,可以看到,其实就是 findNodeModule 没有成功通过 repoterPath 找到 repoter ,导致了对应报错。
于是继续进入 findNodeModule 方法,Jest 没有直接通过 require 去读取 repoterPath,而是做了一些操作,这里由于我们没有传入 resolver,所以使用默认的 resolver 处理 path。
继续进入 resolver,其实内部没什么逻辑,直接调了一个三方包的方法,我们继续进去,省略无关的逻辑代码,最后会走到这个 loadNodeModulesSync。
loadNodeModulesSync的逻辑很清晰,就是加载所有目标模块可能存在的目录(通过NODE_PATH,和一些寻路策略,找到所有可能模块目录),然后去加载对应的JS文件。
然后我们查看这个 dirs,就能够发现问题了。可以看到,后面就会通过这里生成的dirs去遍历磁盘,寻找对应包的入口文件,但是这个生成的dirs最后都带了 jest,这个是我的包 exports 里的内容,最后结果自然是找不到。
由于我的自定义repoter那个包,不只是提供了jest的repoter,还提供了其他测试框架的repoter,所以我用了exports提供了多个导出路径:
显然 Jest 这里是不想直接用原生的加载模式,用了自己的一些封装,最后调到了 resolve 这个三方包上。最后由于这个包不能识别 exports,导致找不到我的自定义repoter。
resolve's PR: add support for the exports package.json attribute
相关 issue:
显然 resolve 自己还没有实现对于 exports 的支持,作者也回应由于各种问题,暂不会实现。目前这个PR还没进去
根因总结
由于Jest用来导入模块的第三方包resolve,并没有实现对于package.json 中 exports 的支持,导致 Jest 在读取自定义repoter 带有 exports 实现形式的时候,找不到对应的包。
Jest 27.x 解决方案
直接使用原生方法读取对应模块真实路径传给Jest即可:
Jest 后续如何修复的
我很好奇后续 Jest 后续是如何修复这个问题的。因为在 Jest 29 版本我显然没有遇到这个问题,于是我对 Jest 29 版本也进行了源码debug。
可以看到 Jest 自己实现了一个 getPathInModule 来替换原来直接读取模块的方式。在 getPathInModule 方法中,明确的去解析了模块名称,找到了模块的 package.json ,利用第三方包 resolve.exports 读取了 exports,拼出完整的入口文件路径之后才返回,最后交给了 resolveSync(来自resolve包)。
从这里的 git 记录能找到对应的 PR,PR作者提到了我们上面看到的同一块儿关键代码,指出了那里的问题。
关联的issue:github.com/jestjs/jest… ,这个问题在 Jest 28 的 alpha 版本被修复。