Babel入门

简述

Babel 是一个解决 javascript 兼容性问题的工具链,它支持将 ECMAScript 2015 及更新的 javascript 代码转换为可兼容早期 javascript 版本的代码,从而兼容不同浏览器或者其他运行环境(如不同的 node.js 版本)。从本质上讲,Babel 就是一个编译器。

下面示例把 ES2015 的箭头函数转换为了 funtion 函数:

//  Input: ES2015 arrow function
[1, 2, 3].map((n) => n + 1);

//  Output: ES5 equivalent
[1, 2, 3].map(function(n) {
  return n + 1;
});
upload.png

安装 & 运行

安装三个包

至少在项目中安装以下包(开发环境中安装即可):

yarn add --dev @babel/core @babel/cli @babel/preset-env

如果要支持 typescript,则需要再多安装一个预设环境:

yarn add --dev @babel/preset-typescript

配置Babel

Babel版本≥v7.8

如果你的 Babel 版本大于等于 v7.8.0,则可以使用 json 文件来配置。在项目根目录中创建一个名为 babel.config.json 的文件,基本内容为:

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1",
          "node": "current",
        },
        "useBuiltIns": "usage",
        "corejs": "3.6.5",
      }
    ]
  ]
}

简单来说,上面文件通过 presets 字段预置了 @babel/env 环境。其中,指定了目标运行环境及其版本号;babel 将会把源码转换为能够适配这些运行环境的代码。

如果和你的运行环境不同,可依具体情况修改、增加或减少目标平台及版本。

Babel版本小于v7.8

如果 Babel 版本小于 v7.8.0,则需要通过 js 文件来配置。在项目根目录中创建一个名为 babel.config.js 的文件,内容为:

const presets = [
  [
    "@babel/env",
    {
      targets: {
        edge: "17",
        firefox: "60",
        chrome: "67",
        safari: "11.1",
        node: "current",
      },
      useBuiltIns: "usage",
      "corejs": "3.6.4",
    },
  ],
];

module.exports = { presets };

本文将以该方式进行说明

编译

编译单个文件

在项目的 src 目录下创建一个文件 test.ts

const welcome = (name: string): string => {
  return `Hello ${name}`;
}
console.log(welcome.toString())
console.log(welcome("linxiaozhou"))

在项目根目录执行命令:

./node_modules/.bin/babel src/test.js --out-file dist/test.js

Babel会将 src/test.ts 文件进行编译,同时将编译后的文件输出到 dist/test.js

如果你不想用 typescript,则将 .ts 改为 .js 即可。


或者更简单地、使用 npx(npm package runner,npm@5.2.0 及更高版本提供)命令:

npx babel src/test.ts --out-file dist/test.js

如果不指定 --out-file dist/test.js 输出的路径,则 Babel 会直接把编译后的文件在控制台打印出来:

upload.png

编译指定目录

src 目录下的全部文件转换并存储在 dist 目录中:

npx babel src --out-dir dist

如果要编译 typescript 代码,必须显式地指定后缀名(--extensions ".ts" 或使用简写 -x ".ts"),而如果代码中既有 js 又有 ts,则可以指定两种:

npx babel src -x ".js,.ts" --out-dir dist

写进package.json

为了方便我们部署,可以将编译的命令直接写进 package.json

