背景
这两天帮兄弟团队新建一个 Next.js 项目,主要是内部的基础包,组件库等在最新的 next12 & webpack5 & react18 环境下跑通。其中遇见了如上图所示的编译错误, 即 .d.ts 文件没有设置对应的 loader 处理。
回头看一下报错处的代码 import BasicLogic from ‘./BasicLogic’, 没有写文件后缀怎么会被解析成 BasicLogic.d.ts, 而不是 BasicLogic.js 了 !?
问题排查
类似于 Node.js 不写文件后缀会依次尝试 .js、.json、.node, webpack 则是通过 resolve.extensions 字段来配置需要自动补充的文件后缀。
于是先检查 webpack-config.ts 文件, 发现 extensions 字段配置正确 ✅, webpack 尝试的解析顺序将是 .js > .tsx > .ts > .jsx > .json > .wasm, 按数组索引由小到大优先级由大到小降低。
1 | // packages/next/build/webpack-config.ts |
接着检查了一下 next 是否配置了 resolve 文件解析相关的插件, 使得错误的篡改了原有的解析顺序, 排查了一圈未发现可疑的插件。此时彷佛陷入了僵局, 一时想不明白问题出在了哪 🤯
问题定位
最后盯着报错信息看久了, 似乎对这小子有点眼熟了。sync ^.*$ 好像是在扫描文件? 什么时候会出现通过正则扫描文件的操作了,可以看一个简单的例子
对于 require 函数, webpack 支持传入的文件路径可是静态的字符串, 也可以是夹杂着变量的字符串拼接。
当然 webpack 不能准确推算出 dynamicPath 的值来定位到具体的文件, 因为 dynamicPath 是运行时变量, 或者例子中 dynamicPath 赋值语句可以写成 const dynamicPath = windows.dynamicPath, 使得静态分析没有了可能。
接着我们 debug 一下 webpack 对这样写法的处理代码, 通过上图 DEBUG CONSOLE 的信息可知, require(../utils/${dynamicPath}
) 其实会转换为类似于 require(../utils/*.*
) 语句。
作用就是会去 utils 目录下扫描正则匹配上的模块, 匹配成功的模块全部会打包进入 dist 目录备用。
当实际浏览器运行时就会一股脑把正则匹配上的模块加载进内存, 从而支持动态获取某个 key 值对应的模块, 所以你只需确保 dynamicPath 是 utils 下目录真实存在的模块就好。
回头看 import BasicLogic from ‘./BasicLogic’ 这行代码, 完全就是 es6 支持静态分析的 import 语句, 怎么出现了 require 函数传入了变量参数的问题了? 一搜索发现是如上图该文件的 51 行写了 require 函数 😓
问题解决
既然确保了 .d.ts 是被无辜扫描进去的模块, 且实际运行时也不会用到该模块, 那么我们可以配置一个 ignore-loader 来处理 *.d.ts 的文件就好了。我个人认为要尽量避免使用含有变量参数的动态 require、动态 import 这样的语法, 除非正则匹配上的文件都是运行时需要的, 否则大量冗余模块使得 js 体积无故增大。