美文网首页框架学习
了解 Babel 各个模块

了解 Babel 各个模块

作者: shianqi | 来源:发表于2017-11-09 16:32 被阅读141次

    了解 Babel 各个模块

    本文所研究的是 babel 6 版本。
    babel 62015年10月30号 发布,主要做了以下更新:

    • 拆分成几个核心包,babel-core, babel-node, babel-cli...
    • 没有了默认的转换,现在你需要手动的添加 plugin。也就是插件化
    • 添加了 preset,也就是预置条件。
    • 增加了 .babelrc 文件,方便自定义的配置。

    babel 中有很多包,必须弄明白这些是干嘛的,才能让我们更好的使用这个工具。

    • babel-core
    • babel-cli
    • babel-external-helpers
    • babel-node
    • babel-register
    • babel-runtime
    • babel-polyfill

    babel-core

    babel 的核心包,包括核心 api,比如 transform,主要是处理转码的。 它会把我们的 js 代码,抽象成 ast,即 abstract syntax tree 的缩写,是源代码的抽象语法结构的树状表现形式。

    主要 API

    var babel = require('babel-core');
    var transform = babel.transform;
    
    // babel.transform(code: string, options?: Object)
    transform("code", options) // => { code, map, ast }
    
    // babel.transformFile(filename: string, options?: Object, callback: Function)
    var path = require('path');
    var result = babel.transformFileSync(
        path.resolve(__dirname, './test.js'),
        {
            presets: ['env'],
            plugins: ['transform-runtime'],
        },
        function(err, result) {// { code, map, ast }
            console.log(result);
        });
    
    // babel.transformFileSync(filename: string, options?: Object)
    var result = babel.transformFileSync(
        path.resolve(__dirname, './test.js'),
        {
            presets: ['env'],
            plugins: ['transform-runtime'],
        }
    );
    
    // babel.transformFromAst(ast: Object, code?: string, options?: Object)
    // 把 ast 传入,解析为 code 代码
    

    babel-cli

    提供命令行运行 babel

    babel-external-helpers

    babel-cli 中的一个 command,用来生成 helper 函数。

    babel 有很多辅助函数,例如 toArray 函数, jsx 转化函数。这些函数是 babel transform 的时候用的,都放在 babel-helpers 这个包中。如果 babel 编译的时候检测到某个文件需要这些 helpers,在编译成模块的时候,会放到模块的顶部。

    但是如果多个文件都需要提供,会重复引用这些 helpers,会导致每一个模块都定义一份,代码冗余。所以 babel 提供了这个命令,用于生成一个包含了所有 helpers 的 js 文件,用于直接引用。然后再通过一个 plugin,去检测全局下是否存在这个模块,存在就不需要重新定义了。

    使用:

    1. 执行 babel-external-helpers 生成 helpers.js 文件

      node_modules/.bin/babel-external-helpers > helpers.js
      
    2. 安装 plugin

      npm install --save-dev babel-plugin-external-helpers
      
    3. 然后在 babel 的配置文件加入

      {
          "plugins": ["external-helpers"]
      }
      
    4. 入口文件引入 helpers.js

      require('./helpers.js');
      

    如果使用了 transform-runtime,就不需要生成 helpers.js 文件了,这个在后面的 babel-runtime 再说。

    babel-node

    也是 babel-cli 下面的一个 command,主要是实现了 node 执行脚本和命令行写代码的能力。

    babel-register

    通过改写 node 本身的 require,添加钩子,然后在 require 其他模块的时候,就会触发 babel 编译。也就是你引入 require('babel-register') 的文件代码,是不会被编译的。只有通过 require 引入的其他代码才会。

    npm install babel-register --save-dev
    
    require('babel-register')({ presets: ['react'] });
    require('./test')
    

    它的特点就是实时编译,不需要输出文件,执行的时候再去编译。所以它很适用于开发。总结一下就是,多用在 node 跑程序,做实时编译用的,通常会结合其他插件作编译器使用,比如 mocha 做测试的时候。

    babel-runtime

    npm install babel-runtime --save
    

    Babel 转译后的代码要实现源代码同样的功能需要借助一些帮助函数。可能会重复出现在一些模块里,导致编译后的代码体积变大。Babel 为了解决这个问题,提供了单独的包 babel-runtime 供编译模块复用工具函数。(core-jsregenerator

    启用插件 babel-plugin-transform-runtime 后,Babel 就会使用 babel-runtime 下的工具函数,转译代码如下:

    'use strict';
    // 之前的 _defineProperty 函数已经作为公共模块 `babel-runtime/helpers/defineProperty` 使用
    var _defineProperty2 = require('babel-runtime/helpers/defineProperty');
    var _defineProperty3 = _interopRequireDefault(_defineProperty2);
    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
    var obj = (0, _defineProperty3.default)({}, 'name', 'JavaScript');
    

    除此之外,babel 还为源代码的非实例方法(Object.assign,实例方法是类似这样的 "foobar".includes("foo"))和 babel-runtime/helps 下的工具函数自动引用了 polyfill。这样可以避免污染全局命名空间,非常适合于 JavaScript 库和工具包的实现。例如 const obj = {}, Object.assign(obj, { age: 30 }); 转译后的代码如下所示:

    'use strict';
    // 使用了 core-js 提供的 assign
    var _assign = require('babel-runtime/core-js/object/assign');
    var _assign2 = _interopRequireDefault(_assign);
    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
    var obj = {};
    (0, _assign2.default)(obj, {
      age: 30
    });
    

    babel-runtime 适合 JavaScript 库和工具包实现

    • 避免 babel 编译的工具函数在每个模块里重复出现,减小库和工具包的体积;
    • 在没有使用 babel-runtime 之前,库和工具包一般不会直接引入 polyfill。否则像 Promise 这样的全局对象会污染全局命名空间,这就要求库的使用者自己提供 polyfill。这些 polyfill 一般在库和工具的使用说明中会提到,比如很多库都会有要求提供 es5 的 polyfill。在使用 babel-runtime 后,库和工具只要在 package.json 中增加依赖 babel-runtime,交给 babel-runtime 去引入 polyfill 就行了;

    core-js

    core-js 是用于 JavaScript 的组合式标准化库,它包含 ES5 (e.g: object.freeze), ES6PromiseSymbols, Collections, Iterators, Typed arrayses7+提案等等的 polyfills 实现。

    regenerator

    它是来自于 facebook 的一个库,链接。主要就是实现了 generator/yeild, async/await。

    所以 babel-runtime 是单纯的实现了 core-js 和 regenerator 引入和导出,比如这里是 filter 函数的定义,做了一个中转并处理了 esModule 的兼容。

    babel-polyfill

    Babel 默认只转换新的 JavaScript 语法,而不转换新的 API。例如,IteratorGeneratorSetMapsProxyReflectSymbolPromise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。如果想使用这些新的对象和方法,必须使用 babel-polyfill,为当前环境提供一个垫片。

    不同于 babel-runtime 的是,babel-polyfill 是一次性引入你的项目中的,就像是 React 包一样,同项目代码一起编译到生产环境。

    注意: babel

    transform-runtime 和 babel-polyfile 对比

    • babel-polyfill 是当前环境注入这些 es6+ 标准的垫片,好处是引用一次,不再担心兼容,而且它就是全局下的包,代码的任何地方都可以使用。缺点也很明显,它可能会污染原生的一些方法而把原生的方法重写。如果当前项目已经有一个 polyfill 的包了,那你只能保留其一。而且一次性引入这么一个包,会大大增加体积。如果你只是用几个特性,就没必要了,如果你是开发较大的应用,而且会频繁使用新特性并考虑兼容,那就直接引入吧。

    • transform-runtime 是利用 plugin 自动识别并替换代码中的新特性,你不需要再引入,只需要装好 babel-runtime 和 配好 plugin 就可以了。好处是按需替换,检测到你需要哪个,就引入哪个 polyfill,如果只用了一部分,打包完的文件体积对比 babel-polyfill 会小很多。而且 transform-runtime 不会污染原生的对象,方法,也不会对其他 polyfill 产生影响。所以 transform-runtime 的方式更适合开发工具包,库,一方面是体积够小,另一方面是用户(开发者)不会因为引用了我们的工具,包而污染了全局的原生方法,产生副作用,还是应该留给用户自己去选择。缺点是随着应用的增大,相同的 polyfill 每个模块都要做重复的工作(检测,替换),虽然 polyfill 只是引用,编译效率不够高效。

    plugin

    babel-plugin-transform-runtime

    transform-runtime 是为了方便使用 babel-runtime 的,它会分析我们的 ast 中,是否有引用 babel-rumtime 中的垫片(通过映射关系),如果有,就会在当前模块顶部插入我们需要的垫片。

    另外,它还有几个配置

    // 默认值
    {
      "plugins": [
        ["transform-runtime", {
          "helpers": true,
          "polyfill": true,
          "regenerator": true,
          "moduleName": "babel-runtime"
        }]
      ]
    }
    

    如果你只需要用 regenerator,不需要 core-js 里面的 polyfill 那你就可以在 options 中把 polyfill 设为 falsehelpers 设为 false,就相当于没有启用 babel-plugin-external-helpers 的效果,比如翻译 async 的时候,用到了 asyncToGenerator 函数,每个文件还会重新定义一下。moduleName 的话,就是用到的库,你可以把 babel-runtime 换成其他类似的。

    presets

    presets 就是 plugins 的组合,你也可以理解为是套餐

    babel-preset-lastet(包括 es2105,es2016,es2017)跟默认情况下的 env 是一样的,也就是说包括 lastest 在内,这四个 presets 都要被 babel-preset-env 代替。即:

    babel-preset-env

    它能根据当前的运行环境,自动确定你需要的 pluginspolyfills。通过各个 es 标准 feature 在不同浏览器以及 node 版本的支持情况,再去维护一个 featureplugins 之间的映射关系,最终确定需要的 plugins

    注意: babel-preset-env 并不是包括所有的 babel-preset-esbabel-preset-stag,而是所有的 babel-preset-esbabel-preset-stag-4
    详情请看这里

    //.babelrc
    
    {
      "presets": [
        [
          "env",
          {
            "targets": { // 配支持的环境
              "browsers": [ // 浏览器
                "last 2 versions",
                "safari >= 7"
              ],
              "node": "current"
            },
            "modules": true,  //设置ES6 模块转译的模块格式 默认是 commonjs
            "debug": true, // debug,编译的时候 console
            "useBuiltIns": false, // 是否开启自动支持 polyfill
            "include": [], // 总是启用哪些 plugins
            "exclude": []  // 强制不启用哪些 plugins,用来防止某些插件被启用
          }
        ]
      ],
      plugins: [
        "transform-react-jsx" //如果是需要支持 jsx 这个东西要单独装一下。
      ]
    }
    

    useBuiltIns

    env 会自动根据我们的运行环境,去判断需要什么样的 polyfill,而且,打包后的代码体积也会大大减小,但是这一切都在使用 useBuiltIns,而且需要你安装 babel-polyfill,并 import。它会启用一个插件,替换你的import 'babel-polyfill',不是整个引入了,而是根据你配置的环境和个人需要单独的引入 polyfill

    总结

    • 具体项目还是需要使用 babel-polyfill 配合 useBuiltIns,只使用 babel-runtime 的话,实例方法不能正常工作(例如 "foobar".includes("foo"));
    • JavaScript 库和工具可以使用 babel-runtime 配合 babel-plugin-transform-runtime,在实际项目中使用这些库和工具,需要该项目本身提供 polyfill

    参考文献


    相关文章

      网友评论

        本文标题:了解 Babel 各个模块

        本文链接:https://www.haomeiwen.com/subject/bocmmxtx.html