您现在的位置是:亿华云 > IT科技类资讯
解剖Babel - 向前端架构师迈出一小步
亿华云2025-10-03 02:04:19【IT科技类资讯】9人已围观
简介当聊到Babel的作用,很多人第一反应是:用来实现API polyfill。事实上,Babel作为前端工程化的基石,作用远不止这些。作为一个庞大的家族,Babel生态中有很多概念,比如:preset、
当聊到Babel的解剖架构作用,很多人第一反应是前端:用来实现API polyfill。
事实上,师迈Babel作为前端工程化的解剖架构基石,作用远不止这些。前端
作为一个庞大的师迈家族,Babel生态中有很多概念,解剖架构比如:preset、前端plugin、师迈runtime等。解剖架构
这些概念使初学者对Babel望而生畏,前端对其理解也止步于webpack的师迈babel-loader配置。
本文会从Babel的解剖架构核心功能出发,一步步揭开Babel大家族的前端神秘面纱,向前端架构师迈出一小步。师迈
Babel是什么
Babel 是一个 JavaScript 编译器。作为JS编译器,Babel接收输入的JS代码,经过内部处理流程,最终输出修改后的JS代码。

在Babel内部,会执行如下步骤:
将Input Code解析为AST(抽象语法树),这一步称为parsing 编辑AST,这一步称为transforming 将编辑后的香港云服务器AST输出为Output Code,这一步称为printing
从Babel仓库[1]的源代码,可以发现:Babel是一个由几十个项目组成的Monorepo。
其中babel-core提供了以上提到的三个步骤的能力。
在babel-core内部,更细致的讲:
babel-parser实现第一步 babel-generator实现第三步要了解第二步,我们需要简单了解下AST。
AST的结构
进入AST explorer[2],选择@babel/parser作为解析器,在左侧输入:
const name = [ka, song];可以解析出如下结构的AST,他是JSON格式的树状结构:

在babel-core内部:
babel-traverse可以通过「深度优先」的方式遍历AST树 对于遍历到的每条路径,babel-types提供用于修改AST节点的节点类型数据所以,整个Babel底层编译能力由如下部分构成:

当我们了解Babel的底层能力后,接下来看看基于这些能力,上层能实现什么功能?
Babel的上层能力
基于Babel对JS代码的编译处理能力,Babel最常见的上层能力为:
polyfill DSL转换(比如解析JSX) 语法转换(比如将高级语法解析为当前可用的云服务器提供商实现)由于篇幅有限,这里仅介绍polyfill与「语法转换」相关功能。
polyfill
作为前端,最常见的Babel生态的库想必是@babel/polyfill与@babel/preset-env。
使用@babel/polyfill或@babel/preset-env可以实现高级语法的降级实现以及API的polyfill。
从上文我们知道,Babel本身只是JS的编译器,以上两者的转换功能是谁实现的呢?
答案是:core-js
core-js简介
core-js是一套模块化的JS标准库,包括:
一直到ES2021的polyfill promise、symbols、iterators等一些特性的实现 ES提案中的特性实现 跨平台的WHATWG / W3C特性,比如URLcore-js作者Denis Pushkarev
从core-js仓库[3]看到,core-js也是由多个库组成的Monorepo,包括:
core-js-builder core-js-bundle core-js-compat core-js-pure core-js我们介绍其中几个库:
core-js
core-js提供了polyfill的源码库核心实现。
import core-js/features/array/from; import core-js/features/array/flat; import core-js/features/set; import core-js/features/promise; Array.from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3] [1, [2, 3], [4, [5]]].flat(2); // => [1, 2, 3, 4, 5] Promise.resolve(32).then(x => console.log(x)); // => 32直接使用core-js会污染全局命名空间和对象原型。
比如上例中修改了Array的原型以支持数组实例的flat方法。
core-js-pure
core-js-pure提供了独立的命名空间:
import from from core-js-pure/features/array/from; import flat from core-js-pure/features/array/flat; import Set from core-js-pure/features/set; import Promise from core-js-pure/features/promise; from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3] flat([1, [2, 3], [4, [5]]], 2); // => [1, 2, 3, 4, 5] Promise.resolve(32).then(x => console.log(x)); // => 32这样使用不会污染全局命名空间与对象原型。
core-js-compat
core-js-compat根据Browserslist维护了不同宿主环境、不同版本下对应需要支持特性的集合。
Browserslist[4]提供了不同浏览器、node版本下ES特性的支持情况
Browserslist
比如:
"browserslist": [ "not IE 11", "maintained node versions" ]代表:非IE11的版本以及所有Node.js基金会维护的版本。
@babel/polyfill与core-js关系
@babel/polyfill可以看作是:core-js加regenerator-runtime。
regenerator-runtime是generator以及async/await的运行时依赖单独使用@babel/polyfill会将core-js全量导入,造成项目打包体积过大。
从Babel v7.4.0[5]开始,@babel/polyfill被废弃了,可以直接引用core-js与regenerator-runtime替代为了解决全量引入core-js造成打包体积过大的问题,我们需要配合使用@babel/preset-env。
preset的含义
在介绍@babel/preset-env前,我们先来了解preset的意义。
初始情况下,Babel没有任何额外能力,其工作流程可以描述为:
const babel = code => code;其通过plugin对外提供介入babel-core的能力,类似webpack的plugin对外提供介入webpack编译流程的能力。
plugin分为几类:
@babel/plugin-syntax-*语法相关插件,用于新的语法支持。比如babel-plugin-syntax-decorators[6]提供decorators的语法支持 @babel/plugin-proposal-*用于ES提案的特性支持,比如babel-plugin-proposal-optional-chaining是可选链操作符特性支持 @babel/plugin-transform-*用于转换代码,transform插件内部会使用对应syntax插件多个plugin组合在一起形成的集合,被称为preset。
@babel/preset-env
使用@babel/preset-env,可以「按需」将core-js中的特性打包,这样可以显著减少最终打包的体积。
这里的「按需」,分为两个粒度:
宿主环境的粒度。根据不同宿主环境将该环境下所需的所有特性打包 按使用情况的粒度。仅仅将使用了的特性打包我们来依次看下。
宿主环境的粒度
当我们按如下参数在项目目录下配置browserslist文件(或在@babel/preset-env的targets属性内设置,或在package.json的browserslist属性中设置):
not IE 11 maintained node versions会将「非IE11」且「所有Node.js基金会维护的node版本」下需要的特性打入最终的包。
显然这是利用了刚才介绍的core-js这个Monorepo下的core-js-compat的能力。
按使用情况的粒度
更理想的情况是只打包我们使用过的特性。
这时候可以设置@babel/preset-env的useBuiltIns属性为usage。
比如:
a.js:
var a = new Promise();b.js:
var b = new Map();当宿主环境不支持promise与Map时,输出的文件为:
a.js:
import "core-js/modules/es.promise"; var a = new Promise();b.js:
import "core-js/modules/es.map"; var b = new Map();当宿主环境支持这两个特性时,输出的文件为:
a.js:
var a = new Promise();b.js:
var b = new Map();进一步优化打包体积
打开babel playground[7],输入:
class App { }会发现编译出的结果为:
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var App = function App() { "use strict"; _classCallCheck(this, App); };其中_classCallCheck为辅助方法。
如果多个文件都使用了class特性,那么每个文件打包对应的module中都将包含_classCallCheck。
为了减少打包体积,更好的方式是:需要使用「辅助方法」的module都从同一个地方引用,而不是自己维护一份。
@babel/runtime包含了Babel所有「辅助方法」以及regenerator-runtime。
单纯引入@babel/runtime还不行,因为Babel不知道何时引用@babel/runtime中的「辅助方法」。
所以,还需要引入@babel/plugin-transform-runtime。
这个插件会在编译时将所有使用「辅助方法」的地方从「自己维护一份」改为从@babel/runtime中引入。
所以我们需要将@babel/plugin-transform-runtime置为devDependence,因为他在编译时使用。
将@babel/runtime置为dependence,因为他在运行时使用。
总结
本文从底层向上介绍了前端日常业务开发会接触的Babel大家族成员。他们包括:
底层
@babel/core(由@babel/parser、@babel/traverse、@babel/types、@babel/generator等组成)
他们提供了Babel编译JS的能力。
注:这里@babel/core为库名,前文中babel-core为其在仓库中对应文件名
中层
@babel/plugin-
*Babel对外暴露的API,使开发者可以介入其编译JS的能力
上层
@babel/preset-
*日常开发会使用的插件集合。
对于立志成为前端架构师的同学,Babel是前端工程化的基石,学懂、会用他是很有必要的。
能看到这里真不容易,给自己鼓鼓掌吧。
参考资料
[1]Babel仓库:
https://github.com/babel/babel/tree/main/packages
[2]AST explorer:
https://astexplorer.net
/[3]core-js仓库:
https://github.com/zloirock/core-js/tree/master/packages
[4]Browserslist:
https://github.com/browserslist/browserslist
[5]Babel v7.4.0:
https://babeljs.io/docs/en/babel-polyfill#docsNav
[6]babel-plugin-syntax-decorators:
https://github.com/babel/babel/tree/main/packages/babel-plugin-syntax-decorators
[7]babel playground:
https://babeljs.io/repl#?browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=MYGwhgzhAECCAO9oG8C-Q&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=script&lineWrap=true&presets=env&prettier=false&targets=&version=7.13.7&externalPlugins=babel-plugin-transform-regenerator%406.26.0
很赞哦!(8821)