Skip to content

厘清@babel/preset-env和@babel/plugin-transform-runtime #23

@xwcoder

Description

@xwcoder

Babel和core-js的关系

Babel is a compiler, core-js is a polyfill.

Babel是一个编译器,用来将那些使用了新语法的代码编译成老旧运行环境(比如ie浏览器)可以识别运行的代码,比如将constlet转换为var

core-js是一个polyfill库,使新的api(native functions)在老旧运行环境下可用,比如Object.assign, Promise。polyfill库有很多比如core-js, es-shims

由于历史原因和core-js本身的优秀,Babel做了很多工作来方便开发者使用core-js,比如@babel/preset-envuseBuiltInscorejs设置项,@babel/plugin-tranfrom-runtimecorejs设置项。

Babel主要做两件事:

  • 转换语法。
  • 添加polyfill。

@babel/preset-env

@babel/preset-env的设置项useBuiltIns用来控制是否添加core-js polyfill以及怎么添加。

  • false: 不添加
  • entry: 在入口文件添加,需要手动在入口文件添加import 'core-js'。缺点是它会import全部polyfill,即使没有用到。
  • usage: 在当前文件添加,并且只添加用到的polyfill。

举例来看,iOS 9开始支持Object.assign,不支持class

// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        debug: true,
        useBuiltIns: 'usage',
        corejs: 3,
        modules: false,
        targets: {
          ios: '8'
        },
      }
    ],
  ],
};
// source
export const config = Object.assign({}, { type: 1 });

export class Person {};
// out
import "core-js/modules/es.object.assign.js";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

export var config = Object.assign({}, {
  type: 1
});
export var Person = function Person() {
  _classCallCheck(this, Person);
};

targetsiOS 8时,@babel/preset-env自动导入了core-js以支持Object.assign,并且使用了一些辅助函数以支持class语法。这些辅助函数被称为helper

通过转译输出的代码可以看到,这样的编译有两个缺点:

  • polyfill的引入方式会污染全局环境。
  • helper直接内嵌在文件中,当一个项目有很多文件,各文件中会有大量重复的helper,这会造成bundle size变大。

@babel/plugin-transform-runtime

@babel/plugin-transform-runtime主要有两个作用:

一是从@babel/runtime包中导入helper,而不是直接嵌入到当前输出文件,从而减小包体积。这个能力通过helper选项控制,默认是true

二是添加polyfill。通过corejs选项控制,默认是false,不添加。

根据corejs选项值需要引入@babel/runtime@babel/runtime-corejs2@babel/runtime-corejs3

将上例中babel.config.js添加@babel-plugin-transform-runtime:

// babel.config.js
plugins: [
  [
    '@babel/plugin-transform-runtime',
    {
      helpers: true,
      corejs: 3,
    }
  ],
]
// out
import _classCallCheck from "@babel/runtime-corejs3/helpers/classCallCheck";
import _Object$assign from "@babel/runtime-corejs3/core-js-stable/object/assign";
export var config = _Object$assign({}, {
  type: 1
});
export var Person = function Person() {
  _classCallCheck(this, Person);
};

从转译输出可以看到:

  • @babel/runtime-xxx导入了helper
  • polyfill的引入方式不会污染全局环境。

@babel/plugin-transform-runtime和@babel/preset-env没有关系

@babel/plugin-tranform-runtime@babel/preset-env都可以添加polyfill,但是两者是完全独立的。看一下Babel作者的说法:

useBuiltIns and @babel/plugin-transform-runtime are mutually exclusive. Both are used to add polyfills: the first adds them globally, the second one adds them without attatching them to the global scope.
You should decide which behavior you want and stick with it.

link

@babel/preset-env可以根据targets导入需要的polyfill,但是会污染全局环境。@babel/plugin-tranform-runtime导入polyfill不会污染全局环境,但是它是环境无关的,只要用到的api存于在于core-js polyfill中就会被导入。

不要混用两者导入polyfill的能力,两者是独立的。例如将Demo中的targets改成iOS 9,编译后的输出依然会导入Object.assign的polyfill。@babel/preset-env@babel/plugin-transform-runtime导入polyfill各有优缺点,需要根据项目的需求选择使用其一。

当开发类库时多使用@babel/plugin-tranform-runtime,可以防止污染全局环境从而造成冲突。

RFC10008

我们肯定希望使用@babel/preset-env时可以不污染全局变量,使用@babel/plugin-transform-runtime时也可以设置类似targets的目标环境。为此Babel作者提了一个RFC

将来可能会:

  • targets作为顶级配置,这样所有presetplugin都可以共享targets
  • 提供顶级配置项polyfills

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions