• @babel/plugin-transform-runtime 到底是什么?


    很多初学者在刚接触 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",
            "debug": true
          }
        ]
      ]
    }

    转译结果

    可以看到,这种模式下,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 上添加。这样直接修改了全局变量的原型,有可能会带来意想不到的问题。这个问题在开发第三方库的时候尤其重要,因为我们开发的第三方库修改了全局变量,有可能和另一个也修改了全局变量的第三方库发生冲突,或者和使用我们的第三方库的使用者发生冲突。公认的较好的编程范式中,也不鼓励直接修改全局变量、全局变量原型。

    2. babel 转译 syntax 时,有时候会使用一些辅助的函数来帮忙转,比如:

    class 语法中,babel 自定义了 _classCallCheck这个函数来辅助;typeof 则是直接重写了一遍,自定义了 _typeof 这个函数来辅助。这些函数叫做 helpers。从上图中可以看到,helper 直接在转译后的文件里被定义了一遍。如果一个项目中有100个文件,其中每个文件都写了一个 class,那么这个项目最终打包的产物里就会存在100个 _classCallCheck 函数,他们的长相和功能一模一样,这显然不合理。

     

    4. @babel/plugin-transform-runtime 这个插件的作用就是解决上面提到的两个问题

    先执行下面两条命令安装两个库:

    yarn add @babel/plugin-transform-runtime -D
    
    yarn add @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 都改为从一个统一的地方引入,并且引入的对象和全局变量是完全隔离的,这样解决了上面的两个问题。

    转 https://zhuanlan.zhihu.com/p/147083132

  • 相关阅读:
    centos networkmanager 和 network配置冲突
    Struts ajax json重新整理
    Struts2 ajax json小例子
    (转)json-lib 的maven dependency
    Struts2文件下载
    jQuery自定义滚动条插件mCustomScrollbar
    Struts2自定义拦截器
    Spring的自动装配在session监听器失效
    mysql 分组+排序+限定
    mysql触发器
  • 原文地址:https://www.cnblogs.com/zhishaofei/p/13896056.html
Copyright © 2020-2023  润新知