xiaoxiaojx / blog

Project for records problems solved in my work and study.
https://xiaoxiaojx.github.io/
MIT License
253 stars 6 forks source link

webpack 不止静态分析 #38

Open xiaoxiaojx opened 2 years ago

xiaoxiaojx commented 2 years ago

image

背景

image 这两天帮兄弟团队新建一个 Next.js 项目,主要是内部的基础包,组件库等在最新的 next12 & webpack5 & react18 环境下跑通。其中遇见了如上图所示的编译错误, 即 .d.ts 文件没有设置对应的 loader 处理。 image 回头看一下报错处的代码 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, 按数组索引由小到大优先级由大到小降低。

// packages/next/build/webpack-config.ts

const resolveConfig = {
    // Disable .mjs for node_modules bundling
    extensions: isNodeServer
      ? [
          '.js',
          '.mjs',
          ...(useTypeScript ? ['.tsx', '.ts'] : []),
          '.jsx',
          '.json',
          '.wasm',
        ]
      : [
          '.mjs',
          '.js',
          ...(useTypeScript ? ['.tsx', '.ts'] : []),
          '.jsx',
          '.json',
          '.wasm',
        ],
    // ...
  }

接着检查了一下 next 是否配置了 resolve 文件解析相关的插件, 使得错误的篡改了原有的解析顺序, 排查了一圈未发现可疑的插件。此时彷佛陷入了僵局, 一时想不明白问题出在了哪 🤯

问题定位

image

最后盯着报错信息看久了, 似乎对这小子有点眼熟了。sync ^.*$ 好像是在扫描文件? 什么时候会出现通过正则扫描文件的操作了,可以看一个简单的例子

image

对于 require 函数, webpack 支持传入的文件路径可是静态的字符串, 也可以是夹杂着变量的字符串拼接。

当然 webpack 不能准确推算出 dynamicPath 的值来定位到具体的文件, 因为 dynamicPath 是运行时变量, 或者例子中 dynamicPath 赋值语句可以写成 const dynamicPath = windows.dynamicPath, 使得静态分析没有了可能。

接着我们 debug 一下 webpack 对这样写法的处理代码, 通过上图 DEBUG CONSOLE 的信息可知, require(../utils/${dynamicPath}) 其实会转换为类似于 require(../utils/*.*) 语句。

作用就是会去 utils 目录下扫描正则匹配上的模块, 匹配成功的模块全部会打包进入 dist 目录备用。

当实际浏览器运行时就会一股脑把正则匹配上的模块加载进内存, 从而支持动态获取某个 key 值对应的模块, 所以你只需确保 dynamicPath 是 utils 下目录真实存在的模块就好。

image

回头看 import BasicLogic from './BasicLogic' 这行代码, 完全就是 es6 支持静态分析的 import 语句, 怎么出现了 require 函数传入了变量参数的问题了? 一搜索发现是如上图该文件的 51 行写了 require 函数 😓

问题解决

既然确保了 .d.ts 是被无辜扫描进去的模块, 且实际运行时也不会用到该模块, 那么我们可以配置一个 ignore-loader 来处理 *.d.ts 的文件就好了。我个人认为要尽量避免使用含有变量参数的动态 require、动态 import 这样的语法, 除非正则匹配上的文件都是运行时需要的, 否则大量冗余模块使得 js 体积无故增大。

fantasticsoul commented 2 years ago

👍🏻