varHarrie / varharrie.github.io

:blue_book: Personal blog site based on github issues.
https://varharrie.github.io
MIT License
3.66k stars 544 forks source link

Babel的那些库 #29

Open varHarrie opened 5 years ago

varHarrie commented 5 years ago

自从接触ES2015开始,babel就一直在用,每个项目都在用。每次初始化项目后,.babelrc@babel/core@babel/cli@babel/preset-env@babel/transform-runtime等等信手拈来,但是每次想要区别core-js@babel/poly-fill@babel/runtime@babel/plugin-transform-runtime@babel/preset-env,却又含糊不清,索性来一次整理。

我们都知道,ES的每次标准公布,都会有许多新的特性,而这些特性无非分为两部分,一部分是新语法,如箭头函数解构赋值扩展运算符for...infor...ofclassasync/await等,另一部分是API扩展,如PromiseMapSetArray.fromString.prototype.includes等。

在低版本的浏览器中要使用高版本的ES特性,就需要对其代码进行转换。对于新语法,需要通过语法转换,比如将箭头函数转化为普通函数,将赋值结构转换成单独取值,将扩展运算符转换成Object.assign等实现,而这就是babel的核心工作。对于新API,我们是可以通过JavaScript原有的语法去实现的,实现类似功能的库叫做polyfill

当然,也不是所有API都可以通过polyfill实现,比如Proxy

core-js

以模块化方式实现了ES6+的标准API,包括PromiseSymbolMapSet等,还有各种原型链上的扩展,如String.prototype.incluesArray.prototype.find等。说白了就是polyfill,对于箭头函数async/await语法,自然是无能为力的。

我们可以通过引入直接使用:

// import 'core-js' // 引入所有
import 'core-js/features/array/find';
import 'core-js/features/promise';

[1, 2, 3].find((i) => i === 1);
Promise.resolve(64).then((x) => console.log(64));

当然,这么做会直接污染全局环境,例如Promise会挂在到window上,find会挂在到Array.prototype上,也可以通过命名空间方式引入:

import find from 'core-js-pure/features/array/from';
import Promise from 'core-js-pure/features/promise';

@babel/polyfill

源码中可以看到,@babel/polyfill就是简单地直接引用core-jsregenerator-runtime。因此,它会将扩展的API直接挂载到全局(window)和对应的原型(prototype)上,但是这样会导致一些问题:

使用方式很简单,写在入口文件开头:

import '@babel/polyfill';

regenerator-runtime,一个提供用于Generator和异步函数的运行时。

@babel/plugin-transform-runtime

可以在一定程度上替换@babel/polyfill来使用,主要解决了两个问题:

  1. 避免产生多次的helper函数。

    babel在对新语法转换过程当中,会借助一些helper函数来实现,比如class语法:

    // 输入:
    class Circle {}
    
    // 输出:
    function _classCallCheck(instance, Constructor) {
     //...
    }
    
    var Circle = function Circle() {
     _classCallCheck(this, Circle);
    };

    这里的_classCallCheck每次转换class语法都会产生,数量多起来极大影响最终的代码体积。为了解决这个问题,有个@babel/runtime专门封装了这些helper函数。通过@babel/plugin-transform-runtime将这些helper函数的引用替换为从@babel/runtime中引入。

  2. 解决@babel/polyfill中污染全局环境和体积过大的问题。

    @babel/plugin-transform-runtime中维护了一份API和core-js特性模块间的映射,可以根据具体使用情况,按需自动引入这些core-js中的特性模块。

    // 输入:
    const p = Promise.resolve()
    
    // 输出:
    var _promise = require("@babel/runtime-corejs2/core-js/promise");
    
    var _promise2 = _interopRequireDefault(_promise);
    
    var p = _promise2.default.resolve();

但是,为了防止污染现有的运行环境,对于实例函数(如Array.prototype.find)并不支持。

babel-runtime6.x版本中,还集成了core-js@2.x/library(相当于core-js-pure),在7.x版本这部分被移除了,如果有需要,可以使用@babel/runtime-corejs2代替。

@babel/preset-env

在了解@babel/preset-ev先说说babel的编译过程以及pluginpreset的概念。

babel-core作为的编译过程主要分为三个步骤:

  1. 解析:通过@babbel/parser将代码转换为AST
  2. 转换:babel本身不具备代码编译转换功能,而是依赖于一个个plugin来进行处理,转换过后的代码仍然是AST
  3. 生成:通过@babel/generatorAST转换为JS代码。

所以说,plugin中实现了具体的转换功能。比如要将箭头函数转化为普通函数,就需要@babel/plugin-transform-arrow-functions,要将class语法转化为函数实现,就需要@babel/plugin-transform-classes

preset则代表一组plugin。比如需要转换jsx的语法,只需要@babel/preset-react就行了,它已经包含了@babel/plugin-transform-react-jsx@babel/plugin-transform-react-display-name@babel/plugin-transform-react-jsx-source@babel/plugin-transform-react-jsx-self

同理@babel/preset-env也代表了一组plugin。通过默认的配置,我们就能够使用ES最新规范中的语法,当然也可以通过配置来达到你需要的效果。

总结

最后简单总结一下: