You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
(以下配置来自 (babel-loader 项目主页)[https://www.npmjs.com/package/babel-loader]的“Some files in my node_modules are not transpiled for ie 11” 一节)
{test: /\.m?js$/,exclude: {and: [/node_modules/],// Exclude libraries in node_modules ...not: [// Except for a few of them that needs to be transpiled because they use modern syntax/unfetch/,/d3-array|d3-scale/,/@hapi[\\/]joi-date/,]},use: {loader: 'babel-loader',options: {presets: [['@babel/preset-env',{targets: "ie 11"}]]}}}
目前,我的项目没有用到 babel,因为我认为我的目标用户应该都有足够高的浏览器版本,所以我是直接使用 tsc 来编译 ts 代码的,且 tsconfig 的 target 设为了
ESNext
,这意味着完全不会有 polyfill 打包进最终的生成的代码里。但随着我使用的新特性越来越多,对浏览器版本的要求也越来越高,所以我还是决定为项目引入 babel。
TypesScript VS Babel
首先要了解直接用 TypesScript 编译(
ts-loader
、tsc
)和用 Babel 编译(babel-loader
、@babel/preset-typescript
)的区别。直接用 TypeScript 编译的话,TypeScript 做了两件事情:编译代码和检测类型,而用 Babel 编译的话,Babel 只负责编译代码,所以还需要额外使用
tsc --noemit
(如果你还想生成声明文件的话,用tsc --emitDeclarationOnly
)命令来检测类型。另外,用 Babel 编译代码的话,还需要在 tsconfig 中添加
"isolatedModules": true
。有关这两者的差异可以查看 TypeScript 的官方文档:Using Babel with TypeScript。
现在常见的做法是使用 Babel 编译代码、用 TypeScript 检测类型。@babel/preset-env 可以根据浏览器范围确定输出的代码,这比 TypeScript 自己的 target 选项要灵活的多。
项目背景
划词翻译使用了 monorepos 组织代码,在这个 monorepos 中,项目类型分为两种:lib(即模块)和 app(即实际需要运行起来的项目)。
lib 类型的项目使用了 rollup 来打包,且只提供 cjs / es 两种输出类型,也就是说,如果 app 要使用 lib,则必须使用 webpack 这类模块打包工具,不能直接通过
<script>
标签引入。app 类型的项目使用了 webpack 来打包。
这次改造会针对这两种类型的项目进行。
对于 lib 类型的项目
lib 类型现在使用了 @rollup/plugin-typescript 来编译。
要想改为使用 babel 来编译代码的话,需要先添加一个 babel.config.js:
然后将下面的这部分 rollup 配置:
改为:
然后就能正常编译了。
@babel/runtime 的问题
由于我们没有使用 browserslist 文件,也没有给 @babel/preset-env 指定 targets,所以 babel 默认将我们的代码转为了 es5 兼容代码,检查 babel 生成的文件的话,会发现 babel 注入了很多 runtime 代码(runtime 代码的介绍,类似于 TypeScript 里的 tslib)。
作为一个 lib 项目,我不希望 runtime 代码注入到最终生成的代码当中,现在我有两个选择:
一,将 runtime 代码作为模块的一个依赖
这样做的话,所有 lib 都可以从
@babel/runtime
模块导入 runtime 代码,能有效减少 app 最终的项目体积。我参考了 @rollup/plugin-babel 的说明,做了一些改动。
首先是 rollup 配置:
然后
npm i -D @babel/plugin-transform-runtime
并将它加入 babel config 中:最后
npm i @babel/runtime
将它作为项目的依赖。这样就完成配置了,运行 rollup,它报错了:
这个错报的很奇怪,因为 babel config 里写的明明是
@babel/plugin-transform-runtime
,但 rollup babel 插件却在读取@babel/preset-plugin-transform-runtime
。我做了下面三种尝试,均未解决此问题:
skipPreflightCheck: true
配置(来源)rootDir: path.join(process.cwd(), '../..')
但均无效。
所以目前来看,这个方法是行不通了,等下次我再试试看要怎么解决这个问题。
二,不要注入任何 runtime 代码,由 app 负责注入
lib 生成的代码不添加任何 runtime:原样保留
async / await
、String.prototype.matchAll
等现代浏览器才支持的写法,然后当有 app 使用这个 lib 时,由 app 负责注入。即使不是因为上面的报错,我也更倾向于这种做法,毕竟不同 app 对浏览器的支持要求不尽相同:面向 C 端用户的网站可能要尽可能兼容老浏览器,但企业内部网站、基于 Electron 的应用就不需要这么严格,使用固定的 browserslist 配置无法满足所有项目的要求。
如果使用这种方式,lib 只需要将 TypeScript 的 target 设为
ESNext
,然后直接用 TypeScript 编译即可,但 app 需要做一些额外配置。以在 Webpack 里用到的 babel-loader 为例,为了加快 babel-loader 速度,我们一般会
exclude: /node_modules/
,即告诉 babel 不要处理 node_modules 里的代码,但如果我们需要 babel 来处理 node_modules 里的一些代码的时候,就需要这么写了:(以下配置来自 (babel-loader 项目主页)[https://www.npmjs.com/package/babel-loader]的“Some files in my node_modules are not transpiled for ie 11” 一节)
对于 app 类型的项目
需要做如下改动:
.browserslistrc
文件来确定要支持的浏览器范围。"isolatedModules": true
babel.config.js 文件内容:
webpack.config.js 中 babel-loader 相关配置:
然后运行 webpack,发现报了一个错:
看了一下出错的文件,发现文件开头有这么一行代码:
但是这里 import 的 MyApp 是一个 TypeScript Interface,我猜测是启用了 isolatedModules 之后导致 TypeScript 没法判断这个 MyApp 是不是类型。不过这个问题也好解决,改个名字就可以了。
对改造结果进行确认
改完之后,webpack 就可以正常运行了,但是还有一些事情需要确认。
确认 React JSX Transform
React 17 引入了新的 JSX Transform,详情见官网介绍 Introducing the New JSX Transform。
我要确保的就是:babel 在开发环境下引用的是
react/jsx-dev-runtime
,在生产环境下引用的是react/jsx-runtime
。我确认的方法是使用一个 webpack 插件 Webpack Bundle Analyzer,完成打包后,这个插件会弹出来一个网页,包含 webpack 处理的所有模块的信息。
在启动了 webpack 的生产环境打包后,我搜了一下
react
,就能看到我的代码里使用的是react-jsx-runtime.production.min.js
,这是符合预期的。在启动 webpack 的开发环境后,搜到的是
react-jsx-runtime.development.js
,理想状态是开发环境应该使用react-jsx-dev-runtime.development.js
。但神奇的是我找不到让 babel 引入 jsx-dev-runtime 的方法,谷歌搜到的都是对官方介绍的解读;@babel/preset-react 虽然有 development 选项,但是设为 true 之后引用的还是 jsx-runtime;@babel/plugin-transform-react-jsx的文档示例里用的也是 jsx-runtime。
在 TypeScript 里可以用 jsx 选项选择使用哪一个,但 babel 似乎无法做到,先放一放吧。
确认 polyfills 代码引用方式
polyfills 指的是由 core-js 提供的现代浏览器的特性如
Promise
、String.prototype.includes
等。给 @babel/preset-env 添加
debug: true
,会打印出我们使用的所有插件和 polyfill,但看不到 runtime 代码的情况,先略过。由于没有给 @babel/preset-env 配置
useBuiltIns
选项,所以目前项目没有加入任何 polyfill。我根据文档使用了
useBuiltIns: "entry", corejs: "3.26"
并安装了 core-js v3.26.1,然后就能在控制台看到每个文件使用到的 core-js 代码。Webpack Bundle Analyzer 插件里也能搜到 core-js 的使用情况。
确认 runtime 代码引用方式
runtime 代码是指 babel 在转换语法时用到的辅助函数,例如
_extends
。在 Webpack Bundle Analyzer 弹出的分析报告中搜索
@babel/runtime
,能看到 babel 是统一从@babel/runtime
里引用辅助函数的,例如./../node_modules/@babel/runtime/helpers/esm/extends.js
,这是符合我的预期的。换句话说,只要不是给每个文件都单独注入了类似
_extends
这样的辅助函数就行。总结
无
The text was updated successfully, but these errors were encountered: