迁移babel7+tree-shaking
Realwate opened this issue · 1 comments
babel7主要变化
废弃 babel-preset-stage-x
babel团队认为,非标准的特性是不稳定的(通过stage来推进),按照stage来划分需要花很多精力来维护。
对应 stage < 3
的 plugin
也修改了命名,从@babel/plugin-transform-*
改为 @babel/plugin-proposal-*
proposal
表示这只是一个提案中的非标准特性,随时可能变化,使用前要考虑清楚。
废弃 babel-preset-es20xx
使用 babel-preset-env
不需要自己决定去用哪些 preset
,而是由 env
自动的根据要支持的环境启用相应的 preset
使用 Scoped Packages
更加清晰,易于区分 official package 和 community package
如:
babel-core -> @babel/core
babel-preset-env -> @babel/preset-env
迁移步骤
# 会修改 babelrc 和 package.json
npx babel-upgrade --write
// babelrc before
{
"presets": [
"react",
"es2015",
"stage-0"
],
"plugins": [
"transform-decorators-legacy",
"react-hot-loader/babel"
]
}
// after
{
"presets": [
"@babel/preset-react",
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"react-hot-loader/babel",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-json-strings",
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions",
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-logical-assignment-operators",
"@babel/plugin-proposal-optional-chaining",
[
"@babel/plugin-proposal-pipeline-operator",
{
"proposal": "minimal"
}
],
"@babel/plugin-proposal-nullish-coalescing-operator",
"@babel/plugin-proposal-do-expressions",
"@babel/plugin-proposal-function-bind"
]
}
这样就很快完成了 babel7 的升级,但是还不够。
- 因为我们之前是直接引用
preset-stage-0
,升级后实际上很多proposal-plugin
是没用到的(扫下每个插件的文档) babel7
的 config-file 也有一些变化,简单来说,它提供了两种配置babel.config.js
和.babelrc
。
babel.config.js
作为整个项目通用的配置,可作用于node_modules
。而.babelrc
只作为所在package
的配置,不会影响到其他package
。
这意味着我们项目根目录下配置的.babelrc
对node_modules
是不起作用的,而我们很多库都依赖babel
的转译。
解决:项目根目录新建个 babel.config.js ,同时删掉没用到的 proposal-plugin (package.json中也对应删除),结果如下。
// babel.config.js
module.exports = (api) => {
api.cache(true)
return {
'presets': [
'@babel/preset-react',
'@babel/preset-env'
],
'plugins': [
[
'@babel/plugin-proposal-decorators',
{
'legacy': true
}
],
'react-hot-loader/babel',
'@babel/plugin-syntax-dynamic-import',
[
'@babel/plugin-proposal-class-properties',
{ 'loose': true }
],
'@babel/plugin-proposal-function-bind'
]
}
}
babel-transform-runtime
babel 在转换语法的时候会生成一些helper(如 class 的 classCallCheck 等),@babel/plugin-transform-runtime 这个插件会将所有生成 helper 的地方,统一从@babel/runtime
引入,减少了冗余代码。
在 babel.config.js
中添加 @babel/plugin-transform-runtime
,然后 install
npm i @babel/plugin-transform-runtime -D
npm i @babel/runtime
因为我们的编译包括 node_modules,要忽略 @babel/runtime
本身的处理。
node_modules下的包可能使用了多种模块格式,而 babel-plugin-transform-runtime
默认是添加 import
语句,在wabpack
中同一模块下,import
语句不能和 module.exports
同时使用 (babel文档 webpack相关issue)
(PS: 原来可以是因为 babel 默认会将所有的模块转化成 cjs ,再传给 webpack。但是这样无法做 tree-shaking,下文会提到)
所以添加配置。
'sourceType': 'unambiguous', // 自动推断编译的模块类型
'ignore': [/@babel[/\\]runtime/], // 忽略 @babel/runtime处理
polyfill
polyfill 部分变化不大,继续走 script 引入,不经过 webpack 打包。
tree-shaking
webpack
在 production mode
会自动进行 tree-shaking
优化,消除未使用到的模块代码,原理是通过 es6 module 的静态分析。
需要配置 babel
不转换我们的 module
,给 env 加个配置,让 webpack
来处理模块。
['@babel/preset-env', {
'modules': false
}]
我们的代码中(业务代码、库)可能会混用 import
和 modules.exports
,这在 webapck
中是不允许的。issue
如下代码会报错
// enum.js 混用了 import 和 modules.exports
import { i18next } from 'gm-i18n'
let enum = {
a, b
}
modules.exports = enum
// 引用模块
import { a } from 'enum'
// 提示
// export 'a' was not found
// console 输出
// enum.js:347 Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'
解决:统一只使用es6模块。也可以配置babel把模块先转换成cjs格式,但是这样webpack就不能做tree-shaking,不推荐
一般而言,tree-shaking
对业务代码没多大作用,主要是针对 node_modules 下的库。webpack4 新增了 sideEffects
选项,进一步优化了 tree-shaking
的结果。可以看下Webpack 中的 sideEffects 到底该怎么用?
简单来说,如果一个模块的export没有被任何文件用到 -> 去掉这个模块代码不会产生任何影响(反例是polyfill)-> 那就可以设置 sideEffects:false
对于我们自己发布的库,统一使用 es6 module,并且在 package.json
中加上 sideEffects: false
(也可以设置为具体的文件),这样跟着业务代码一起打包,就能通过 tree-shaking
去除没用到的模块代码(例如我们的图标库 gm-svg
,针对不同的项目去掉没用到的图标)。
最终...
最后的 babel.config.js
文件内容如下
module.exports = (api) => {
api.cache(true)
return {
'sourceType': 'unambiguous', // 自动推断编译的模块类型(cjs,es6)
'ignore': [/@babel[/\\]runtime/], // 忽略 @babel/runtime
'presets': [
'@babel/preset-react',
['@babel/preset-env', {
'modules': false
}]
],
'plugins': [
[
'@babel/plugin-proposal-decorators',
{
'legacy': true
}
],
'react-hot-loader/babel',
'@babel/plugin-syntax-dynamic-import',
[
'@babel/plugin-proposal-class-properties',
{ 'loose': true }
],
'@babel/plugin-proposal-function-bind',
'@babel/plugin-transform-runtime'
]
}
}
package.json
新增的依赖如下
"dependencies": {
"@babel/runtime": "^7.4.3"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.4.3",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.0",
}
学到了