• [转] babel 7 全套


    babel 官方在 2018-08-27 发布了文章,babel 7 正式发布,距离 babel 6 相隔3年。

    包含内容(官网cp):

    1. upgrade tool
    2. JavaScript 配置文件 babel.config.js
    3. 选择性的配置 overrides
    4. TC39 提案 支持 @babel/proposals
    5. jsx、typescript、flow 的支持
    6. Babel 辅助函数的变化
    7. 自动 Polyfill (试验) useBuiltins

    详细内容可见:Babel 7 发布

    此篇文章主要介绍 babel 7 相关 presetsplugins@babel 下其他所有的 packages、babel 其他工具、以及 babel 插件相关,即 babel 全套(包含部分流程上的源码链接)

    至于 AST 抽象语法树相关可见另一文 抽象语法树-AST-与-编译器-Compiler 内容。

    transform or polyfill

    ES 相关功能可分为:

    1. 新的写法、运算符、async/await、const、let、class 等老浏览器无法识别的一类
    2. Object.assign 等可通过 现有 JavaScript 代码封装实现(例如 core-js / polyfill)的语法糖

    要注意的是,babel 作为转换工具,@babel/core, 大部分(也可以理解为全部) babel plugins 只会作用于第1类。

    像 第2类 的处理,需要 @babel/polyfill (已废弃,替代品见下文),或者 @babel/preset-env 设定 useBuiltIns 进行,参见:babel-preset-env index.js L190

    具体需要用到 polyfill 的功能,可见

    babel 概览

    babel 仓库 packages

    详见: babel monorepo

    babel 7 升级后,相应 babel 包全部统一至 @babel namespace 下。

    可以理解为,babel 自身的能力,全部在 @babel namespace 下,而像 babel-loader、gulp-babel 此类其他工具的插件,则仍然作为单独的 package

    plugins

    从上方的仓库包内,或者 babel 官网 plugins 上,可以看到非常多的 plugins

    这里我们先关注 transformproposal 的插件。其对应的作用,就是将抽象语法树 A(es 2015 及以上等等) 转换为 抽象语法树 B(es 5),在上一文 抽象语法树-AST-与-编译器-Compiler 中,称为 visitor

    presets

    babel 6 支持 preset-es2015 这类年份已经成规范的内容,以及 preset-stage-0 这类未确定的草案的内容。

    而 babel 7 统一:

    1. 将 preset-es2015 这类年份已经成规范的内容统一为 @babel/preset-env,因为规范已落地,可以放心提前使用,提前了解 JS 新规范特性,也减少大家的配置负担(不需要关心到底是 es2015、es2016 还是 es2017)
    2. 将 preset-stage-0 这类未确定的草案,移出 preset。因为草案未落地,使用了这些最新的特性,可能对大家未来没有太大帮助。减少未确定的语法的干扰

    当下 presets 官方支持的有:

    • @babel/preset-env
    • @babel/preset-flow
    • @babel/preset-react
    • @babel/preset-typescript

    而如果我们还是想要使用到一些草案性质的语法、方法,在 babel monorepo 列表中,可以看到很多 plugin-proposal- 前缀的,可以使用对应的 plugin 进行引入

    另一方面,我们如果查看 @babel/preset-env 的 package.json 文件

    // etc...
    "@babel/plugin-proposal-object-rest-spread": "^7.5.5",
    "@babel/plugin-proposal-optional-catch-binding": "^7.2.0",
    "@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
    // etc...

    也就更清楚,presets 的含义,就是 “预设” 的 plugins,进行的相应组装

    注意:Plugin 会运行在 Preset 之前,Plugin 会从前到后顺序执行,Preset 的顺序则是从后向前。

    babel 转换流程

    以此段代码为例

    const a = 1
    console.log([1].includes(a))
    "scripts": {
      "build": "babel src --out-dir dist"
    }
    

    @babel/cli

    1. commander 解析
    2. dir 文件
    3. util.compile
    4. 默认 / 相关配置项
    5. 调用 @babel/core transformFile

    基于 babel 配置 / 命令行配置,使用 @babel/core 进行编译、输出

    @babel/core

    1. transformFile 读取文件,运行 runAsync
    2. transformation/index.js runSync
    3. normalizeFile
    4. transformFile
    5. generateCode

    @babel/parser

    1. 上方的 normalizeFile 对应的调用 @babel/parser

    @babel/traverse

    1. 上方的 transformFile 对应的合并 plugin vistors,调用 @babel/traverse

    @babel/generator

    1. 上方的 generateCode 对应的调用 @babel/generator

    概括

    即 @babel/core transform / transformFile ... 方法,包含了 @babel/parser,@babel/traverse,@babel/generator 连串调用。

    @babel/preset-env 配置项

    useBuiltIns && corejs

    useBuiltIns: 'entry'

    // 必须在开头引入
    import '@babel/polyfill'
    const a = 1
    console.log([1].includes(a))
    

    corejs: 2

    // 编译后,@babel/polyfill 被拆分成 N 个包
    require("core-js/modules/es6.array.copy-within");
    // *** 省略 200+行 ***
    require("regenerator-runtime/runtime");
    var a = 1;
    console.log([1].includes(a))
    

    corejs: 3

    // 没变化
    require("@babel/polyfill");
    var a = 1;
    console.log([1].includes(a));
    

    因为 @babel/polyfill 只是 core-js 2.x + regenerator-runtime 的组合,因此其无法被处理出 core-js 3

    corejs: 3 + useBuiltIns: 'entry' 的话,就会报警告: @babel/polyfill is deprecated. Please, use required parts of core-js and regenerator-runtime/runtime separately

    如果将原代码更改为:

    import 'core-js'
    import 'regenerator-runtime/runtime'
    const a = 1
    console.log([1].includes(a))
    // 编译后,core-js 被拆分成 N 个包
    require("core-js/modules/es.symbol");
    // *** 省略 500+行 ***
    require("regenerator-runtime/runtime");
    var a = 1;
    console.log([1].includes(a))
    

    useBuiltIns: 'usage'

    // 可以省略
    // import '@babel/polyfill'
    const a = 1
    console.log([1].includes(a))
    

    corejs: 2

    // 多了这两行
    require("core-js/modules/es7.array.includes");
    require("core-js/modules/es6.string.includes");
    // import '@babel/polyfill'
    var a = 1;
    console.log([1].includes(a));
    

    corejs: 3

    require("core-js/modules/es.array.includes");
    // import '@babel/polyfill'
    var a = 1;
    console.log([1].includes(a));
    

    概括

    1. @babel/polyfill 已被弃用,建议使用 core-js 和 regenerator-runtime/runtime 代替。因 @babel/polyfill 就是它俩组成,用来模拟完整的 ES2015+ 环境。确实没必要再包一层
    2. corejs 3 比 2 更完善
    3. 根据具体需要使用 useBuiltIns: 'usage' 或 useBuiltIns: 'entry' ( usage 风险:npm 的 dependencies 包进行业务开发,babel 默认是不会检测 依赖包的代码的。 也就是说,如果某个需要 polyfill 的特性,依赖包使用了但是业务代码没有使用,会引起未可知的 BUG)

    参考

    1. Polyfill 方案的过去、现在和未来
    2. Babel 社区概览

    其他配置项

    1. 详见:@babel/preset-env docs

    @babel namespace 下其他工具

    弃用

    @babel/polyfill

    已废弃,使用 import 'core-js'; import 'regenerator-runtime/runtime' 代替

    babel 其他运行方式

    @babel/node

    1. 以 child_process 形式
    2. @babel/core transform,与上方 transformFile 流程类似
    3. node repl 调用node vm 运行
    4. 才疏学浅,也不好解释更多。最关键:不要在生产环境运行。 开发环境大概可以减少一点测试时间,类似 ts-node,属于偷懒用法 / 做法,用处不大
    5. 详见:@babel/node docs

    @babel/register

    1. 通过 pirates 工具,给 node require 加了 hook
    2. 在文件顶部使用 require('@babel/register') 后,后续 require("./my-plugin.xxx") 都会经过 babel.transfrom 后,得到编译后的代码再运行
    3. 文档:@babel/register
    4. 类似 @babel/node,偷懒用法 / 做法,最关键:不要在生产环境运行

    @babel/standalone —— 浏览器 / 特定环境

    1. 一个完整的 babel.js / babel.min.js 文件,用来进行 原代码 transform 等功能
    2. 文档:@babel/standalone
    3. 一般情况下,用处不大,也用不上。除非是需要特有环境下运行的,例如:JSFiddle, JS Bin, the REPL on the Babel site

    parser 阶段语法检查

    @babel/highlight

    1. 使用 js-tokens(有趣的是,其内部用的是 另一个老牌解析器 esprima) 分词
    2. 使用 命令行高亮
    3. 具体用法见:@babel/highlight

    @babel/code-frame

    1. 使用 @babel/highlight,大致是优化了错误代码的显示,加了代码下方的箭头 / 错误信息。具体用法见:@babel/code-frame docs

    编译结果优化

    @babel/plugin-transform-runtime

    例如:

    class A {}
    

    如果不使用此插件,编译后内容

    "use strict";
    
    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
    
    var A = function A() {
      _classCallCheck(this, A);
    };
    

    如果是多个文件,那么每个文件都会有 _classCallCheck 方法,如果再把这些文件合并在一起,_classCallCheck 就会有 N 次定义,以此类推,还有其他各种函数。

    如果使用了插件

    plugins: [
      ['@babel/transform-runtime', {
        // corejs: false, use @babel/runtime
        // corejs: 2, use @babel/runtime-corejs2
        corejs: 3, // use @babel/runtime-corejs3
      }],
    ]
    

    对应的编译后内容

    // corejs false
    var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
    var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
    var A = function A() {
      (0, _classCallCheck2["default"])(this, A);
    };
    
    // corejs 2
    var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
    var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
    var A = function A() {
      (0, _classCallCheck2["default"])(this, A);
    };
    
    // corejs 3
    var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
    var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
    var A = function A() {
      (0, _classCallCheck2["default"])(this, A);
    };
    

    就仍然会是 require 形式引用。最终 webpack、browserify 等工具进行打包时, 就不会有 N 个重复的

    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
    
    1. 文档见:@babel/plugin-transform-runtime docs
    2. 看起来上方,corejs 的配置最终编译后内容没什么差距,但实际上 Symbol 等使用,编译后结果会有一些差异。因此还是推荐根据 @babel/env 配置 的 corejs 版本,相应的也 进行 @babel/transform-runtime 插件的 corejs 配置。

    @babel/runtime, @babel/runtime-corejs2, @babel/runtime-corejs3

    针对 @babel/env 的 corejs 配置

    ['@babel/env', {
      useBuiltIns: 'usage',
      corejs: 3,
    }]
    

    对应的 runtime 版本,仅用于 @babel/transform-runtime 插件内部

    @babel/plugin-external-helpers

    功能 与 @babel/plugin-transform-runtime 类似 或者 说有冲突,推荐使用 @babel/plugin-transform-runtime,此工具也就没必要了。

    其需要配合 @babel/cli 的 babel-external-helpers 命令行工具使用,参考阅读:babel-external-helpers

    babel 关于移除 @babel/plugin-external-helpers 的讨论 - Remove babel-plugin-external-helpers in favor of modular helpers

    @babel/helpers

    像前文提到的,会有 _classCallCheck 产生:

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

    其是通过 @babel/helpers helpers.js,经过 @babel/template 转换成 AST 后,插入的。

    而像 @babel/plugin-transform-runtime,实际上有一个 build-dist.js 将 @babel/helpers helpers.js 里面的这些 helper,一个个的编译成单独的文件放在 @babel/runtime helpers 目录下,相应处理后,此部分原来被添加的代码片段,就变成了

    var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
    

    其他 helpers 同理

    其他 - 解析 / 转换 / 插件开发相关

    • @babel/types
    • @babel/template
    • @babel/helper-xxxx-xxxx
    • @babel/plugin-proposal-xxxx-xxxx
    • @babel/plugin-syntax-xxxx-xxxx
    • @babel/plugin-transform-xxxx-xxxx

    此部分内容,为避免此文太长(不看),单独放到 babel 插件开发相关 一文内。

    其他 babel 工具

    还好用

    1. 已经移动到 jest 仓库,主要为 jest 的测试代码 提供 babel 转换,让其可以使用 es6 的方式编写
    2. 使用 ts-jest 貌似更好一些
    • babel-eslint,一个 AST parser,原代码 -> babel AST -> estree
    1. eslint 仅仅支持 es 最新的语法,不支持一些 proposals 的内容,因此需要 babel-eslint 将这些内容,通过 babel 转换出新的 AST,给到 eslint
    2. 补充:前文抽象语法树-AST-与-编译器-Compiler 提到 eslint 使用 espree,而 babel 根据自身情况做了一些ESTree 的调整,因此又做了一步 babylon-to-espree
    3. .eslintrc.js -> module.exports = { parser: "babel-eslint" };
    4. 配合使用 对应的规则调整 eslint-plugin-babel
    5. 使用 typescript-eslint 也行。不过都可以直接用 ts-lint 了。玩法(轮子)真多 (> w <)

    用处不大 / 再见了您

    • ember-cli-babel
    • broccoli-babel-transpiler
    • babelify
    • babel-brunch
    • grunt-babel
    • generator-babel-plugin

    其他 presets or plugins

    其他参考

  • 相关阅读:
    Python IDE
    Codeforces Beta Round #69 Div1
    HDU1595 find the longest of the shortest[最短路]
    MFC/OpenGL下的调色板
    ApacheCN 编程/大数据/数据科学/人工智能学习资源 2019.12
    计算机电子书 2016 BiliDrive 备份
    计算机电子书 2017 BiliDrive 备份
    Java项目中常见的异常处理
    从小工到专家第三次读后感
    《梦断代码》读后感1
  • 原文地址:https://www.cnblogs.com/chris-oil/p/13826178.html
Copyright © 2020-2023  润新知