{
    ...
  "scripts": {
    "build": "babel src -x \".js,.ts\" --out-dir dist",
    ...
}

运行

有时候我们需要在 node.js 环境下通过命令直接运行编译后的 javascript 或 typescript 文件、并且不想先 build 出一个文件。此时我们就需要一个安装一个 @babel/node 工具:

yarn add --dev @babel/node

基本命令格式为:

babel-node [options] [ -e script | script.js ] [arguments]
upload.png

详情可参看下面链接: https://babeljs.io/docs/en/babel-node

编译并运行js文件

在项目根目录中创建一个测试文件 test.js

const welcome = name => {
  return `Hello ${name}!`;
};
console.log(welcome("linxiaozhou"));

然后执行 npx babel-node --extensions \".js,.ts\" test.js

upload.png

编译并运行ts文件(含ts语法)

在项目根目录中创建一个测试文件 test.ts:

const welcome = (name: string): string => {
  return `Hello ${name}!`;
};
console.log(welcome.toString());
console.log(welcome("linxiaozhou"));

然后执行 npx babel-node -x ".ts" --inspect --presets @babel/preset-env test.ts

upload.png

如果出现报错,可在命令中加上 --inspect 或写入 package.json 文件并通过 yarn 命令运行。

使用 npx babel-node 命令 需要注意以下几点:

  • 不要 在生产环境中使用它,否则会导致内存占用过高
  • 先安装 @babel/node@babel/core再使用 npx babel-node 命令,否则npx会安装过期的 babel-node

配置你的Babel

Babel的工作原理

Babel 是一个编译器,通过 Babel 可以将源码编译成目标代码:


upload.png

如果把 Babel 看做一个黑盒,则黑盒内需要通过 解析(parsing) -> 转换(transforming) -> 打印(printing) (有些地方叫 代码生成Generate)三个步骤来实现源码的编译。

upload.png

而其中最为关键的步骤就是 转换(transforming )。如果里面什么也没做,那么输出的就是源码本身。而这个步骤是插件式的,里面堆满了(也可能一个都没有)插件,在我们触发了 Babel 的编译动作后, Babel 内核会使用这些插件对源码进行转换,从而实现编译的功能。

upload.png

这里有两个关键词需要理解:

  • 插件 Plugins
    即转换插件(Transform Plugins),每一个插件都可用于转换你的源码。如果你想看看有哪些插件可用,可以阅读官方文档。
    官方文档https://babeljs.io/docs/en/plugins#transform-plugins
  • **预置库 Presets **
    提供了按照一定规则组织插件的方式,支持用户使用官方提供、其他用户共享或者用户自己定义预置库,每一个预置库都可以批量声明使用哪些插件、每个插件的参数是什么。Babel提供了一些常用的预置库,可以参考官方文档。
    官方文档https://babeljs.io/docs/en/presets

可以说,每一个独立的插件(Plugins)都各自规定了转换源码的规则,而预置库(Presets)则用于组织多个插件,两者都有各自的应用场景。

配置方式

关于配置方式的说明,可以参考这篇官方文档:
https://babeljs.io/docs/en/config-files#configuration-file-types

针对 Babel 的配置,有以下几种方式:

  • 配置文件:通过配置文件来指定项目或文件夹(模块)的 Babel 配置
  • 命令行:通过 @babel/cli 命令行来执行指定的 Babel 配置
  • API: 通过 @babel/core 调用api的方式来使用指定的 Babel 配置

配置文件

以文件形式进行 Babel 配置则有两种方式:

  • 项目范围配置(Project-wide configuration): 前文提到的 Babel 版本≥ v7.8.0的 babel.config.json 或 Babel 版本小于 v7.8.0的babel.config.js 都属于项目层面的Babel配置
  • 目录配置(File-relative configuration): 可以通过遍历项目内各文件夹(模块)中的 .babelrc.json 文件和 package.json(含 babel 字段内容),来单独指定这个文件夹(模块)的编译要求

而配置文件又可以分为两类:json 和 js,其书写规则分别为:

{
  "presets": [...],
  "plugins": [...]
}

module.exports = function (api) {
  api.cache(true);

  const presets = [ ... ];
  const plugins = [ ... ];

  return {
    presets,
    plugins
  };
}

此外,package.json 的书写规则有一些不同,需要在 babel 字段下书写,例如:

{
  "name": "my-package",
  "version": "1.0.0",
  "babel": {
    "presets": [ ... ],
    "plugins": [ ... ],
  }
}

如果需要写一个拓展性好的配置文件,建议使用 js 文件来书写。这样可以通过指定不同的环境来增减插件及预置。下面官方例子中,通过全局环境的判断,为线上环境增加新的插件。

const presets = [ ... ];
const plugins = [ ... ];

if (process.env["ENV"] === "prod") {
  plugins.push(...);
}

module.exports = { presets, plugins };

命令行

在通过命令行来执行 Babel 命令时,可以通过 --plugins 来指定使用的插件、通过 --presets 来指定预置库

babel --plugins @babel/plugin-transform-arrow-functions script.js

API

下面给出了一个官方示例:

require("@babel/core").transform("code", {
  plugins: ["@babel/plugin-transform-arrow-functions"]
});

本文不对这部分做详细介绍,如需了解更多信息,可参考下面链接:
https://babeljs.io/docs/en/babel-core#transform

插件 Plugins

前文已经说明,插件是管理 Babel 转换规则的最小单元。Babel 提供了如下模块,每个模块都有一个或多个的插件可供使用:

  • ES3
  • ES5
  • ES2015
  • ES2016
  • ES2017
  • ES2018
  • Modules
  • Experimental
  • Minification
  • React
  • Other

基本格式和使用

插件的声明格式为:

{
  "plugins": ["pluginA", ["pluginB"], ["pluginC", {}]]
}

其中,插件 pluginC 传入了一个选项 {},具体的参数依插件而定。


下面给出了一个例子:

{
  "plugins": [
    [
      "transform-async-to-module-method",
      {
        "module": "bluebird",
        "method": "coroutine"
      }
    ]
  ]
}

这里为 Babel 编译器设置了一个名为 transform-async-to-module-method 的插件,同时传入了参数:

{
    "module": "bluebird",
    "method": "coroutine"
}

引入路径

如果一个插件在 npm 上,可以直接声明其名称:

{
  "plugins": ["babel-plugin-myPlugin"]
}

你也可以直接引入路径:

{
  "plugins": ["./node_modules/asdf/plugin"]
}

简写

如果一个插件的名称是 babel-plugin- 打头,则可以简写剩余的文本:

{
  "plugins": [
    "myPlugin",
    "babel-plugin-myPlugin" // 与上一行等价
  ]
}

或者,插件位于一个组织(scoped package)下:

{
  "plugins": [
    "@org/babel-plugin-name",
    "@org/name" // 在@org下,等价于babel-plugin-name
  ]
}

运行顺序

当有多个插件要运行的时候,将会按声明的顺序运行。例如:

{
  "plugins": ["plugin1", "plugin2", "plugin3"]
}

上面示例中,将会按这个顺序运行:plugin1 -> plugin2 -> plugin3


需要注意区分的是,预置库的运行顺序是 相反的。例如:

{
  "presets": ["preset1", "preset2", "preset3"]
}

这个例子中,将会按照这个顺序运行:preset1 -> preset2 -> preset3

创建一个插件

如果想要创建一个自己的插件,可以阅读官方文档,这里不做说明。
官方文档:https://github.com/jamiebuilds/babel-handbook

预置Presets

官方提供了一些预置库供我们使用:

预置Presets其实是在管理各种插件 Plugins,通过组合不同的插件 Plugins 来管理编译代码的方式。如 @babel/preset-typescript 就是包含了 @babel/plugin-transform-typescript 插件

预置库的基本声明格式为:

{
  "presets": [
    "presetA",
    ["presetA"],
    ["presetA", {}],
  ]
}

其中,{} 为该预置库的选项(OPTIONS)。

@babel/preset-env

安装

需要注意的是,Babel 不应该出现在生产环境中,因此需要加上 --dev(npm 安装则加上--save-dev):

yarn add @babel/preset-env --dev

书写格式

基本格式为:

{
  presets: [
    [
      "@babel/preset-env",
      <options>
    ]
  ]
}

其中,<options> 为可选项,可以不填(但不推荐)!

下面给出了一个简单的示例:

{
  presets: [
    // 预置库1:preset-env
    [
      "@babel/preset-env",
      {
        targets: {
          chrome: "58",
          ie: "11",
        },
      },
    ],

    // 其他预置库
    // ...
  ],
}

上面示例中,presets 字段的值为数组,每个元素是一个数组;此外,每个元素也可以是一个字符串(如 "@babel/preset-typescript" ),例如:

{
  presets: [
    // 预置库1:preset-env
    [
      "@babel/preset-env",
      {
        targets: {
          chrome: "58",
          ie: "11",
        },
      },
    ],

    // 预置库2:preset-typescript
    "@babel/preset-typescript"`,

    // 其他预置库
    // ...
  ],
}

下面几节内容会针对一些常用的 options 作进一步说明。如需查看完整的介绍,可以打开以下链接阅读官方文档:
https://babeljs.io/docs/en/babel-preset-env#options

options.targets

targets 格式为 string | Array<string> | { [string]: string } ,默认值为 {}


参数targets.<string>
类型string
说明:指定你的项目要支持的目标环境,可以支持的环境包括但不限于:chrome, opera, edge, firefox, safari, ie, ios, android, node, electron
举例

{
   targets: {
      chrome: 58,
      ie: 1,
    }
}

参数targets.esmodules
类型boolean
说明:如果目标浏览器支持 ES Modules,则置为 true。如果声明了该字段,则 targets.browsers 字段会被忽略。
举例

{
   targets: {
      esmodules: true
    }
}

参数targets.node
类型string | "current" | true
说明:如果要编译为当前版本 node.js 的版本,则置为 "current"true
举例

{
   targets: {
      node: "current"
    }
}

参数targets.safari
类型string | "tp"
说明:如果要编译为 Safiri 的预览版(technology preview),则置为 "tp"
举例

{
   targets: {
      safari: "tp"
    }
}

参数targets.browsers
类型string | Array<string>
说明:可以用单个字符串,也可以用字符串数组。其完整说明可参照这个链接:https://github.com/browserslist/browserslist#full-list
举例

{
   targets: {
      browsers: "> 0.25%, not dead"
    }
}

或者写作数组:

{
    targets:  {
      browsers: [
          "> 0.25%",
          "not dead"
        ]
    }
}

需要注意的是,如果 target.<string> 有与之冲突的,会以 target.<string> 设定的参数为准。此外,targets.browsers 参数会在后续版本中移除,可以直接写入 targets 中,例如:

{
  "targets": "> 0.25%, not dead"
}

options.loose

宽松模式开关,类型为boolean,默认为 false。当 loose: true 时,将启用宽松模式。

下面给出了一个示例:

// INPUT
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    toString() {
        return `(${this.x}, ${this.y})`;
    }
}

在非宽松模式下,需要严格按照ES6规范来定义:

// OUTPUT: loose===false
"use strict";

var _createClass = (function () {
    function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
            if ("value" in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor); // (A)
        }
    }
    return function (Constructor, protoProps, staticProps) {
        if (protoProps) defineProperties(Constructor.prototype, protoProps);
        if (staticProps) defineProperties(Constructor, staticProps);
        return Constructor;
    };
})();

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

var Point = (function () {
    function Point(x, y) {
        _classCallCheck(this, Point);

        this.x = x;
        this.y = y;
    }

    _createClass(Point, [{
        key: "toString",
        value: function toString() {
            return "(" + this.x + ", " + this.y + ")";
        }
    }]);

    return Point;
})();

在宽松模式下,编译出的代码更像是我们按照 ES5 规范手写的代码:

// OUTPUT: loose === true
"use strict";

function _classCallCheck(instance, Constructor) { ··· }

var Point = (function () {
    function Point(x, y) {
        _classCallCheck(this, Point);

        this.x = x;
        this.y = y;
    }

    Point.prototype.toString = function toString() { // (A)
        return "(" + this.x + ", " + this.y + ")";
    };

    return Point;
})();

更多官方说明,请参看这篇文档
https://2ality.com/2015/12/babel6-loose-mode.html

options.modules

可选参数有 "amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默认为 auto

其中 cjscommonjs 的别名

编写一个自己的预置库

如果我们有多个项目需要共享一个定制的预置库,我们可以自己编写一个预置库并发布到 npm。预置库暴露出去的 js 文件基本格式如下:

// index.js

module.exports = function() {
  return {
    // 包含其他预置库
    presets: [
      "preset1",
      "preset2",
      "preset3",
    ],

    // 指定需要引入的插件
    plugins: [
      "pluginA",
      "pluginB",
      "pluginC",
    ]
  };
}

举个例子,我们需要引入默认的预置库、同时引入两个插件:

// index.js

module.exports = () => ({
  presets: [
    require("@babel/preset-env"),
  ],
  plugins: [
    [require("@babel/plugin-proposal-class-properties"), { loose: true }],
    require("@babel/plugin-proposal-object-rest-spread"),
  ],
});

引入规则

前面介绍了官方提供的预置库,同时给出了自己编写预置库的方式。那么,如何在项目中引用它们呢?

引入路径

不论是官方的还是自定义的预置库,如果是已经安装到 node_modules 的,都按照这个方式引入:

{
  "presets": ["babel-preset-myPreset"]
}

例如:

{
  "presets": [
      "@babel/preset-react",
      "babel-preset-myPreset",
    ]
}

如果一个预置库没有安装到 node_modules 中(即为项目的文件),则通过路径来引入:

{
  "presets": ["./myProject/myPreset"]
}

例如:

{
  "presets": [
      "@babel/preset-react",
      "./myProject/babel-preset-myPreset",
    ]
}

简写

上一节的第一个例子中,如果一个预置库的包名称为 babel-preset- 开头,可以简写后面的名称。例如一个预置库的包名为 babel-preset-myPreset,则可以简写做 myPreset

{
  "presets": [
    "myPreset",
    "babel-preset-myPreset" // 与上一行等价
  ]
}

运行顺序

如果有一个预置库如下:

{
  "presets": [
    "a",
    "b",
    "c"
  ]
}

则运行的顺序为:c -> b -> a,它和插件的运行顺序 刚好相反

插件与预置库的覆盖处理

如果使用了多个预置库,应该怎么覆盖相关配置呢?Babel 会:

  • 使用 Object.assign 按顺序覆盖 pluginspresets 以外的字段
  • 使用 Array.concat 分别合并pluginspresets 字段。参考官方例子:
const config = {
  plugins: [["plugin-1a", { loose: true }], "plugin-1b"],
  presets: ["preset-1a"],
  sourceType: "script"
}

const newConfigItem = {
  plugins: [["plugin-1a", { loose: false }], "plugin-2b"],
  presets: ["preset-1a", "preset-2a"],
  sourceType: "module"
}

BabelConfigMerge(config, newConfigItem);
// returns
({
  plugins: [
    ["plugin-1a", { loose: true }],
    "plugin-1b",
    ["plugin-1a", { loose: false }],
    "plugin-2b"
  ], // new plugins are pushed
  presets: [
    "preset-1a",
    "preset-1a",
    "preset-2a"
  ], // new presets are pushed
  sourceType: "module" // sourceType: "script" is overwritten
})

注:示例来自官网、有修改。官网的示例中,合并后的 presets 应该写错了

upload.png

https://babeljs.io/docs/en/configuration#how-babel-merges-config-items

基本命令

@babel/cli 为开发者提供了 Babel 命令行,我们可以在命令行中直接使用 Babel。本章将介绍几个常用的命令。

官方文档:https://babeljs.io/docs/en/babel-cli

安装

前面已经提到,在安装 Babel 的时候,一般都已经安装。如果没有,可单独执行:

yarn add --dev @babel/core @babel/cli

需要注意的是,如果你的项目中没有 package.json 文件,务必在安装之前创建一个,否则会影响 npx 的使用。(如果你不想用 npx 命令,则可以使用 ./node_modules/.bin/babel 替代 npx babel

编译单个文件

执行下面命令,将在控制台打印出编译后的结果:

npx babel src/test.ts

./node_modules/.bin/babel src/test.ts

如果需要把编译后的内容另存为一个文件,则可以加上 --out-file 或者 -o 参数并制定新文件名:

npx babel src/test.ts --out-file dist/test.js

Soure Map

Babel 提供了 Source Map,加上参数 --source-maps

npx babel src/test.ts --out-file dist/test.js --source-maps

编译文件夹

如果需要把整个文件夹的文件(及子文件夹里的文件)编译并指定输出目录,可以使用 --out-dir 参数。下面命令把 src 目录编译并输出到 dist 目录中:

npx babel src --out-dir dist

忽略一些文件

如果要编译的目录中有不需要编译的文件,可以设置参数为 --ignore,如:

npx babel src --out-dir dist --ignore "src/**/*.spec.js","src/**/*.test.js"

上面命令中忽略了以 spec.jstest.js 结尾的全部文件。

指定插件

通过 --plugins 参数指定要使用的插件,多个预置库用逗号 , 隔开:

npx babel src/test.ts --out-file dist/test.js --plugins=@babel/proposal-class-properties,@babel/transform-modules-amd

指定预置库

通过 --presets 参数指定要使用的预置库,多个预置库用逗号 , 隔开:

npx babel src/test.ts --out-file dist/test.js --presets=@babel/preset-env,@babel/flow

最简项目配置参考

项目结构及源码地址

项目结构图:

.
├── dist
├── src
│   └── test.ts
├── README.md
├── babel.config.js
└── package.json

项目源码地址:
https://github.com/KKDestiny/babel-setup.git

初始化项目

新建一个项目:

yarn init

填写相关信息:


upload.png

安装以下包:

yarn add --dev @babel/core @babel/cli @babel/preset-env
upload.png

如果你的项目是 typescript,则还需要安装:

yarn add --dev @babel/preset-typescript
upload.png

注意看上面的打印信息,安装这个预置库的同时也一起安装了typescript的插件 @babel/plugin-transform-typescript@7.12.1


添加 ignore 文件:

dist/
node_modules/

yarn-error.log
yarn.lock

创建配置文件

在项目根目录新建 babel.config.js 文件:

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          node: "current",
        },
      },
    ],
    "@babel/preset-typescript",
  ],
  plugins: [
  ],
};

这里我们指定了使用 @babel/preset-env@babel/preset-typescript 这两个预置库,但没有额外的插件。

注意,如果没有引入 typescript 的预置库或插件,则在编译的时候会报错:


upload.png

除了上述的方式引入 typescript,还可以通过插件的方式(记得先安装插件 yarn add --dev @babel/plugin-transform-typescript):

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          node: "current",
        },
      },
    ],
  ],
  plugins: [
  "@babel/plugin-transform-typescript"
  ],
};

添加编译命令

设置编译命令,在 package.json 文件中增加一个 scripts 的命令:

{
  "scripts": {
    "compile": "npx babel src -x \".js,.ts\" --out-dir dist"
  }
}

这个命令是触发 Babel 编译 src 目录中的全部 jsts 文件,然后拷贝到 dist 目录中。

测试

创建 src 目录,在里面创建一个 test.ts 文件:

const welcome = (name: string): string => {
  return `Hello ${name}`;
}
console.log(welcome.toString())
console.log(welcome("linxiaozhou"))

在项目根目录运行 yarn compile

upload.png

如果运行成功,会自动创建 dist 目录(如果没有),同时把 src 目录下的全部文件编译后依次拷贝到对应位置。

编译前删除旧文件

在实际项目中,我们可能进行多次编译。如果删除了其中一些文件,则再次编译是不会自动删除这些文件的。因此建议在执行编译命令之前,先删除已经编译出来的文件(夹),以确保生产环境的文件列表是干净的。

这里推荐一个包 rimraf,它相当于执行了Linux命令 rm -rf(强制删除文件夹及其子文件夹和包含的文件):

yarn add  --dev rimraf

然后将 package.json 文件的 compile 命令改为:

{
  "scripts": {
    "compile": "npx rimraf dist && npx babel src -x \".js,.ts\" --out-dir dist"
  }
}

最终的 package.json 文件内容为:

{
  "name": "babel-setup",
  "version": "1.0.0",
  "main": "index.js",
  "author": "linxiaozhou.com",
  "license": "MIT",
  "scripts": {
    "compile": "npx rimraf dist && npx babel src -x \".js,.ts\" --out-dir dist"
  },
  "devDependencies": {
    "@babel/cli": "^7.12.10",
    "@babel/core": "^7.12.10",
    "@babel/preset-env": "^7.12.11",
    "@babel/preset-typescript": "^7.12.7",
    "rimraf": "^3.0.2"
  }
}

更进一步

编写插件

抽象语法树(Abstract Syntax Tree,AST 树)是什么?如何通过 AST 来编写插件?这个会在后面做进一步研究。
https://blog.csdn.net/weixin_42436686/article/details/112515290?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-2&spm=1001.2101.3001.4242

在学习 Prettier 的时候看到一个好工具,可以用来研究AST:

屏幕快照 2021-01-23 01.18.42.png

放上链接:https://prettier.io/playground

如何组织插件

可以简单地把插件分为几类:

  • proposal:处于提案状态,与ES版本有关,如果提案在选定的版本中通过,则不需要再使用
  • transform:比较稳定,一般不会有变动
  • 其他:按需添加

如何更好地去组织这些插件、形成自己的一个预置库?这是后面需要进一步了解的内容。

运行规则

有下面这样的例子。尽管我们一般不会写成这样,但如果我们调用了多个预置库,那么不同预置库中就有可能会指定同一个插件、设置不同参数。

babel-presets/babel-preset-a.js

module.exports = function() {
  return {
    plugins: [
      [
        "@babel/plugin-transform-arrow-functions",
        {
          spec: true
        }
      ],
    ]
  };
}

babel-presets/babel-preset-b.js

module.exports = function() {
  return {
    plugins: [
      [
        "@babel/plugin-transform-arrow-functions",
        {
          spec: false
        }
      ],
    ]
  };
}

可以看到,babel-preset-ababel-preset-b 中都定义了使用插件 @babel/plugin-transform-arrow-functions,但传入的参数却不同:

  • 插件babel-preset-a
    传入参数为 spec: true
  • 插件babel-preset-b
    传入参数为 spec: false

如果a在前、b在后:

module.exports = {
  presets: [
    [
      "@babel/preset-env",
    ],
    "./babel-presets/babel-preset-a",
    "./babel-presets/babel-preset-b",
  ],
  plugins: [
  ],
};

如果b在前、a在后:

module.exports = {
  presets: [
    [
      "@babel/preset-env",
    ],
    "./babel-presets/babel-preset-b",
    "./babel-presets/babel-preset-a",
  ],
  plugins: [
  ],
};

以上两种情况下,分别会有什么结果?这个问题会在后续做进一步研究。

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容