Open xiaoxiaojx opened 2 years ago
为什么 abort-controller 打出来的包还会有 es6 的代码
为什么 abort-controller 打出来的包还会有 es6 的代码
没有 es6 的代码,import 是 @babel/plugin-transform-runtime 自动添加的
各种模块化方案真是万恶之源啊 CJS,ESM,AMD,UMD,IIFE
666
刚遇到了,还挺复杂。。
webpack 就是一坨一坨一坨,真恶心
Thank! It's works
问题定位
报错信息如下
首先查看 node_modules 中 abort-controller 包的代码, 找到报错的地方, 为下图中红色下划线标出的有 exports 变量这一行代码
仔细查看发现代码并无明显语法错误, 报
exports is not defined
不合常理正常来说 webpack 打包过后会把该模块的代码放在一个闭包函数中去运行, 通过函数参数中传入 module, exports 等变量, 运行完成后 module, exports 的值即为该模块的导出来的值, 和 Node.js 编译运行一个 js 文件模块的原理是类似的, 如下所示👇
但是这里报错的地方的闭包函数却不长上面那样, 区别是该闭包函数传入的第二个参数值是
__webpack_exports__
而非exports
?! 所以代码在浏览器运行时该模块作用域内没有exports
, 就出现了本文开始的错误信息exports is not defined
❌🤔 那么是什么条件决定了形参何时命名为
__webpack_exports__
, 何时为exports
了? 接着去探寻一下 webpack 这部分实现的代码通过查看 webpack 的代码我们发现 isHarmony 变量的值为 true 则会命名为
__webpack_exports__
, isHarmony 为 true 的条件是isStrictHarmony 为 true 或者当前有 import、export 等 ES Module 语句时, 可想而知 CommonJs 则会命名为exports
isStrictHarmony 为 true 的条件则是该文件是类型是
"javascript/esm"
, 通过查看 webpack 默认规则 .mjs 文件类型即为"javascript/esm"
这也容易理解, 当发现该文件是 ES Module 模块时, 没有必要传入 exports, 因为 CommonJs 导出模块变量时才会去 exports 上面去赋值导出变量, 所以 ES Module 模块里面 exports 变量不是一个关键字, 用户可以像普通变量一样使用
🤯 不过我们回头看一下, 报错的 abort-controller 包在 node_modules 中的代码不就是 CommonJs 规范的吗, 按理来说此时 isHarmony 为 false, 函数的入参是 exports 才对!
而报错的该包因为如下有 const 语句的 es6 代码 ⚠️ 为了兼容低版本的浏览器, 我们的脚手架中开了一个口子去白名单开放编译 node_modules 中的不规范的包
既然经过了一次 babel-loader, 那么我们需要知道 abort-controller 代码经过 babel-loader 编译后交给 webpack 处理时的代码长什么样子
接着看看我们的 babel 配置的 preset 使用的是 babel-preset-react-app
深入 babel-preset-react-app 的代码, 发现其内置使用了 @babel/plugin-transform-runtime 插件
@babel/plugin-transform-runtime 的作用是当检测到该文件代码中有 es6 等高级语法的代码时, 会通过在文件顶部添加 import 等语句动态添加对应的 polyfill, 达到按需添加 polyfill 的作用
相比直接把如下的 _classCallCheck 等实现的代码直接插入到文件头部, 通过 import 一行代码动态添加能够有效避免每个文件中都有这些重复的 polyfill 具体实现的代码, 所以使用 transform-runtime 也算一个常见的优化手段
😯 到这里我们知道了 abort-controller 这个包在 node_modules 中的代码虽然是 CommonJs, 但是通过 @babel/plugin-transform-runtime 给其添加的 import 语句, 使得 webpack 判断它为 ES Module 模块了
接着我们只能探索 @babel/plugin-transform-runtime 是否能通过 require 来动态添加 polyfill 了, 这样 webpack 也不会误判了...
通过查看插入 import 语句实现的代码,此时我们是因为进入了下图 isModuleForBabel 为 true 的逻辑, 而
builder.import()
显然就是添加一个 import AST 的函数实现。如果能进入 else 的逻辑使用上builder.require()
逻辑不就解决了我们的问题!如上图, 要是 babel 能根据原始代码来动态赋值 sourceType, 从而影响添加的是 import 语句还是 require 语句即可
谷歌一下就找到了官方的解决方案, babel@7.x 版本 sourceType 字段新增了
unambiguous
选项, 即如果原始代码有 import 或者 export 语句则把 sourceType 赋值为module
, 否则赋值为script
webpack/issues/4039兄弟问题
顺带一提下面这个问题造成的原因是一样的
何时会报上面那个错误, 看一下下面这个例子就知道了, 只是因为不同版本抛错的方式有所不同
问题解决
sourceType
Type:
"script"
|"module"
|"unambiguous"
, Default:"module"
"script"
- Parse the file using the ECMAScript Script grammar. No import/export statements allowed, and files are not in strict mode."module"
- Parse the file using the ECMAScript Module grammar. Files are automatically strict, and import/export statements are allowed."unambiguous"
- Consider the file a "module" if import/export statements are present, or else consider it a "script".