Skip to content

babel 的 preset 和 js 的标准提案 #5

@inetfuture

Description

@inetfuture

引言

babel 从 6 开始有 preset 的概念,用来组织繁杂的插件,其实就相当于快捷方式,帮助你快速指定需要的插件组合。比如 babel-preset-react,只需要指定这一个 preset,就可以使用 react 开发所需的插件,包括 babel-plugin-syntax-jsx 等。

preset 和 js 的标准提案

除了 react preset 外,官方维护 preset 还有这些:

  • flow:包含 flow type 所需插件
  • es2015:包含 es2015 语法标准所有相关插件
  • es2016:包含 es2016 语法标准所有相关插件
  • es2017:包含 es2017 语法标准所有相关插件
  • latest:包含从 2015 开始历年语法标准所有相关插件
  • env:在 latest 基础上提供环境配置能力,比如可以配置只支持某一个浏览器的某几个版本,会自动按需启用、禁用插件
  • stage-0:包含处于标准提案 stage 0 阶段的语法所有相关插件
  • stage-1:包含处于标准提案 stage 1 阶段的语法所有相关插件
  • stage-2:包含处于标准提案 stage 2 阶段的语法所有相关插件
  • stage-3:包含处于标准提案 stage 3 阶段的语法所有相关插件

那么 es2015, stage-0 等等这些之间到底是什么关系呢?

js 有很长的历史,中间有多年停滞不前,不但没有任何改进,还因为浏览器大战导致极度混乱。但是随着互联网繁荣,前端对 js 越来越倚重,再加上服务端 node.js 兴起,促使社区成立了标准委员会,开始推进 js 的升级,这个委员会叫做 TC39。

他们负责讨论和改进标准提案,最终通过后,会将多个提案整理到某一年的发布中。es2015 就是第一个发布。

中间每一个提案,都要依次经过 stage 0、stage 1、stage 2、stage 3、stage 4 五个阶段。其中到达 stage4 的为已通过,会出现在下一年的发布中。到达 stage3 的属于比较成熟的,极有可能在较小改动的前提下通过并发布,stage3 之前的则不太成熟,stage 越往前越容易大幅改动甚至被完全去掉。所以对于开发者来说,使用 stage 3 之前的语法有一定风险。

知道了这些,babel 的这些 preset 也就不难理解,es20xx 的,都是已经正式通过并在那一年发布的,stage-x 的都是还在讨论中,不太稳定的。

在这里可以查看各个提案的状态:

我们以 Rest/Spread Properties 为例,该提案处在 stage 3,所以如果你想使用包含它的 preset,应该使用 babel-preset-stage-3(当然你始终可用直接指定插件 babel-plugin-transform-object-rest-spread 而非 preset)。

我之前用的时候看到 babel-preset-latest,以为它包含了所有的,实际它只包含了所有正式发布的。😢

示例

babel 的最佳实践是使用 babel-preset-env,示例配置文件 .babelrc 如下:

{
  "presets": [
    ["env", {
      "targets": {
        "node": "current",
        "browsers": "last 2 versions"
      }
    }]
  ]
}

相当于声明“我需要支持当前使用的 node.js 版本,以及主流浏览器的最近两个版本”,该 preset 会自动启用先关插件以满足这个需求。

假设我们当前使用的 node.js 版本是 v7.7.4,有如下代码 demo.js

import { MongoClient } from 'mongodb';

async function run() {
    const db = await MongoClient.connect('mongodb://localhost/test');
    console.log(db);
}

run();

执行 babel demo.js 得到编译结果:

'use strict';

var run = function () {
    var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
        var db;
        return regeneratorRuntime.wrap(function _callee$(_context) {
            while (1) {
                switch (_context.prev = _context.next) {
                    case 0:
                        _context.next = 2;
                        return _mongodb.MongoClient.connect('mongodb://localhost/test');

                    case 2:
                        db = _context.sent;

                        console.log(db);

                    case 4:
                    case 'end':
                        return _context.stop();
                }
            }
        }, _callee, this);
    }));

    return function run() {
        return _ref.apply(this, arguments);
    };
}();

var _mongodb = require('mongodb');

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

run();

编译结果是较复杂的,主要是因为现在主流浏览器还不支持 async/await,babel 做了很多工作。实际上即便如此编译出来,也是不能直接在浏览器跑的(不考虑我这里用 mongodb 的库作为例子),还需要引入 generator 运行时。

如果我们把上面配置中的 "browsers": "last 2 versions" 去掉,只留下 "node": "current",编译结果则简单许多,因为我们所使用的 v7.7.4 版 node.js 已经原生支持 async/await,babel 不再需要额外处理,如下:

'use strict';

var _mongodb = require('mongodb');

async function run() {
    const db = await _mongodb.MongoClient.connect('mongodb://localhost/test');
    console.log(db);
}

run();

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions