Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Babel 基础入门 #5

Open
Tracked by #6
swiftwind0405 opened this issue Feb 9, 2020 · 0 comments
Open
Tracked by #6

Babel 基础入门 #5

swiftwind0405 opened this issue Feb 9, 2020 · 0 comments
Labels

Comments

@swiftwind0405
Copy link
Owner

swiftwind0405 commented Feb 9, 2020

什么是 Babel

Babel 是一个 JavaScript 编译器,用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前版本和旧版本的浏览器或其他环境中。简单来说 Babel 的工作就是:

  • 语法转换
  • 通过 Polyfill 的方式在目标环境中添加缺失的特性(@babel/polyfill 模块)
  • JS 源码转换(codemods)

Babel 三个编译阶段

Babel 的编译过程和大多数其他语言的编译器相似,可以分为三个阶段:

  • 解析(Parsing):将代码字符串解析成抽象语法树。
  • 转换(Transformation):对抽象语法树进行转换操作。
  • 生成(Code Generation): 根据变换后的抽象语法树再生成代码字符串。

image

Babel 常用 API

@babel/core

Babel 的编译器,核心 API 都在这里面,比如常见的 transformparse

@babel/cli

cli 是命令行工具, 安装了 @babel/cli 就能够在命令行中使用 babel 命令来编译文件。当然我们一般不会用到,打包工具已经帮我们做好了。

@babel/node

直接在 node 环境中,运行 ES6 的代码

babylon

Babel 的解析器

babel-traverse

用于对 AST 的遍历,维护了整棵树的状态,并且负责替换、移除和添加节点。

babel-types

用于 AST 节点的 Lodash 式工具库, 它包含了构造、验证以及变换 AST 节点的方法,对编写处理 AST 逻辑非常有用。

babel-generator

Babel 的代码生成器,它读取 AST 并将其转换为代码和源码映射(sourcemaps)

插件 Plugins

Babel 构建在插件之上,使用现有的或者自己编写的插件可以组成一个转换通道,Babel 的插件分为两种: 语法插件和转换插件。

语法插件

这些插件只允许 Babel 解析(parse) 特定类型的语法(不是转换),可以在 AST 转换时使用,以支持解析新语法,例如:

import * as babel from "@babel/core";
const code = babel.transformFromAstSync(ast, {    
	//支持可选链    
	plugins["@babel/plugin-proposal-optional-chaining"],    
	babelrcfalse
}).code;

转换插件

转换插件会启用相应的语法插件(因此不需要同时指定这两种插件),这点很容易理解,如果不启用相应的语法插件,意味着无法解析,连解析都不能解析,又何谈转换呢?

@babel/plugin-transform-runtime

@babel/plugin-transform-runtime 是一个可以重复使用 Babel 注入的帮助程序,以节省代码大小的插件

预设 Preset

我们假设 ES6 有 10 个新特性,那为了把 ES6 转换成 ES5 就需要安装 10 个转换插件,配置文件很长不说,npm install 的时间也会很长。为了解决这个问题,babel 还提供了一组插件的集合,就叫 presets。

通过使用或创建一个 preset 即可轻松使用一组插件。

presets 是这么分类的

  1. es201X, latest,这些是已经纳入到标准规范的语法。例如 es2015 包含 const let 等新语法,es2017 包含 syntax-trailing-function-commas。latest 是一个每年更新的 preset,目的是包含所有 es201x。但也是因为更加灵活的 env 的出现,已经废弃。
  2. stage-x,这里面包含的都是当年最新规范的草案,每年更新。 这里面还细分为 Stage 0(想法),Stage 1(提案),Stage 2(初稿),Stage 3(候选),Stage 4(完成)。低一级的 stage 会包含所有高级 stage 的内容,例如 stage-1 会包含 stage-2, stage-3 的所有内容。
  3. env, react等。

官方 Preset

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

@babel/preset-env

@babel/preset-env 主要作用是对我们所使用的并且目标浏览器中缺失的功能进行代码转换和加载 polyfill,在不进行任何配置的情况下,@babel/preset-env 所包含的插件将支持所有最新的 JS 特性(ES2015,ES2016 等,不包含 stage 阶段),将其转换成 ES5 代码。例如,如果你的代码中使用了可选链(目前,仍在 stage 阶段),那么只配置 @babel/preset-env,转换时会抛出错误,需要另外安装相应的插件。

需要说明的是,@babel/preset-env 会根据你配置的目标环境,生成插件列表来编译。对于基于浏览器或 Electron 的项目,官方推荐使用 .browserslistrc 文件来指定目标环境。默认情况下,如果你没有在 Babel 配置文件中(如 .babelrc)设置 targetsignoreBrowserslistConfig@babel/preset-env 会使用 browserslist 配置源。

