presets
@babel/preset-env
-
- 是一大堆插件的集合,包含了当前浏览器环境下,所有语言特性的插件,可以根据 browserslist 的结果,选择合适的插件将新语言特性转译成旧浏览器可以支持的表达方式。你可以无需管理目标环境需要的语法转换或浏览器polyfill,就可以使用最新的 JavaScript。这将让你的生活更简单,也会让 JavaScript 打包文件更小
- Babel7版本开始,babel-preset-stage-0、babel-preset-stage-1、babel-preset-stage-2、babel-preset-stage-3,预设都已经不推荐使用了,因为对开发造成了一些困扰,也不再更新。也不再推出babel-preset-es2017以后的年代preset了。
- @babel/preset-env包含了babel-preset-latest的功能,并对其进行增强,现在@babel/preset-env完全可以替代babel-preset-latest。
- preset-env 的原理是根据 targets 的配置查询内部的 @babe/compat-data 的数据库,过滤出目标环境不支持的语法和 api,引入对应的转换插件
- preset-env 默认会支持所有 es 标准的特性,如果没进入标准的,不再封装成 preset,需要手动指定 plugin-proposal-xxx。
{
"presets": [["@babel/preset-env", {
"targets": "> 0.25%, not dead",
"useBuiltIns": "usage",// or "entry" or "false"
"corejs": 3
}]]
}
corejs 就是 babel 7 所用的 polyfill,需要指定下版本,corejs 3 才支持实例方法(比如 Array.prototype.fill )的 polyfill。
useBuiltIns 就是使用 polyfill (corejs)的方式,是在入口处全部引入(entry),还是每个文件引入用到的(usage),或者不引入(false)。
配置了这两个 option 就可以自动引入 polyfill 了。
@babel/preset-react
并且有相应的开发配置项:
添加经典的运行时:
当启用 development 配置时,自动运行时(自 v7.9.0 起)会自动添加这些插件的功能。如果你已经启用了自动运行时,再添加 @babel/plugin-transform-react-jsx-self 或 @babel/plugin-transform-react-jsx-source 会发生错误。
@babel/preset-typescript
-
- 用了 TypeScript 这一 JavaScript 超集,则建议您使用此预设(preset)。它包含以下插件:@babel/plugin-transform-typescript。它的作用是转换 TypeScript 代码。
@babel/preset-flow
-
- 如果您使用了 Flow,则建议您使用此预设(preset)。Flow 是一个针对 JavaScript 代码的静态类型检查器。此预设(preset)包含以下插件:@babel/plugin-transform-flow-strip-types
- flow是facebook开发的一个静态语言类型检查器
Integration Packages
@babel/cli
-
- 安装好后可以使用 babel的命令,依赖 babel的配置文件 .babelrc.js 和 @babel/core
例如: npx babel a.js -o b.js
,将转译后的代码写入到一个文件npx babel src --out-dir lib
,编译整个 src 目录下的文件并输出到 lib 目录
@babel/polyfill
-
- 从 Babel 7.4.0 开始,这个包已被弃用,取而代之的是直接包含core-js/stable(填充 ECMAScript 特性)
@babel/plugin-transform-runtime
-
- babel-plugin-transform-runtime 就是可以在我们使用新 API 时 自动 import babel-runtime 里面的 polyfill,具体插件做了以下三件事情:
-
-
- 当我们使用 async/await 时,自动引入 babel-runtime/regenerator
- 当我们使用 ES6 的静态事件或内置对象时,自动引入 babel-runtime/core-js
- 移除内联 babel helpers 并替换使用 babel-runtime/helpers 来替换
-
-
- babel-plugin-transform-runtime 优点:
-
-
- 不会污染全局变量
- 多次使用只会打包一次
- 依赖统一按需引入,无重复引入,无多余引入
- 避免 babel 编译的工具函数在每个模块里重复出现,减小库和工具包的体积
-
-
- @babel/preset-env配置 corejs后,也可以注入corejs和polyfill,但是会重复注入,并且是全局引入。这个包解解决了 corejs 的重复注入和全局引入 polyfill 的两个问题。
@babel/register
-
- @babel/register只有一个功能,就是重写node的require方法。@babel/register在底层改写了node的require方法,在代码里引入@babel/register模块后,所有通过require引入并且以.es6, .es, .jsx 和 .js为后缀名的模块都会经过babel的转译。
- @babel/register 使用 Node 的 require() 钩子系统(hook system) 在加载文件时即时编译文件
- @babel/register 是一个动态的编译器。上面讲到的 babel 使用,都是通过编译源码文件生成一个新的文件(通过命令 npx babel test.js -o bundle.js), 最终我们使用的代码是 bundle.js。
- @babel/register 提供了一种动态编译的功能, 当在代码顶部引用 @babel/register 之后, 使用 node test.js 可以直接运行源码文件, 在运行的时候,@babel/register 会动态编译代码。
require('@babel/register')
require("./my-plugin");
class Persion {
constructor () {
console.log('new a persion')
}
}
let p = new Persion()
@babel/standalone
-
- 用于在Node上运行的构建系统(例如Webpack.Rollup、Rarcel)来提前转换JS
- 调试React源码;
- Babel 官网也用到了这个包,在线实时javascript编辑器网站(如 JSFiddle, JS Bin, REPL on the Babel );
- 直接嵌入到应用中,例如:V8 javascript 引擎;
- 其它开发语言( ReactJS.NET, ruby-babel-transpiler 等);
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const getMessage = () => "Hello World";
document.getElementById('output').innerHTML = getMessage();
</script>
其工作原理藏在 babel-standalone 的核心源码中,最后的编译行为由:
import {
transformFromAst as babelTransformFromAst,
transform as babelTransform,
buildExternalHelpers as babelBuildExternalHelpers,
} from "@babel/core";
在前端发展方向之一——Web IDE 和智能化方向上,相信类似的设计和技术将会有更多的施展空间,@babel/standalone 对于我们的现代化前端发展思路,应该有启发
Tool Packages
@babel/parser
const { parse } = require("@babel/parser");
const ast = parse(`a b`, { errorRecovery: true, plugins: ['jsx'] });
console.log(ast.errors[0].code); // BABEL_PARSER_SYNTAX_ERROR
console.log(ast.errors[0].reasonCode); // MissingSemicolon
@babel/traverse
-
- 有了 AST,我们还需要对 AST 完成修改,才能产出编译后的代码。这就需要对 AST 进行遍历,此时 @babel/traverse 就派上用场了,使用方式如下
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
const code = `function square(n) {
return n * n;
}`;
const ast = parser.parse(code);
traverse(ast, {
enter(path) {
if (path.isIdentifier({ name: "n" })) {
path.node.name = "x";
}
},
});
@babel/types
-
- 遍历的同时,如何对 AST 上指定内容进行修改呢?这就又要引出另外一个“家族成员”,@babel/types 包提供了对具体的 AST 节点的修改能力。
- 判断节点类型、创建各种类型的节点,从而实现对节点增删改查
const id = types.identifier('abc')
const str = types.stringLiteral('Hello World')
生成一个react组件
log(
types.program([
types.importDeclaration(
[types.importDefaultSpecifier(types.identifier('React'))],
types.stringLiteral('react')
),
types.exportDefaultDeclaration(
types.arrowFunctionExpression(
[types.identifier('props')],
types.jsxElement(
types.jsxOpeningElement(types.jsxIdentifier('Component'), [
types.jsxAttribute(
types.jsxIdentifier('onClick'),
types.jSXExpressionContainer(
types.identifier('handleClick')
)
)
]),
types.jsxClosingElement(types.jsxIdentifier('Component')),
[
types.jsxElement(
types.jsxOpeningElement(types.jsxIdentifier('Image'), [
types.jsxAttribute(
types.jsxIdentifier('src'),
types.stringLiteral('https://image1.suning.cn/uimg/cms/img/159642507148437980.png')
)
]),
types.jsxClosingElement(types.jsxIdentifier('Image')),
[],
true
)
],
false
)
)
)
])
)
import React from "react";
export default (props =>
<Component onClick={handleClick}>
<Image src="https://image1.suning.cn/uimg/cms/img/159642507148437980.png"></Image>
</Component>
);
@babel/generator
-
- 得到了编译后的 AST 之后,最后一步:使用 @babel/generator 对新的 AST 进行聚合并生成 JavaScript 代码:
import { parse } from "@babel/parser";
import generate from "@babel/generator";
const code = "class Example {}";
const ast = parse(code);
const output = generate(
ast,
{
/* options */
},
code
);
source code ===> babel/parser ===> AST ===> babel/traverse、babel/types ===> AST ===> babel/generator ===> output code
@babel/code-frame
-
- 用以生成终端错误提示
@babel/runtime
-
- @babel/plugin-transform-runtime 需要和 @babel/runtime 配合使用;
- @babel/runtime含有 Babel 编译所需的一些运行时 helpers 函数,供业务代码引入模块化的 Babel helpers 函数,可以对代码瘦身
- 里面放运行时加载的模块,包括三部分:regenerator、corejs、helper。
-
-
- corejs 这就是新的 api 的 polyfill,分为 2 和 3 两个版本,3 才实现了实例方法的polyfill
- regenerator 是 facebook 实现的 aync 的 runtime 库,babel 使用 regenerator-runtime来支持实现 async await 的支持
- helper 是 babel 做语法转换时用到的函数,比如 _typeof、_extends 等
-
class Circle {}
// 变成
function _classCallCheck(instance, Constructor) {
//...
}
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
// 这意味着每个包含类的文件每次都会_classCallCheck重复该函数。
// @使用@babel/plugin-transform-runtime,它将把对函数的引用替换为@babel/runtime版本。
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
@babel/template
-
- 封装了基于 AST 的模板能力,可以将字符串代码转换为 AST。比如在生成一些辅助代码(helper)时会用到这个库。
@babel/core
-
- @babel/core 被多个 Babel 包应用,而 @babel/core 的能力由更底层的 @babel/parser、@babel/code-frame、@babel/generator、@babel/traverse、@babel/types等包提供。这些“家族成员”提供了更基础的 AST 处理能力。
Help Packages
helper 是用于插件之间复用代码的方式,也就是给插件开发用的库。
@babel/helper-compilation-targets
这个库提供了 3 个 api:
- 根据 query 查询目标环境版本: getTargets
- 过滤目标环境: filterItems
- 判断某个插件是否需要:isRequired
先通过 query 确定目标环境,然后对目标环境做过滤,之后判断某个插件是否需要的 3个阶段。
@babel/helper-module-imports
为组件库实现单组件按需加载并且自动引入其样式
按需引用的包 babel-plugin-import 就是基于这个实现的
为了解决在打包过程中把项目中引用到的外部组件或功能库全量打包,从而导致编译结束后包容量过大的问题
比如把 import { Button } from 'antd' 转换为 import { Button } from 'antd/lib/button';
// .babelrc
"plugins": [ ["import", { "libraryName": "antd", "libraryDirectory": "lib"}, "antd"],
["import", { "libraryName": "antd-mobile", "libraryDirectory": "lib"}, "antd-mobile"]
]
Babel 分层理念
我们通过 Babel 对代码的编译过程,可以从微观上缩小为前端基建的一个环节,这个环节融入整个工程中,也需要和其他环节相互配合。而 babel-loader 就是 Babel 结合 Webpack,融入整个基建环节的例子。
在 Webpack 编译生命周期中,babel-loader 作为一个 Webpack loader,承担着文件编译职责。我们暂且将 babel-loader 放到 Babel 家族中,先来看看下面这张“全家福”。
如上图所示,Babel 生态基本按照:辅助层 → 基础层 → 胶水层 → 应用层,四级结构完成。其中部分环节角色的界定有些模糊,比如 @babel/highlight 也可以作为应用层工具出现。
基础层提供了基础的编译能力,完成分词、解析 AST、生成产出代码的工作。基础层中,我们将一些抽象能力下沉为辅助层,这些抽象能力被基础层使用。同时,在基础层之上,我们构建了如 @babel/preset-env 等预设/插件能力,这些类似“胶水”的包,完成了代码编译降级所需补丁的构建、运行时逻辑的模块化抽象等工作。在最上层,Babel 生态提供了终端命令行、Webpack loader、浏览器端编译等应用级别的能力。