随着前端技术的快速迭代,「前端工程化」使得前端工程师的编程效率得到了大幅度提高。在平常的页面开发中,我们更多的是关注如何使用「ES6」、「ES7」这些新特性来进行更高效的工作,至于这些新特性是如何兼容不同浏览器和平台的,会由「Webpack」等脚手架帮我们处理。
然而,只会使用「ES6」、「ES7」这些新特性是远远不够的,知其然并知其所以然,了解「Webpack」是如何将这些新特性语言转换为浏览器兼容的版本才有利于我们的成长。
因此,「今天就想和大家来聊聊js代码是如何实现跨浏览器兼容的~」
Babel的作用
「Babel的本质是一个编译器,主要功能就是将采用ES5+ 进行编程的js代码转换为浏览器兼容的js语法,以便于页面可以正常显示。」
需要「Babel」的原因是目前的浏览器并不能及时的「兼容」js的新语法,而在开发过程中,我们往往会选择「es6」、「es7」、「jsx」、「ts」进行开发,如果不做兼容性处理,可能会导致浏览器不能识别并执行这些代码。
因此就需要「Babel编译器」来将这些高版本的js语言编译并转换成「向后兼容」的代码,以确保代码在各种浏览器和平台上运行良好。
Webpack和Babel的关系
前文说到,「JavaScript」在不同浏览器上的兼容性处理由「Babel」完成, 那么「Webpack」和「Babel」的关系是什么呢?
简单来说,前端工程化的核心是「Webpack」,而「Webpack本质是一个js的代码打包机」,并支持将「非js」的语言转换为「js」语言,最终打包成一个「js」文件输出。
在打包「js」功能的基础上,「Webpack」在进行代码打包过程中,会使用一系列的「loader」和「plugin」来提高前端代码的性能和可维护性,从而实现前端工程化的目的。
比如使用「css-loader」将css文件转换为js模块,使其可以被「Webpack」处理,使用「terser-webpack-plugin」来压缩js代码,提升性能等等。
「在这其中,Babel就是以Webpack中loader和plugin的角色来处理JavaScript浏览器兼容性问题。」
因此,「从关系的角度来说,Webpack和Babel是一种协作关系;从使用的角度来说,Babel是Webpack的loader和plugin,最终目的就是为了更好的实现前端工程化」。
如果仅仅是实现js语言的「向后兼容」,只使用「Babel」也是完全可以的,本文也会演示如何自定义一个「Babel」插件来做一些有趣的事情~
Babel的执行过程
对于计算机来说,我们敲出来的代码都是「高级语言」,即人能读懂但计算机读不懂,因此想要执行就必须经过「编译」。
像「react」、「vue」等框架需要对其 「.vue」 和 「.jsx」 代码进行编译才能在页面上正常显示也是同理。
既然「Babel」需要将高版本js语法转换成低版本js语法,那么也势必需要进行编译,因此「Babel」的执行过程就是一个编译转换的过程。
「Babel的执行过程如下图所示:」
由此可以发现,使用「Babel」进行代码转换的过程主要包含三个部分:
编译(parse)
转换(transform)
生成(generator)
1. parse
在「Babel」执行过程中,编译器插件是@babel/parser,其「作用就是将源码转换为AST」,使用前需要「npm install @babel/parser」, 使用方法如下:
const babelParser = require("@babel/parser");
const code = "const name= 'dossweet';";
const ast = babelParser.parse(code);
console.log(ast);
执行后的结果如下:这就是「Babel」生成的「AST结构」。可以发现这个「AST」和平常我们看到的「AST」结构好像不同,因为没有发现“name"、“=”等词法单元。
这是因为「Babel」的「parser」是根据「Babel」的「AST」结构生成的,基于「ESTree规范」。
"name"、“=”等词法单元都在body:[[Node]]中,执行「ast.program.body[0].declarations」就能看到编写的代码内容啦:
2. transform
「transform是Babel中非常重要的一个环节,将高版本的JS转换为低版本的JS就是在这个阶段执行。」
「Babel」的执行可以看做是一个工具链。所谓工具链,就是需要依赖一些插件来完成将高版本js转换为低版本js,因此「只有包含插件的Babel才能发挥出真正的作用」。
如果没有转换插件,「Babel」只会将源码生成「AST抽象语法树」,然后再通过生成器生成和原来的源码一模一样的代码,这样的过程没有任何意义,「因此插件是Babel的核心,也是用户可以自定义执行过程的环节。」
「Babel」官网提供了一些官方的插件,例如 「@babel/core」 、「@babel/types」 、「@babel/traverser」 来供大家使用。
「@babel/types」插件的主要作用是创建、修改、删除、查找ast节点。
「@babel/traverser」插件的主要作用是对ast进行遍历parse,在迭代的过程中可以定义回调函数,回调函数的参数提供了丰富的增、删、改、查以及类型断言的方法。
「@babel/core」插件的主要作用是将底层的插件进行封装,并加入读取、分析配置文件等功能来是简化插件的执行。
3. generator
「generator是Babel的生成器」,其使用也是以@babel/generator的方式进行的,「主要功能就是将转换后的AST抽象语法树转换为代码,以便于浏览器可以运行」。
可以发现,「Babel」将高版本的js语法转换为低版本的js语法就是通过先将源代码转换为AST抽象语法树,然后借助一系列插件来遍历抽象语法树中的节点,修改js语法为低版本的写法,然后将转换后的抽象语法树生成低版本的源码,从而达到不同浏览器兼容的目的。
与此同时,「Babel」在「tranform阶段」修改抽象语法树时,不仅可以修改js语法为低版本,还能做一些其他有意义的事情,比如「删去所有的console.log」、「统计代码执行时间」、「修改变量名称」等,这些工作可以大幅度提高我们的开发效率,不再需要我们在业务代码中手动处理,体验非常nice~
「接下来,我们就一起实现一个简易的Babel插件,来进一步体验Babel插件的魅力吧!」
实现一个Babel的插件
插件格式:
「Babel」给予了开发者自定义插件的功能,但是我们需要按照插件的规范来进行功能的开发。
一个「Babel插件」的框架至少需要包含如下内容:
export default function({ types: t }) {
return {
visitor: {
// 访问者方法 1
Identifier(path, state) {
// 处理 Identifier 节点的代码
},
// 访问者方法 2
BinaryExpression(path, state) {
// 处理 BinaryExpression 节点的代码
},
// 访问者方法 n
// ...
}
};
};
其中 「{type: t}」 是一个「Babel的对象」,「visitor」是插件的访问者,用于访问抽象语法树上的节点,从而进行一些增删改查的操作。
「Identifier」和「BinaryExpression」等都是访问者方法,在遍历抽象语法树时会被调用。
「Visitor」 中的每个函数接收2个参数:「path」 和 「state」。其中 「path」 参数表示当前正在被遍历的节点,我们可以使用它来访问和修改节点,「state」用于在插件内部存储状态信息。
插件功能:
删除所有的console.log
插件源码:
const { types: t } = require('@babel/core');
module.exports = function() {
return {
visitor: {
CallExpression(path) {
if (t.isMemberExpression(path.node.callee) &&
t.isIdentifier(path.node.callee.object, { name: 'console' }) &&
t.isIdentifier(path.node.callee.property, { name: 'log' })) {
path.remove();
}
}
}
};
};
上述代码中,使用了 「@babel/types」 模块中的 「types 对象」,该对象提供了一些常见的 「AST」 节点类型定义和操作方法,例如 isMemberExpression、isIdentifier 等。通过这些方法,我们可以判断 「AST」 节点的类型,并对其进行处理。
在这个插件中,我们主要是判断函数调用是否为 「console.log」,如果是,则调用 「path.remove()」 方法将其从 「AST」 中移除,从而实现将 「console.log」 语句自动注释掉的功能。
「仓库地址:https://github.com/dossweet/BabelStudy」
总结
「Babel」的本质就是一个「编译器」,用法就是使用插件来修改「抽象语法树AST」,从而实现高版本js语法转换为低版本js语法的目的,最终达到保证不同的浏览器环境都可以正常使用页面的效果。
在「前端工程化」中,「Babel」常作为「Webpack」的「loader」和「plugin」与「Webpack」配合使用。
考虑到「Babel」是通过操作「抽象语法树」来进行一些转换工作,因此我们也可以自定义一些插件来提高我们的开发效率~
参考文献:
[1] Babel官网: https://www.babeljs.cn/
[2] 编写Babel插件: https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md
[3] 理解Babel的基本原理和使用方法: https://www.cnblogs.com/jyybeam/p/13375179.html
往期推荐
Vue3除了keep-alive,还有哪些页面缓存的实现方案
最后
欢迎加我微信,拉你进技术群,长期交流学习...
欢迎关注「前端Q」,认真学前端,做个专业的技术人...
点个在看支持我吧