Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【bigo】重识babel 7 #67

Open
russellmars opened this issue Sep 1, 2021 · 0 comments
Open

【bigo】重识babel 7 #67

russellmars opened this issue Sep 1, 2021 · 0 comments

Comments

@russellmars
Copy link

russellmars commented Sep 1, 2021

工欲善其事,必先利其器

作为一个前端开发者,想要使用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;
});

@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"
      }
    ]
  ]
}

input

// index.js
const a = 'hello world'

const set = new Set();

const foo = () => {
  console.log('function')
}

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');
};

在我们的代码中,用到了哪些特性,就需要把对应的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";

会在此时 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

可能的配置如下

// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: { version: '3.16', proposals: true }, // 实际的corejs版本
      }
    ]
  ]
};

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 option Install command
false npm install --save @babel/runtime
2 npm install --save @babel/runtime-corejs2
3 npm 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
// 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 }]]
};
// index.js
import "core-js/stable"
import "regenerator-runtime/runtime"
// other code

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
// 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 }]]
};

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
// 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
        }
      }
    ]
  ]
};

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
      }
    ]
  ]
};

总结

从上面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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant