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 配置还存在两个问题

  1. 从上面的转译结果可以看到,includes 这个 api 直接是 require 了一下,并不是另一种更符合直觉的方式:

var includes = require(‘xxx/includes’)
所以 babel 的 polyfill 机制是,对于例如 Array.from 等静态方法,直接在 global.Array 上添加;对于例如 includes 等实例方法,直接在 global.Array.prototype 上添加。这样直接修改了全局变量的原型,有可能会带来意想不到的问题。这个问题在开发第三方库的时候尤其重要,因为我们开发的第三方库修改了全局变量,有可能和另一个也修改了全局变量的第三方库发生冲突,或者和使用我们的第三方库的使用者发生冲突。公认的较好的编程范式中,也不鼓励直接修改全局变量、全局变量原型。

  1. 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的主要内容,如果未能解决你的问题,请参考以下文章

跨域 webpack + vue-cil 中 proxyTable 处理跨域

webpack5处理js资源

一webpack的基本使用

一webpack的基本使用

webpack对css进行各大平台浏览器的兼容性处理

webpack配置css兼容性和压缩