webpack处理javascript兼容性--Babel
Posted ~往无前
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了webpack处理javascript兼容性--Babel相关的知识,希望对你有一定的参考价值。
.
package允许你使用Babel和webpack转译javascript文件。因为javaScript的版本一直都在更新迭代,所以需要转译为浏览器可认识的文件内容,经常来说,我们会将ES6转换成ES5。
一、Babel初使用
1.基本兼容性处理
1.1安装包
npm install babel-loader balel/core babel/preset-env -D
1.1配置文件
{
test:/\\.js$/,
exclude:/node_modules/,
use:{
loader:'babel-loader',
options:{
//预设:只是babel做怎么样的兼容性处理
presets:['@babel/perset-env',]
}
}
}
2.@babel polyfill
上面我们对兼容性进行了简单的实现,但是问题就是一些新的语法,无法进行转换比如promise不能转换。babel只会转换ES6语法,不会转换新的api,让新的api生效的方法是使用传统的polyfill,为此需要引入这个模块。
2.1 安装polyfill
安装的时候必须用 --save保证引用到生产环境而不是开发环境。
npm install --save @babel/polyfill
2.2导入polyfill
- polyfill 用起来很方便,但是你应该和 @babel/preset-env 以及 useBuiltIns 的option 一起用。如果不这样做的 话,我们建议你手动引入需要的每个polyfill。在打包时,可以将器在入口文件中引入。
缺点:使用这个方法的缺点就是即使不需要转译的程序他也会进行转译,我只要解决部分兼容性的问题,但是将所有的兼容性代码全部注入,所以会增加代码的体积。
3.按需加载
babel 7 的 @babel/preset-env 的 useBuiltsIns提供了相对比较完美的解决方案 :
- useBuiltIns 默认为 false,根据 browserlist 是否转换新语法与 polyfill 新 API
1.false : 不启用polyfill, 如果 import ‘@babel/polyfill’, 会无视 browserlist 将所有的 polyfill 加载进来。
2.entry : 启用,需要手动 import ‘@babel/polyfill’, 这样会根据 browserlist 过滤出 需要的 polyfill
3.usage : 不需要手动import ‘@babel/polyfill’(加上也无妨,构造时会去掉), 且会根据 browserlist + 业务代码使用到的新 API 按需进行 polyfill
上面提到了在入口文件中加入polyfill的问题就是对所有的程序进行了转译,使得代码体积过大。那么新的解决方法就是按需载入:
{
test:/\\..js$/,
exclude:/node_modules/,
loader:'babel-loader',
options:{
//预设:只是babel做怎么样的兼容性处理
presets:[
'@babel/perset-env',
{
//按需加载
useBuiltIns:'usage',
//指定core-js版本
corejs:{
version:3
},
//指定兼容性做到哪个版本浏览器
targets:{
chrome:'60',
firefox:'60',
id:'9',
safari:'10',
edge:'17',
}
}
]
}
4. plugin-transform-runtime
上面的两种方法对代码进行了转译但是带来的问题就是Babel在各个文件中都插入了辅助代码,使得代码体积过大!所以plugin-transform-runtime 可以节省代码的大小也可转译迭代器。那么这个东西到底什么呢?我们在下面来做具体解释。
二、@babel/plugin-transform-rumtime到底是什么?
很多初学者在刚接触 babel 的时候,通常会看到这样一个报错信息:
ReferenceError: regeneratorRuntime is not defined
这个报错表面上是由于 async function 语法被 babel 转译之后的代码使用了 regeneratorRuntime 这个变量,但是这个变量在最终的代码里未定义造成的报错。
babel 在转译的时候,会将源代码分成 syntax 和 api 两部分来处理:
syntax:类似于展开对象、optional chain、let、const 等语法
api:类似于 [1,2,3].includes 等函数、方法
1.首先写一个最简单的 babel 配置文件:
{
"presets":[["@babel/preset-env"]]
}
转译结果如下:
上面说过,const 这种语法为 syntax,includes 这种方法为 api。可以看到,syntax 很轻松就转好了,但是 api 并没有做任何处理。babel 转译后的代码如果在不支持 includes 这个方法的浏览器里运行,就会报错。
2.babel 使用 polyfill 来处理 api。@babel/preset-env 中有一个配置选项 useBuiltIns,用来告诉 babel 如何处理 api。由于这个选项默认值为 false,即不处理 api,所以上面的代码转译后没有处理 includes 这个方法。
设置 useBuiltIns 的值为 “entry”,同时在源代码的最上方手动引入 @babel/polyfill 这个库(该库一共分为两部分,第一部分是 core-js,第二部分是 regenerator-runtime。其中 core-js 为其他团队开源的另一个独立项目):
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
corejs:{
version:3
},
}
]
]
}
可以看到,这种模式下,babel 会将所有的 polyfill 全部引入,这样会导致结果的包大小非常大,而我们这里仅仅需要 includes 一个方法而已。
3.正确的做法是使用按需加载,将 useBuiltIns 改为 “usage”,babel 就可以按需加载 polyfill,并且不需要手动引入 @babel/polyfill:
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"debug": true
}
]
]
}
到这里,最开始的那个问题真正的原因就有了。babel 在转译 async function 的时候,生成的代码里使用了 regeneratorRuntime 这个变量,而这个变量是放在 regenerator-runtime 这个 polyfill 库中的,所以如果不修改 useBuiltIns 引入 polyfill,那么自然会报 undefined 错误,因为根本就没有引入这个变量。
到目前为止,上面的 babel 配置还存在两个问题
- 从上面的转译结果可以看到,includes 这个 api 直接是 require 了一下,并不是另一种更符合直觉的方式:
var includes = require(‘xxx/includes’)
所以 babel 的 polyfill 机制是,对于例如 Array.from 等静态方法,直接在 global.Array 上添加;对于例如 includes 等实例方法,直接在 global.Array.prototype 上添加。这样直接修改了全局变量的原型,有可能会带来意想不到的问题。这个问题在开发第三方库的时候尤其重要,因为我们开发的第三方库修改了全局变量,有可能和另一个也修改了全局变量的第三方库发生冲突,或者和使用我们的第三方库的使用者发生冲突。公认的较好的编程范式中,也不鼓励直接修改全局变量、全局变量原型。
- babel 转译 syntax 时,有时候会使用一些辅助的函数来帮忙转,比如:
class 语法中,babel 自定义了 _classCallCheck这个函数来辅助;typeof 则是直接重写了一遍,自定义了 _typeof 这个函数来辅助。这些函数叫做 helpers。从上图中可以看到,helper 直接在转译后的文件里被定义了一遍。如果一个项目中有100个文件,其中每个文件都写了一个 class,那么这个项目最终打包的产物里就会存在100个 _classCallCheck 函数,他们的长相和功能一模一样,这显然不合理。
4. @babel/plugin-transform-runtime 这个插件的作用就是解决上面提到的两个问题
先执行下面两条命令安装两个库:
npm install @babel/plugin-transform-runtime -D
npm install @babel/runtime-corejs3
其中 @babel/plugin-transform-runtime 的作用是转译代码,转译后的代码中可能会引入 @babel/runtime-corejs3 里面的模块。所以前者运行在编译时,后者运行在运行时。类似 polyfill,后者需要被打包到最终产物里在浏览器中运行。
再修改配置:
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"debug": true
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3 // 指定 runtime-corejs 的版本,目前有 2 3 两个版本
}
]
]
}
从上图可以看到,在引入了 transform-runtime 这个插件后:
- api 从之前的直接修改原型改为了从一个统一的模块中引入,避免了对全局变量及其原型的污染,解决了第一个问题。
- helpers 从之前的原地定义改为了从一个统一的模块中引入,使得打包的结果中每个 helper 只会存在一个,解决了第二个问题。
总结
babel 在转译的过程中,对 syntax 的处理可能会使用到 helper 函数,对 api 的处理会引入 polyfill。
默认情况下,babel 在每个需要使用 helper 的地方都会定义一个 helper,导致最终的产物里有大量重复的 helper;引入 polyfill 时会直接修改全局变量及其原型,造成原型污染。
@babel/plugin-transform-runtime 的作用是将 helper 和 polyfill 都改为从一个统一的地方引入,并且引入的对象和全局变量是完全隔离的,这样解决了上面的两个问题。
以上是关于webpack处理javascript兼容性--Babel的主要内容,如果未能解决你的问题,请参考以下文章