写在前面
先理解下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分兼容的编译代码
栗子不是很恰当,旨在帮助自己理解和记忆。