祖传开头
这篇文章算是一次关于babel的一次扫盲。整理出一份babel的基础概念扫盲,写给自己,也写需要这种基础梳理的小伙伴吧。希望大家都不要因为非常基础的问题,比如babel版本之类的小问题绊住了手脚哈哈哈!另外babel也是一个非常好的学习工具,可以把es6的代码转化成es5,抛开各种语法糖,探究js的本质。总而言之,希望这篇文章能对各位有所助益吧~
一、Babel基本概念
特点:
- 静态分析,在不需要执行代码的情况下,对代码分析处理。
- 通过组合各种模块,生一个能实现特定功能的集合。
- @babel/core 包含了所有babel的 核心功能,使用babel前必须先引入。
?一个简单的例子
第一步:在 .babelrc
文件中进行babel的配置。
{
"presets": [
[
"@babel/preset-env"
]
]
}
**第二步:安装 @babel/core
、 @babel/cli
和 ****@babel/preset-env**
。
@babel/core是babel的和核心库。@babel/cli是babel能在终端环境中可以运行的工具库。
第三步:创建源代码文件和babel运行命令。
src/index.js
const newArys = [1, 2, 3, 4, 5].map((n) => n * n);
package.json
"scripts": {
"compiler": "babel src --out-dir lib",
"watch": "npm run compiler -- --watch"
}
命令 compiler
的作用就是将 src
目录下所有的JavaScript文件都进行解析,最后输出到 lib
目录下。
命令 watch
的作用就是监听 src
目录下是否有进行改动,若有就重新进行编译。
**
更多babel-cli命令
以下列举了几个比较常见的配置命令。
说明 | 使用 | --out-file/-o | 指定输出文件 | babel src --out-file index.js | --out-dir/-o | 指定输出目录 | babel src --out-dir lib | --watch/-w | 监听文件改动并且进行重新编译 | babel src --out-dir lib --watch | --source-maps/-s | 生成映射代码 | babel src --out-file script-compiled.js --source-maps | --ignore | 指定不需要转换的文件 | babel src --out-dir lib ---ignore "src/**/*.spec.js", |
---|
www.babeljs.cn/docs/babel-…
二、Babel原理
三个步骤
babel的处理步骤主要分为以下步骤:
第一步:解析(parse)
将原代码转换成AST树,也就是抽象语法树。这个步骤分为两个阶段:
- 词法分析
- 语法分析
词法分析
简单的来理解,就是把原代码(字符串形式)进行分割,生成一个个最小单位——令牌(token),方便后续的组装。
一个简单的表达式:
a * b
经过词法分析后,以上表达式分割成3个部分。变成一组 令牌流(tokens)
。形式如下:
[
{ type: { ... }, value: "a", start: 0, end: 1, loc: { ... } },
{ type: { ... }, value: "*", start: 2, end: 3, loc: { ... } },
{ type: { ... }, value: "b", start: 4, end: 5, loc: { ... } },
...
]
词法分析
,就是把每个token的关系描述清楚,把有关系的token组合在一起。比如上述三个token组合起来就是个获取乘积的表达式。经过词法分析后,就会生成AST树。
?先来看一个具体的AST树:(通过AST Explorer模拟AST树的生成)
源码:
function add(a, b) {
return a + b
}
{
"type": "Program",
"start": 0,
"end": 37,
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 37,
"id": {
"type": "Identifier",
"start": 9,
"end": 12,
"name": "add"
},
"expression": false,
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 13,
"end": 14,
"name": "a"
},
{
"type": "Identifier",
"start": 16,
"end": 17,
"name": "b"
}
],
"body": {
"type": "BlockStatement",
"start": 19,
"end": 37,
"body": [
{
"type": "ReturnStatement",
"start": 23,
"end": 35,
"argument": {
"type": "BinaryExpression",
"start": 30,
"end": 35,
"left": {
"type": "Identifier",
"start": 30,
"end": 31,
"name": "a"
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 34,
"end": 35,
"name": "b"
}
}
}
]
}
}
],
"sourceType": "module"
}
AST树的每一层结构都比较相似,每一层树都会由一个个节点(Node)构成。每个节点的type都会对这个节点的类型进行说明:
Program
:一个程序的根节点类型FunctionDeclaration
:函数声明Identifier
:变量声明BinaryExpression
:表达式- ...
{
type:"",
id:"",
params:{},
body:{}
}
第二步:转换(transform)
这个步骤就是接收到第一步生成的AST树后进行遍历,对AST的节点进行更新、添加、删除等操作。也是整个编译过程中最复杂的部分,也是babel插件介入工作并完成一些特定功能的过程。
babel插件原理:
- 创建一个访问者,这个访问者对象会提供具体的获取树节点信息的方法
- 实际访问的是一个路径
**
?看一个简单的babel插件例子
// babel-plugins-mm
module.exports = function () {
return {
visitor:{
Identifier: {
enter(path) {
console.log("Entered!"+ path.node.name);
},
exit(path) {
console.log("Exited!"+ path.node.name);
}
}
}
};
}
使用插件:(只写插件名称,默认会从node-modules里查找)mm
是 babel-plugins-mm
的简写。
{
"presets": [
["@babel/preset-env"]
],
"plugins": [
["mm"]
]
}
第三步:生成(generate)
最后一步就是就是将处理过后的AST再次转化后字符串形式的代码。(深度优先的遍历AST树,并生成字符串形式的代码。)
三、Babel配置
{
// 预设
"presets":[...],
// 插件
"plugins":[...],
// 环境配置
"env": {
"development": {
"plugins": [...]
},
"production": {
"plugins": [...]
}
}
}
4、Babel预设
目前官方的推荐的一些预设:
@babel/preset-env
@babel/preset-flow
@babel/preset-react
@babel/preset-typescript
...
注意:babel @7.0.0之后对标准阶段提案的一些预设都会废弃,比如以下预设不推荐使用了。
@babel/preset-stage-0
@babel/preset-stage-1
@babel/preset-stage-2
@babel/preset-stage-3
@babel/preset-env
对ES2015和ES2016进行转换,并且处理polyfill。
preset-env有以下配置,具体可以访问官网查看。
targets
spec
loose
modules
debug
include
exclude
useBuiltIns
corejs
forceAllTransforms
configPath
ignoreBrowserslistConfig
shippedProposals
tagets
指定适配游览器范围,如果设置了该参数,优先级会比.browserslist
或者 package.json/browserslist
设置的高。如果不设置,则默认会获取 .browserslist
或者 package.json/browserslist
指定的游览器适配范围。
但是在项目中,还是建议使用 .browserslistrc
或者 package.json/browserslist
指定目标。
useBuiltIns
"usage"
| "entry"
| false
, defaults to false
.
这个配置参数主要的作用是明确如何处理polyfill。
**polyfill
先简单的了解一下 polyfill
,中文成为垫片,形象一点描述用一个个垫片铺平低端游览器的坑,使代码运行的和在高端游览器中一样。对ES2015+的一些语法进行转化适配(除了一些实验性的Js特性),比如一些新的内置对象和实例方法等(Promise、Array.from、Object.assign...)
@babel/polyfill
其实就是由 core-js/stable
和 regenerator-runtime/runtime
组合而成。
- core-js:@babel/polyfill的底层依赖库,为ES2015+的JS特性提供编译转化功能。
- **regenerator-runtime : **专门为
Generator
函数提供编译转化功能,比如在项目中使用了async/await就需要引入这个插件。
以下两种写法其实是一样,而且只能两种方式取一种,否则会报重复引入的报错。
import "@babel/polyfill";
// ====等同于====
import "core-js/stable";
import "regenerator-runtime/runtime";
但是由于polyfill的体积比较大,通常情况下我们不需要引入完成的polyfill,而且全部引入也会导致打包后体积变大。所以此处useBuiltIns就开始发挥作用。
useBuiltIns:usage
不需要在文件入口处引入polyfill,env会提供一个插件,根据每个文件的需求进行特殊引入垫片。
此时需要你在babel配置中指定corejs的版本的版本。
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}
比如promise-a.js文件只使用到promise,就只会在该文件入口引入promise相关的垫片。
src/promise-a.js
// src/promise-a.js
new Promise((resolve)=>{
console.log("promise A");
resolve()
})
lib/promise-a.js
"use strict";
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");
new Promise(function (resolve) {
console.log("promise A");
resolve();
});
useBuiltIns: 'entry'
需要在文件入口处手动引入polyfill,然后根据当前的环境尽可能的引入需要的功能。
比如引入一个array相关的垫片。
src/entry.js
import "core-js/es/array";
lib/entry.js
"use strict";
require("core-js/modules/es.array.concat");
require("core-js/modules/es.array.copy-within");
require("core-js/modules/es.array.every");
require("core-js/modules/es.array.fill");
require("core-js/modules/es.array.filter");
require("core-js/modules/es.array.find");
require("core-js/modules/es.array.find-index");
require("core-js/modules/es.array.flat");
require("core-js/modules/es.array.flat-map");
...
useBuiltIns: false
不为文件自动添加垫片,也不会对于自行引入的core-js或者@babel/polyfill有任何转化。
corejs
2
, 3
or { version: 2 | 3, proposals: boolean }
, defaults to 2
.
这个选项需要和useBuiltIns结合起来使用。当useBuiltIns: usage或者useBuiltIns: entry时才会起效。
core-js目前存在两个版本,分别是coreJs2和coreJs3。coreJs2目前已经不再更新,所以对于一些特别新的特性只会在coreJs3去更新。
如果使用coreJs3时只想注入稳定的ECMAScript功能,有以下两种方式:1.对于useBuiltIns: usage
corejs: { version: 3, proposals: true }<br />
2.对于useBuiltIns: entry
corejs配置为 3
,另外在项目引入import "core-js/proposals/string-replace-all"
✍其他小知识点:
1.插件名称简写
@babel/plugin-XXX 等同于 @babel/XXX
2.babel命名
@7.0.0以上的包都是以@开头命名,用于区分6.x版本
3.执行顺序
- 插件在 Presets 前运行。
- 插件顺序从前往后排列。
- Preset 顺序是颠倒的(从后往前)。
参考文档
Babel用户手册
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!