• 重识babel 7


    在这里插入图片描述

    本文首发于:https://github.com/bigo-frontend/blog/ 欢迎关注、转载。

    工欲善其事,必先利其器

    作为一个前端开发者,想要使用ECMAScript 2015+新语法,又要兼容旧版的浏览器,babel相关的工具及配置是一个无法绕过去的坎。

    前段时间笔者想优化公司内部的一个npm库的size,苦于胸总中无沟壑,只能老老实实看babel的官方文档,写了4个“自认为”是使用babel的最佳配置,分别对应webapp和library的配置,希望对大家日程开发实践和性能优化有一定借鉴意义,如有不足之处,也欢迎大家斧正。

    下面我将围绕babel相关的主要npm库,为大家娓娓道来(本文基于最新的babel7)。

    @babel/core

    babel核心库,使用babel必须要安装的。

    在这里我们解释一下babel到底是什么,这里引用官方的定义:

    Babel 是一个 JavaScript 编译器

    Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情

    • 语法转换
    • 通过 Polyfill 方式在目标环境中添加缺失的特性(通过第三方 polyfill 模块,例如 core-js,实现)
    • 源码转换 (codemods)
    // Babel 输入: ES2015 箭头函数
    [1, 2, 3].map((n) => n + 1);
    
    // Babel 输出: ES5 语法实现的同等功能
    [1, 2, 3].map(function(n) {
      return n + 1;
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    @babel/cli:

    babel命令行工具,单独使用babel时安装,当babel配合webpackrollup等打包工具一起使用的时候,通常会有相应的loader或着plugin,此时可能并不需要@babel/cli

    @babel/preset-env

    在解释这个库的作用之前,我们看一下最原始的babel配置是怎样的:

    babel.config.js

    module.exports = {
      plugins: [
        "@babel/plugin-transform-block-scoping", 
        "@babel/plugin-transform-arrow-functions",
        [
          "babel-plugin-polyfill-corejs3",
          {
            "method": "usage-global"
          }
        ]
      ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    input

    // index.js
    const a = 'hello world'
    
    const set = new Set();
    
    const foo = () => {
      console.log('function')
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    output

    require("core-js/modules/es.array.iterator.js");
    
    require("core-js/modules/es.object.to-string.js");
    
    require("core-js/modules/es.set.js");
    
    require("core-js/modules/es.string.iterator.js");
    
    require("core-js/modules/web.dom-collections.iterator.js");
    
    var a = 'hello world';
    var set = new Set();
    
    var foo = function foo() {
      console.log('function');
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在我们的代码中,用到了哪些特性,就需要把对应的babel插件添加进来,后续如果我们还要添加其他的esnext特性,就要这样一个一个的加入各种各样的插件,这对开发这来说非常的不友好。

    如果有一个工具可以把常用的plugin都一股脑加进来,开发者并不需要关心自己的代码用了什么新特性,也不关心要安装哪些babel插件,添加plugin的这些工作全都由这个工具去完成,那就轻松很多了。

    这时候preset就登场了,我看看官方介绍:

    Babel 的预设(preset)可以被看作是一组 Babel 插件和或 options 配置的可共享模块。

    preset有很多,官方的preset中,有根据stage的不同,和ECMAScript的版本的不同推出的各种preset,而今天我们的主角是@babel/preset-env,其他的基本都被废弃掉了。

    通过官方文档的描述,preset-env主要做的是转换JavaScript最新的语法,而作为可选项 preset-env 也可以转换 JavaScript 最新的 API (指的是比如数组最新的方法includes,Promise等等)

    总之,就是把所有的常用插件都汇聚到了一起,省去了自己配置插件的功夫。

    这个插件有很多选项可以配置,我们挑几个重要的讲

    useBuiltIns

    "usage" | "entry" | false, defaults to false.

    此选项配置 @babel/preset-env 如何处理 polyfill。

    entry

    我们需要在代码的入口文件顶部加入两行代码:

    import "core-js";
    import "regenerator-runtime/runtime";
    
    • 1
    • 2

    会在此时 babel 会根据当前targets描述,把需要的所有的 polyfills 全部引入到你的入口文件(注意是全部,不管你是否有用到高级的 API)

    usage

    无需额外代码,babel 会根据用户代码的使用情况,并根据 targets 自行注入相关 polyfills。

    false

    这种方式下,不会引入 polyfills,你需要人为在入口文件处import ‘@babel/polyfill’ 全量引入。或者手工引入对应模块的polyfill。

    corejs

    string |{ version: string, proposals: boolean },defaults "2.0"

    此选项仅在与 useBuiltIns: usage 或 useBuiltIns: entry 一起使用时才有效,并确保 @babel/preset-env 注入core-js版本支持的polyfill。

    选项需要安装对应的corejs版本

    npm install core-js@3 --save
    
    # or
    
    npm install core-js@2 --save
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可能的配置如下

    // babel.config.js
    module.exports = {
      presets: [
        [
          '@babel/preset-env',
          {
            useBuiltIns: 'usage',
            corejs: { version: '3.16', proposals: true }, // 实际的corejs版本
          }
        ]
      ]
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    targets

    运行代码的目标浏览器。

    亦可以使用browserslist代替该选项。

    loose

    boolean, defaults to false.

    优化编译的产物,如果设置为true,则会生成性能更高的转译代码,但可能不太符合ES规范。具体查看assumptions

    modules

    “amd” | “umd” | “systemjs” | “commonjs” | “cjs” | “auto” | false, defaults to “auto”.

    启用 ES 模块语法到另一种模块类型的转换。

    如果是配合打包工具rollup或是webpack,则false即可。

    @babel/plugin-transform-runtime

    一个插件,可以重用 Babel 注入的辅助代码以节省代码大小。

    helpers

    boolean, defaults to true.

    切换是否将内联的 Babel 助手(classCallCheck、extends 等)替换为对 moduleName 的调用。

    corejs

    false, 2, 3 or { version: 2 | 3, proposals: boolean }, defaults to false.

    @babel/preset-env添加的polyfill都是污染全局的,对于webapp来说是可以接受,而作为library的开发者,并不希望污染全局。

    默认情况下,@babel/plugin-transform-runtime 不polyfill提案阶段的api。如果使用的是 corejs: 3,可以通过使用 proposal: true 选项来启用它。

    需要的依赖项如下

    corejs optionInstall command
    falsenpm install --save @babel/runtime
    2npm install --save @babel/runtime-corejs2
    3npm install --save @babel/runtime-corejs3

    webapp最佳实践1

    这套针对webapp的配置,最大程度的增加对目标浏览器(运行环境)的支持,即便是项目依赖里使用的某个依赖库里使用了某些高级api,代码亦可正常运行。因为他会根据targets去引入目标targets浏览器所需的polyfill,而不管你代码中是否使用了该特性。当然,这种方式的缺点就是打包后的包体积会比较大,有很大可能会包含一些并未用到的polyfill。

    npm install -D @babel/core @babel/preset-env @babel/plugin-transform-runtime
    npm install -S core-js@3
    
    • 1
    • 2
    // babel.config.js
    module.exports = {
      presets: [
        [
          '@babel/preset-env',
          {
            useBuiltIns: 'entry',
            targets: 'Android 4.0, IOS 7', // .browserslistrc
            corejs: { version: '3.16', proposals: true }, // 实际的corejs版本
            loose: true,
            modules: false
          }
        ]
      ],
      plugins: [['@babel/plugin-transform-runtime', { helpers: true }]]
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    // index.js
    import "core-js/stable"
    import "regenerator-runtime/runtime"
    // other code
    
    • 1
    • 2
    • 3
    • 4

    webapp最佳实践2

    这个webapp的配置,则仅针对代码中使用到的api添加polyfill,最大程度的减小打包体积。然而,由于babel不会再对依赖库中的产物进行编译,因此babel便无法检测到依赖库里的代码,一旦某个依赖库需要依赖某些polyfill,则可能最终类库会无法运行。

    npm install -D @babel/core @babel/preset-env @babel/plugin-transform-runtime
    npm install -S core-js@3
    
    • 1
    • 2
    // babel.config.js
    module.exports = {
      presets: [
        [
          '@babel/preset-env',
          {
            useBuiltIns: 'usage',
            targets: 'Android 4.0, IOS 7', // .browserslistrc
            corejs: { version: '3.16', proposals: true }, // 实际的corejs版本
            loose: true,
            modules: false
          }
        ]
      ],
      plugins: [['@babel/plugin-transform-runtime', { helpers: true }]]
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    library最佳实践1

    这个是针对"不关心类库体积大小"的场景下的一个类库开发最佳实践。使用如下的babel配置时,polyfill不会污染全局,同时又能让类库自己能正常运行,不至于代码运行在低版本浏览器里直接就报错。

    然而,由于类库自身添加了局部的polyfill,会使你打包后的体积膨胀。如果类库提供给一个C端应用使用的话,那么应用自身的全局polyfill和类库自身的局部polyfill必然会存在冗余,这样无形又增大了应用的体积,反而降低应用的性能。

    因此,这个实践,只适用于"不关注性能"的场景。

    npm install -D @babel/core @babel/preset-env @babel/plugin-transform-runtime
    npm install -S @babel/runtime-corejs3
    
    • 1
    • 2
    // babel.config.js
    module.exports = {
      presets: [
        [
          '@babel/preset-env', 
          { 
            loose: true, 
            modules: false
          }
        ]
      ],
      plugins: [
        [
          '@babel/plugin-transform-runtime', 
          { 
            helpers: true, 
            corejs: {
              version: 3,
              proposals: true
            }
          }
        ]
      ]
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    library最佳实践2

    以下配置,比较适合 “公司内自有C端业务” 或 “开源类库产品”。通常这些产品对性能有着极致的要求。因此在代码体积方面 “寸土寸金”。

    基于此,我们可以直接将类库的corejs配置设置为false,也就是类库自身不添加任何polyfill。这就要求我们在开发类库项目时,要做到如下2点任选其一:

    1、直接放弃使用ES5+的新特性,使用原生API语法来编写代码(在社区中可以看到很多类库作者是这样做的)

    2、可以使用ES6语法,但是要通过文档告诉宿主环境,也就是告诉我们的调用者,让他注意要主动手工引入对应的polyfill。

    // babel.config.js
    module.exports = {
      presets: [
        [
          '@babel/preset-env', 
          { 
            loose: true, 
            modules: false
          }
        ]
      ],
      plugins: [
        [
          '@babel/plugin-transform-runtime', 
          { 
            helpers: true, 
            corejs: false
          }
        ]
      ]
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    总结

    从上面babel实践和 vue/babel-preset-app 的一些实践总结来看的话,可以将library和webapp最佳实践做如下的总结:

    1. 对于类库开发来说,比如我们要给公司或者github上开发一个开源类库。可能我们大部分"要照顾性能的场景下"最好就把polyfill设置为false, 只把helper设置为true。 然后编码时只用es5语法写类库,或者使用es6但要通过文档告诉调用者。

    2. 如果主web项目的依赖库是以ES5的形式释出的,同时依赖库若使用了ES6+特性。此时,要看该依赖库的作者是否"在文档中声明了其依赖的polyfill"。

    • 若作者声明了依赖polyfill列表。那么我们可以在主项目中使用useBuiltIns:'usage',且需要预先引入类库所需的polyfills。一般项目的引入方法可以使用import语法在入口文件引入,具体模块需参考corejs文档;而若是使用了vue/babel-preset-app的项目,则可以直接通过其polyfill选项配置来指定。
    • 若类库作者并没有声明所依赖的polyfill。则我们为了保险起见,则可以将主项目的babel配置为useBuiltIns:'entry'。从而尽最大可能保证我们主项目引入的全局polyfill能覆盖类库所需。
    1. 若主项目依赖库是用ES6+语法来写的,且使用了目标浏览器不支持的API特性。那么我们可以在主项目中使用useBuiltIns:'usage'的配置。然后在主项目代码中的babel和webpack配置里将对应的依赖库设置为include编译包含,这样的话babel编译则会将该依赖库按照主项目的配置进行编译并遵循useBuiltIns:'usage'配置进行polyfill。(若你主项目是使用vue/babel-preset-app,则请参考其文档进行对应项的配置)

    参考自: https://www.npmjs.com/package/@vue/babel-preset-app

    相关库

    以下为在babel配置实践过程中用到的相关库及版本

    • “@babel/cli”: “^7.14.8”
    • “@babel/core”: “^7.14.8”
    • “@babel/plugin-transform-runtime”: “^7.15.0”
    • “@babel/preset-env”: “^7.15.0”
    • “@babel/runtime-corejs3”: “^7.15.3”
    • “core-js”: “^3.16.1”

    link

    欢迎大家留言讨论,祝工作顺利、生活愉快!

    我是bigo前端,下期见。

  • 相关阅读:
    【MySQL】mysql | 数据处理 | 行转列 | 一种行转列的处理思路
    字符串Hash学习笔记
    线程安全问题 的小案例
    SpringBoot入门
    Hadoop生态圈之元数据管理Atlas
    贴近摄影测量 | 中国最神秘的建筑!
    智慧守护 畅游无忧——北斗应急呼叫柱,为景区安全加码
    Python之多进程
    element-ui tree组件实现在线增删改
    最全的推特群推营销秘籍
  • 原文地址:https://blog.csdn.net/yeyeye0525/article/details/126262032