一、babel介绍
Babel 是 JavaScript 编译器,更确切地说是源码到源码的编译器,通常也叫做“转换编译器(transpiler)”。 意思是说你为 Babel 提供一些 JavaScript 代码,Babel 更改这些代码,然后返回给你新生成的代码。在这个源码到源码的转换过程当中,抽象语法树,即AST,起到了重要的作用。
二、抽象语法树(AST)
抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,一种编程语言的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上。和我们的常说的虚拟dom有点像,虚拟dom是用json的格式把DOM结构抽象成js对象,用于描述DOM的结构,每个节点的类型、属性等等,类似的AST是把js的源代码抽象为js对象的格式,以方便描述这段代码的语法。程序代码本身被映射成为一棵语法树,通过操纵语法树,我们能够精准的获得程序代码中的每一个精确的节点。例如声明语句,赋值语句等。
三、babel的处理步骤
Babel 的三个主要处理步骤分别是: 解析(parse),转换(transform),生成(generate)。
解析步骤接收代码并输出 AST
转换步骤接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。 这是 Babel 或是其他编译器中最复杂的过程,同时也是插件将要介入工作的部分。在babel-loader中有两种方式可以配置babel插件,我们经常会配置:
rules: [
{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ["env", 'stage-0'],
plugins: [
["extract", { "library": "lodash" }],
["transform-runtime", {}]
]
},
}
]
注意:plugins 的插件使用顺序是顺序的,而 preset 则是逆序的。所以上面的执行方式是extract>transform-runtime>stage-0>env
代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps)。代码生成比较简单,就是深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。
有了以上的基础知识之后我们看看babel插件是怎么编写的。
四、编写一个简单的babel插件
1、插件格式
先从一个接收了当前babel对象作为参数的function开始。
export default function(babel) {
// plugin contents
}
我们经常会这样写:
export default function({ types: t }) {
//
}
接着返回一个对象,其 visitor 属性是这个插件的主要访问者。
export default function({ types: t }) {
return {
visitor: {
// visitor contents
}
};
};
Visitor 中的每个函数接收2个参数:path 和 state;
export default function({ types: t }) {
return {
visitor: {
Identifier(path, state) {},
ASTNodeTypeHere(path, state) {}
}
};
};
Path 是表示两个节点之间连接的对象。表示AST中节点之间的相互关联关系。例如,如果有下面这样一个节点及其子节点︰
{
type: "FunctionDeclaration",
id: {
type: "Identifier",
name: "square"
},
...
}
将子节点 Identifier 表示为一个路径(Path)的话,看起来是这样的:
{
"parent": {
"type": "FunctionDeclaration",
"id": {...},
....
},
"node": {
"type": "Identifier",
"name": "square"
}
}
我们发现path除了含有节点之间的关系之外,同时它还包含关于该路径的其他元数据:
{
"parent": {...},
"node": {...},
"hub": {...},
"contexts": [],
"data": {},
"shouldSkip": false,
"shouldStop": false,
"removed": false,
"state": null,
"opts": null,
"skipKeys": null,
"parentPath": null,
"context": null,
"container": null,
"listKey": null,
"inList": false,
"parentKey": null,
"key": null,
"scope": null,
"type": null,
"typeAnnotation": null
}
2、写一个简单的插件
我们写一个简单的插件,把所有定义变量名为a的换成b, 先从astexplorer看下var a = 1的 AST
{
"type": "Program",
"start": 0,
"end": 10,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 9,
"declarations": [
{
"type": "VariableDeclarator",
"start": 4,
"end": 9,
"id": {
"type": "Identifier",
"start": 4,
"end": 5,
"name": "a"
},
"init": {
"type": "Literal",
"start": 8,
"end": 9,
"value": 1,
"raw": "1"
}
}
],
"kind": "var"
}
],
"sourceType": "module"
}
从上面可以看出我们要找的节点类型是VariableDeclarator,以下是我们写出的插件代码:
export default function({ types: t }) {
return {
visitor: {
VariableDeclarator(path, state) {
if (path.node.id.name == 'a') {
path.node.id = t.identifier('b')
}
}
}
}
}
我们要把id属性是 a 的替换成 b 就好了。但是这里不能直接path.node.id.name = 'b'。如果操作的是object,就没问题,但是这里是 AST 语法树,所以想改变某个值,就是用对应的 AST 来替换,现在我们用新的标识符来替换这个属性。
我们用@babel/core中的transform方法对我们写的插件进行测试一下:
import * as babel from '@babel/core';
const c = `var a = 1`;
const { code } = babel.transform(c, {
plugins: [
function({ types: t }) {
return {
visitor: {
VariableDeclarator(path) {
if (path.node.id.name == 'a') {
path.node.id = t.identifier('b')
}
}
}
}
}
]
})
console.log(code); // var b =
3、实现一个简单的按需打包的功能
例如我们要实现把import { Button } from 'antd'转成import Button from 'antd/lib/button'
通过对比 AST 发现,specifiers里的type和source不同。
// import { Button } from 'antd'
"specifiers": [
{
"type": "ImportSpecifier",
...
}
],
"source": {
"type": "Literal",
"start": 23,
"end": 29,
"value": "antd",
"raw": "'antd'"
}
// import Button from 'antd/lib/button'
"specifiers": [
{
"type": "ImportDefaultSpecifier",
...
}
],
"source": {
"type": "Literal",
"start": 19,
"end": 36,
"value": "antd/lib/button",
"raw": "'antd/lib/button'"
}
import * as babel from '@babel/core';
const c = `import { Button } from 'antd'`;
const { code } = babel.transform(c, {
plugins: [
function({ types: t }) {
return {
visitor: {
ImportDeclaration(path) {
const { node: { specifiers, source } } = path;
if (!t.isImportDefaultSpecifier(specifiers[0])) { // 对 specifiers 进行判断
const newImport = specifiers.map(specifier => (
t.importDeclaration(
[t.ImportDefaultSpecifier(specifier.local)],
t.stringLiteral(`${source.value}/lib/${specifier.local.name}`)
)
))
path.replaceWithMultiple(newImport)
}
}
}
}
}
]
})
console.log(code); // import Button from "antd/lib/Button"
五、总结
我们介绍了babel的功能、babel工作的三个阶段以及怎么写一个简单的插件,写插件主要用到了transform这个函数,插件的的函数返回一个对象,visitor是这个插件的访问者,通过它来访问AST,在visitor里定义一些函数做相关的操作。如果想了解更多有关babel插件的知识可访问babel插件手册
向web前端开发者整理提供的chrome插件或应用:比如Postman、JSON Viewer、Page Ruler 、ChromeADB 等等
作为前端开发,我们都习惯使用一些开源的插件例如jquery工具库,那么如何使用原生js来开发封装一个自己的插件呢?接下来就看一下怎么去开发一个自己的js插件,先上代码
jquery.typeahead.js是一款高级的自动补全jQuery插件。该自动补全插件提供超过50个配置选项和回调方法,用于完成自动补全功能,能够完成绝大部分表单自动补全的需求。
这篇文章为大家分享图片轮播插件,最全最简单最通用的 幻灯片轮播插件,pc端和移动端都可完美使用,能满足绝大部分网站的轮播需求。js轮播插件包括Swiper、slick、owl carousel2、jssor/slider 、iSlider 等
在上个项目中,客户希望时间选择插件可以是ios风格的那种,但是找了很久,发现并没有用vue的ios风格时间插件,于是自己便自己造了一个轮子.插件依赖于better-scroll和vue
在前端开发中,使用Visual Studio Code有哪些你常用的插件?推荐几个自己喜欢的,不带链接,自己搜索安装吧。这些都是比较实用、前端必备的插件集
常用谷谷歌浏览器确实没有其它国产软件的内置功能丰富。但是 Google 浏览器的的优点恰恰就体现在拥有超简约的界面,以及支持众多强大好用的扩展程序,用户能够按照自己的喜好去个性化定制浏览器。今天我就给大家介绍几款自己常用的插件。
安装Sublime text 2插件很方便,可以直接下载安装包解压缩到Packages目录,也可以安装package control组件,然后直接在线安装
BlockUI 插件是用于进行AJAX操作时模拟同步传输时锁定浏览器操作。当它被激活时,它会阻止使用者与页面(或页面的一部分)进行交互,直至它被取消。BlockUI以在DOM中添加元素的方法来实现阻止用户与浏览器交互的外观和行为
使用vscode开发vue项目的时候,从远端拉下一个新的项目后,安装完依赖后跑起项目时,发现直接报了一堆语法错误:包括换行、空格、单双引号、分号等各种格式问题
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!