写在前面
先理解下 babel 存在的意义,babel 的出现是为了解决不同的运行环境对于 es 语言的差异性,主要包括 2 类,语法的转译和特性的支持。
本文主要介绍 babel 7,此后的模块包都是以 @bable 开头的。
主要思路
举个栗子:你是个开饭店的,有一个客人来你店里点了一份牛排外卖,然后你做出来送到他的住所。那么完成这件事情需要几个步骤:
- 客人下单
- 你将生牛肉摆上
- 准备点缀物品
- 准备调料
- 操刀并根据客人要求加入点缀物品和调料
- 然后一顿操作
- 最后将 10 分熟的牛肉送到客人面前
大致是这样,先记住这个栗子,我们继续往下看。
主要模块包
- @babel/cli
- @babel/preset-env
- @babel/polyfill
- @babel/core
-
@babel/plugin-transform-xxx(其他插件)
- @babel/runtime
- @babel/plugin-transform-runtime
- @babel/plugin-transform-arrow-functions
- 其他
@babel/cli
@babel/cli 是 babel 提供的内建的命令行工具,主要是提供 babel 这个命令来对 js 文件进行编译。
这里要注意它与另一个命令行工具 @babel/node 的区别,首先要知道他们二者都是命令行工具,但是官方文档明确对他们定义了他们各自的使用范围:
@babel/cli 是一个适合安装在本地项目里,提供 babel 命令,@babel/node 是一个全局安装提供 babel-node 命令。
将此包安装至开发依赖中可以在在当前项目中使用 babel 命令,并可以添加参数然后将转译后的代码输出到目标文件中
执行
./node_modules/.bin/babel src/testItems/babel --out-dir src/testItems/dist
或者
npx babel src/testItems/babel --out-dir src/testItems/dist
会发现 testItems 下会生成一个文件夹 dist 及其下面的 index.js 文件,内容如下
console.log([1, 2, 3].findIndex(x => x == 4));
const alertMe = msg => {console.log(msg);
};
呵呵,和我的源码一毛一样,那有个毛用!!!
不要慌,这是因为我们还没有对源码做相应的处理,就想做牛肉一样,总不能拿盘生牛肉给客户吧,是时候准备材料开始拿刀了!
@babel/preset-env
先看一个简单例子, 这里的 @babel/core 后面会讲到
const babel = require("@babel/core");
babel.transform(
`
console.log([1,2,3].findIndex(x => x == 4))
const alertMe = (msg) => {console.log(msg)
}
`,
{plugins: ["@babel/plugin-transform-arrow-functions"]
},
function(err, result) {console.log(result);
}
);
执行
node src/testItems/babel/index.js
我们运行看下输出
{metadata: {},
options:
{ babelrc: false,
configFile: false,
passPerPreset: false,
envName: 'development',
cwd: 'd:\\ 我的项目 \\bq-test',
root: 'd:\\ 我的项目 \\bq-test',
plugins: [[Plugin] ],
presets: [],
parserOpts:
{sourceType: 'module', sourceFileName: undefined, plugins: [] },
generatorOpts:
{ filename: undefined,
auxiliaryCommentBefore: undefined,
auxiliaryCommentAfter: undefined,
retainLines: undefined,
comments: true,
shouldPrintComment: undefined,
compact: 'auto',
minified: undefined,
sourceMaps: false,
sourceRoot: undefined,
sourceFileName: 'unknown' } },
ast: null,
code:
'console.log([1, 2, 3].findIndex(function (x) {\n return x == 4;\n}));\n\nconst alertMe = function (msg) {\n console.log(msg);\n};',
map: null,
sourceType: 'module' }
可以看出这里的箭头函数已经被转译了,这就是 @babel/plugin-transform-arrow-functions 的作用。
但是在我们实际的开发中会用到更多的新语法,我们不可能一个个的添加插件吧,到这里我们可以看下 @babel/preset-env 这个插件,简单理解字面意思就是 ” 预设 ”,那到底是啥意思呢,大致意思就是官方帮我们准备的好的插件集合(包含 @bable/preset-es2015、@bable/preset-es2016、@bable/preset-es2017),修改代码如下会得到相同输出
const babel = require("@babel/core");
babel.transform(
`
console.log([1,2,3].findIndex(x => x == 4))
const alertMe = (msg) => {console.log(msg)
}
`,
{
presets: [
[
"@babel/preset-env",
{
targets: {node: "4"}
}
]
]
// plugins: ["@babel/preset-env"]
},
function(err, result) {console.log(result);
}
);
@babel/polyfill
我们再仔细看看这里
code:
'console.log([1, 2, 3].findIndex(function (x) {\n return x == 4;\n}));\n\nconst alertMe = function (msg) {\n console.log(msg);\n};',
map: null,
会发现除了箭头函数被转译了之外,findIndex 方法并没有任何的变化,那是为什么呢,这里理解下语法和特性的区别,语法指的是类似箭头函数等编码方式,特性指的是标准 api。后者需要 @babel/polyfill 来解决。
注意,使用 –save 参数而不是 –save-dev,因为这是一个需要在你的源码之前运行的 polyfill。
-
和 @babel/preset-env 一起用的时候
- 如果在.babelrc 中指定 useBuiltIns: ‘usage’ 的话,那么就不要在 webpack.config.js 的 entry array 和 source 中包含 @babel/polyfill 了。注意,@babel/polyfill 依然需要安装
- 如果在.babelrc 中指定 useBuiltIns: ‘entry’ 的话,那么就要和上面讨论的一样,在你应用的入口文件顶部通过 require 或者 import 引入 @babel/polyfill.
- 如果在.babelrc 中没有指定 useBuiltIns 的值或者 设置 useBuiltIns: false. 可以直接在 webpack.config.js 的 entry array 中添加 @babel/polyfill
module.exports = {entry: ['@babel/polyfill', './app'] }
- 如果没有使用 @babel/preset-env. 那么就可以像我们上面讨论的一样把 @babel/polyfill 添加到 webpack 的 entry array 中。你也可以直接通过 import 或 require 把它添加到应用的入口文件顶部。但是我们并不推荐这么做
@babel/core
@babel/core 是 babel 的核心包,起到转译的作用,也就是我们前面提到的刀,主要方法有 transform 等
简单试用下:
下面添加配置文件来使用 babel 命令进行编译
主要有一下几种方式创建配置文件
- babel.config.js
在项目的根目录(package.json 文件所在目录)下创建一个名为 babel.config.js 的文件,并输入如下内容。
module.exports = function (api) {api.cache(true);
const presets = [...];
const plugins = [...];
return {
presets,
plugins
};
}
- .babelrc
在你的项目中创建名为 .babelrc 的文件,并输入以下内容。
{"presets": [...],
"plugins": [...]
}
- package.json
或者,还可以选择将 .babelrc 中的配置信息作为 babel 键(key)的值添加到 package.json 文件中,如下所示:
{
"name": "my-package",
"version": "1.0.0",
"babel": {"presets": [ ...],
"plugins": [...],
}
}
- .babelrc.js
与 .babelrc 的配置相同,但你可以使用 JavaScript 编写。
const presets = [...];
const plugins = [...];
module.exports = {presets, plugins};
这里使用第一种配置方法
module.exports = function(api) {api.cache(true);
const presets = [
[
"@babel/preset-env",
{
"targets": {"node": "2",},
"corejs": "3",
"useBuiltIns": "usage"
}
]
];
return {presets};
};
说明:
- targets 指定需要兼容的运行环境,也就是这里编译的代码能在 node 版本是 2 的 node 环境运行
- 使用 ”useBuiltIns”: “usage” 表示会使用 @bable/polyfill
其他插件
@babel/plugin-transform-runtime
"presets": [
[
"@babel/preset-env",
{
"targets": {"node": "4"}
}
]
],
"plugins": ["@babel/plugin-transform-runtime"]
}
编译后的代码
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
require("core-js/modules/es.array.find-index");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var testClass =
/*#__PURE__*/
function () {function testClass(msg) {(0, _classCallCheck2.default)(this, testClass);
this.message = msg;
}
(0, _createClass2.default)(testClass, [{
key: "say",
value: function say() {alertMe(this.message);
}
}]);
return testClass;
}();
console.log([1, 2, 3].findIndex(function (x) {return x == 4;}));
var alertMe = function alertMe(msg) {console.log(msg);
};
会发现 createClass 等比较通用的方法被提出来了,便于减少重复代码
当然还有其他的插件,这里就不一一列举了
总结
最后回顾一下开头的栗子做一下总结:
- @babel/cli 就是客户发出的订单要求也就是代码当前工程的运行指令
- 源代码就是生牛肉作为输入
- @bable/preset-env(还有其他的一些预设包集合)就是点缀物品改变原代码的表达形式
- @bable/polyfill 就是调料提供源代码的性特性补充
- @bable/core 就是操刀了或者其他的一些操作工具
- 然后结合一些其他插件如 @babel/plugin-transform-runtime 等插件一顿操作
- 最后生成 10 分兼容的编译代码
栗子不是很恰当,旨在帮助自己理解和记忆。