Polyfill

babel 对一些新的 API 是无法转换,比如 GeneratorSetProxyPromise 等全局对象,以及新增的一些方法:includesArray.form 等。所以这个时候就需要一些工具来为浏览器做这个兼容。

官网的定义:babel-polyfill 是为了模拟一个完整的 ES6+ 环境,旨在用于应用程序而不是库/工具。

babel-polyfill 主要有两个缺点:

  • 使用 babel-polyfill 会导致打出来的包非常大,很多其实没有用到,对资源来说是一种浪费。

  • babel-polyfill 可能会污染全局变量,给很多类的原型链上都作了修改,这就有不可控的因素存在。

因为上面两个问题,所以在 Babel7 中增加了 babel-preset-env,我们设置 "useBuiltIns":"usage" 这个参数值就可以实现按需加载 babel-polyfill

插件/预设补充知识

顺序

如果两个转换插件都将处理“程序(Program)”的某个代码片段,则将根据转换插件或 preset 的排列顺序依次执行。

  • 插件在 Presets 前运行。
  • 插件顺序从前往后排列。
  • Preset 顺序是颠倒的(从后往前)。

例如:

{
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-syntax-dynamic-import"
  ]
}

先执行 @babel/plugin-proposal-class-properties,后执行 @babel/plugin-syntax-dynamic-import

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

preset 的执行顺序是颠倒的,先执行 @babel/preset-react, 后执行 @babel/preset-env

插件参数

插件和 preset 都可以接受参数,参数由插件名和参数对象组成一个数组。preset 设置参数也是这种格式。

如:

{
  "plugins": [["@babel/plugin-proposal-class-properties", { "loose": true }]]
}

插件的短名称

如果插件名称为 @babel/plugin-XXX,可以使用短名称 @babel/XXX :

{
  "plugins": [
    "@babel/transform-arrow-functions" // 同 "@babel/plugin-transform-arrow-functions"
  ]
}

如果插件名称为 babel-plugin-XXX,可以使用短名称 XXX,该规则同样适用于带有 scope 的插件:

{
  "plugins": [
    "newPlugin", // 同"babel-plugin-newPlugin"
    "@scp/myPlugin" // 同 "@scp/babel-plugin-myPlugin"
  ]
}

创建 Preset

可以简单的返回一个插件数组

module.exports = function () {
  return {
    plugins: ["A", "B", "C"],
  };
};

preset 中也可以包含其他的 preset,以及带有参数的插件。

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

最佳实践

    1. 如果实现自己的应用,推荐使用 presets-env,原因是 presets-env 可以根据配置的 browerlist 来进行编译,特别是目标浏览器比较高的时候,生成的包会非常小,而且自己的应用也不存在谁污染谁的问题,就算要兼容比较旧的浏览器,最差的情况和 runtime 方式差不多。
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false,
        "useBuiltIns": "usage",
        "corejs": {
          "version": 3,
          "proposals": true
        }
      }
    ]
  ]
}
    1. 如果是做公共的 npm 包,建议使用 runtime 的方式,因为这样可以避免全局污染,你永远无法想象包的调用方会在代码里写了什么,还是谨慎为上。
{
    "presets": [
        [
            "@babel/env",
            {
                "modules": false
            }
        ]
    ],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": {
                    "version": 3,
                    "proposals": true
                },
                "useESModules": true
            }
        ]
    ]
}

注意,这里需要配置一下 corejs 的版本号,不配置编译的时候会报警告。
useBuiltIns 的机构参数:

  • false:此时不对Polyfill 做操作,如果引入 @babel/polyfill 则不会按需加载,会将所有代码引入
  • usage:会根据配置的浏览器兼容性,以及你代码中使用到的 API 来进行 Polyfill ,实现按需加载
  • entry:会根据配置的浏览器兼容性,以及你代码中使用到的 API 来进行 Polyfill ,实现按需加载,不过需要在入口文件中手动加上 import ' @babel/polyfill'

参考资料

@swiftwind0405 swiftwind0405 changed the title 框架的思考 【Day22】Babel Mar 16, 2020
@swiftwind0405 swiftwind0405 changed the title 【Day22】Babel 【Day34】Babel Apr 1, 2020
@swiftwind0405 swiftwind0405 changed the title 【Day34】Babel 【JavaScript】【Webpack】Babel Apr 29, 2020
@swiftwind0405 swiftwind0405 changed the title 【JavaScript】【Webpack】Babel Babel 基础入门 Oct 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant