从我开始接触前端工程化到现在工作一年多,babel
这个词就时不时出现在各种代码里,但是见到了并不意味着就知道了,尤其是各种babel
的包结合起来配置,头都晕了,所以这次写个文章加深下自己的印象。
Babel · The compiler for next generation JavaScriptbabeljs.io/
开头直接简洁明了,就是一个javascript
的解析器。主要就是将不兼容的ES6
以上的api
、语法等转换为大部分浏览器都兼容的es5
。
主要能用于一下地方
api
jsx
,帮你转换为js
说的再多也不如直接实战
我们先创建一个文件夹和一个index.js
文件,然后初始下环境
然后在我们的index.js
文件中随便写点es6
的东西,比如箭头函数或者Array.reduce
之类的api
。
然后在终端安装babel
的核心包@babel/core、@babel/preset-env
以及能让这两个包跑起来的命令工具@babel/cli
安装完包之后直接执行运行命令
然而你发现lib
目录下生成的index.js
里的代码依旧没改变,还是es6
的东西,为什么没有转换呢?因为我们并没有做配置,命令读不到配置自然就按原来的输出了。
在介绍通过配置文件配置之前,我们先来看下通过参数传入,我们先安装@babel/plugin-transform-arrow-functions
,这个包一看就知道是用来转换箭头函数的。
那么我们要怎么用这个plugin
呢?@babel/cli
中提供了--plugins
的option
这里就转换完成了。但每次这样传参就麻烦,所以官方又提供了一种方式,通过配置文件。
让我们来创建一个babel.config.js
的配置文件,当然建议用.json
(>=7.8.0)的,js
是旧版中的。
箭头函数正常转换了。但你应该注意到了reduce
方法和...
解构都没有转换为es5
。
让我们改下配置
我们再重新跑一下指令。
箭头函数是转换了,...
结构也通过apply
的方式转换了,但是这个reduce
并没有变。
在低版本是可以引入@babel/polyfill
处理的,但是在7.4.0
之后就废弃迁移到core-js
包中了。
另外为什么没有自动转换呢?因为现在默认是不需要的,需要我们做其他配置。
首先,我们先安装下core-js
,这个包中的代码都是参与打包的,所以不能放dev
中。
然后我们改下babel.config.js
文件中的配置
useBuiltIns
: 默认是false
,不使用任何的polyfill
。不为false
的时候会直接从core-js
中引入polyfill
,其他无相关的将不会被引入。 其中usage
会分析每一个module
中需要引入的polyfill
然后自动引入,是按需加载,同时代码是放在module
内部的,不会污染全局,但当所有的module
合并后自然会出现很多重复的导入(指编译后的代码,不影响实际效果,因为同一个polyfill
只会被导入一次)。而entry
需要开发者自己去全局入口引入core-js
,这样全局就只有一份polyfill
,但这份polyfill
是全量的,会污染全局,不过你会有重复的导入问题。怎么用还是得看具体的场景,并不是说usage
就会比较好。corejs
: 你安装的core-js
包版本,其中有2
和3
的版本,其中2
的版本比3
少了一些实例自身的属性。另外,我们并不需要补充regenarator-runtime
这个包,因为这个包如果没有配置会自动给我们加上。
让我们重新跑下指令
result
这下就正常转换了。并且只引入了我们需要的。
我们来看下useBuiltIns
的usage
和entry
的不同。
usage
首先新建src
文件夹,然后新增a.js、b.js、c.js
以及index.js
四个文件,其中abc
三个文件导出方法除了名字其它都相同,index.js
导入这三个文件导出的方法。
然后改下我们package.json
中build
的指令
然后终端直接执行npm run build
可以看到编译后的文件中两个polyfill
被重复引用。
entry
首先将babel.config.js
中的useBuildIns
改为entry
,然后在我们的index.js
中将core-js
包导入,然后重新执行我们的npm run build
指令。
可以看到所有的polyfill
都被引入了。
这里越过了@babel/polyfill
这个包的使用,因为目前已经是不推荐了,但如果你接手了一个老项目,那还是得看一下的,这里放下官网链接
https://babeljs.io/docs/en/usage#polyfillbabeljs.io/docs/en/usage#polyfill
配置文件支持多种
babel.onfig.js/json
这两种都是一样的,推荐json
格式,上边已经介绍过了这种方式,就不多说了。作用范围是整个项目。.babelrc.json
,作用范围局部,当前文件夹下,执行转换时会先往上找最近的一个.babelrc.json
文件直到根目录,然后将这个.babelrc.json
和全局的合并之后再获取配置信息。用法如下:package.json
文件中。已经被内置了的就不说了。
@babel/preset-react
,看名字就知道是用于react
的配置,我们先安装然后将这个配置加到babel.config.js
文件中
接着创建react
的文件夹,新建index.jsx
,随便写点react
的代码
然后package.json
中新增script
然后直接执行指令npm run build-react
@babel/preset-typescript
,看名字也知道这是个用来识别转换typescript
的配置然后新增一个ts
的文件夹,创建一个index.ts
,随便搞点东西
接着在package.json
中新增指令,需要注意ts
文件需要提供extensions
的option
最后在babel.config.js
中新增一个预设
然后运行指令npm run build-ts
babel
插件如果大家用过webpack
的插件,应该能发现都有同一个格式,需要定义class.apply
才能被调用。
而babel
也有自己的格式。
我们先创建一个plugins
文件夹,新建log.js
文件
当找到name
是res
的变量时,打印这个节点。
然后我们将它引入到babel.config.js
中。
然后我们执行npm run build
,目标既是我们的index.js
文件
这里面标记了行列的位置,注释,起始结束位置等。
正好就是第一行第六列到第九列。
通过这个例子可以看出这个格式
这里就只展示这一个小demo
,实际上一个plugin
能做的事情很多,可以监听的东西也很多。因为传入visitor
中的是被core
编译转换后的ast
(抽象语法树)
@babel/cli
你是否觉得这个指令的执行者./node_modules/.bin/babel
很奇怪?实际上node_modules/.bin
中存放的是所有第三方包里的命令执行文件入口,如果这个包配合commander
和全局安装,就能做到类似npm
这样的全局命令执行工具。可以看下我之前的文章。
https://zhuanlan.zhihu.com/p/569233377
而这个.bin
中的命令来源是包自身带有的bin
文件夹中的,比如这个@babel/cli
中的bin
文件夹。
要怎么分辨呢?直接去看package.json
中如何配置的即可。
如果你嫌弃./node_modules...
这种执行太冗长,你可以在package.json
中新增一个script
为什么这样会生效呢?因为@bael/cli
这个包配置了这个npm
指令即上面提到的package.json
文件中的bin.babel
。
我们跟着进入babel.js
中,发现是导入cli/lib/babel
这个包,入口自然是index.js
文件
这段代码很简单,就是将命令行带上的参数传入options.js
中,转换后判断是否有配置--out-dir
,如果有则按照传入的来,否则使用默认输出路径。
简单的说下options.js
中做了什么。 就不看代码了,毕竟这不是源码分析。。。
其实这里面也用了commander
,装载了一堆的option
,唯独没有配置指令的名字。。。
抛去commander
的配置,还剩200
多行代码。
主要做了以下几件事。
return
然后参数传入dir.js
中。
这个文件中代码写的很绕,一个套一个的,最终找到的compile
方法居然在utils
里。。。。
这里面主要做了以下的俩件事
ignore
的文件),调用compile
方法,引入core
包,将这些文件里的源码转换成我们想要的兼容低版本的代码。这个包相信大家也差不多猜到了,在学的过程中需要通过它来做兼容判断。
先来看下官方对它的描述
简单的说就是可以帮你自动去做兼容等,不需要你去做额外的处理比如引入插件等。当然,有些东西需要额外配置才能生效,比如polyfill
,需要配置useBuiltIns
以及corejs
。
同样不分析源码,简单的说先是判断当前环境,比如node
,然后通过options
判断加载不同plugin
。
来看下常用的option
targets
:你希望支持的哪些浏览器的哪些版本,会根据你的配置,往你的bundle
中加点兼容性东西,让你的代码可以正常运行在你想要的浏览器中。 当然,如果你配置了browserlist
,也会根据browserlist
的来做判断。默认不配置,会根据默认情况执行,尽量帮你兼容所有浏览器的低版本。include
:和下面的exclude
同类型的作用,用于让某些plugin
一定会被调用。exclude
:让某些plugin
一定不会被调用。useBuiltIns
:用来处理polyfill
的,默认false
不加载polyfill
。其它不多说了,配置
这一块里有对usage
和entry
的分析。corejs
:只有配置了useBuiltIns: usage/entry
这个字段才会生效。默认是版本2
,2
不可以传入具体的版本 ,3
则可以。用于加载core-js
的包,而core-js
需要自行安装并确保包版本和你这配置里的是一致的(指的是core-js2
只能配置2
,不能配置3
,core-js3
同样道理)。browserlistEnv
:基于browserlist
的配置。看名字就知道这个包的重要性,是用来处理源码转换代码的核心包。
我们直接来实战
我们先创建一个transform
的文件夹,新建index.js
文件。
然后我们在index.js
中将@babel/core
包中的transform
方法导入。
我们执行下这个文件node transform/index.js
可以看到我们的箭头函数确实被转换成function
的形式了。
这里依旧不会去分析源码,只需要知道这里面有一个解析器@babel/parser
,将代码转换成object
,经过@babel/traverse
加工处理后,再调用@babel/generator
从object
转换为最终的代码。
上面说到了,将你的代码"字符串·"转换成抽象语法树(AST
),至于怎么转的,这里就不说了,因为这块逻辑是很复杂的,简单的说就是将你的code
进行遍历,将每个字符都过一遍,当然这不是简单的遍历。它里面内置了几十个javscript
特殊字段(比如const、let、var
这些,尤其是空格),根据这些字段,将“不稳定”的状态 转变为稳定状态(比如你定义一个变量,最开始是不确定你这个是不是变量的,但是遍历过前后之后再配上这些特殊字段判断就能确定了),最终将你的code string
变成一颗”树“object
。AST
的应用是非常非常常见的,比如你的vue
的虚拟dom
就是一种AST
,还有你现在正在用的IDE
,都是基于AST
的。
我们来调用下这个包
又或者你可以将上面@babel/core
中的transform
传入的参数第二个插入一个options
,里面传入ast: true
,也是一样的。
这个core
包中也提到了,主要是将你的ast
转换为string
。当然,这里拿到的ast
不是原版从parser
中拿到的ast
,是经过@babel/traverse
执行各种plugin
加工过后的。
我们直接来调用下这个包
这里的code
里的箭头函数居然没有转换成function
的形式,这是为什么呢?因为我们没有配置。。。。。之前@babel/core
能正常转换是因为里面做了读取配置的处理,而generate/parser
都是功能单一的包,不可能会做读取的处理。 这也符合官方的描述 --“默认不处理代码,处理都交由插件“。那么要怎么变成我们想要的呢?
你应该注意到了generator
处理的是ast
,所以如果我们改动ast
的话到时它输出也就是我们改动了的,官方包里也是这么做的。
而改动我们需要用到另一个包@babel/traverse
来帮助我们将箭头函数变为function
的形式。
这个包是用来更新ast
的。
接着我们上面的代码
然后终端执行下node transform/index.js
转换成功!
提供了babel
内置的类型,比如上面提到过的Identifier
和ArrowFunctionExpression
。
这里就不多说了,感兴趣的大佬可自行点下【18】去官网看下。
看名字就知道是和模板有关的。
我们简单的搞下即可,因为重要的包前面几个已经讲完。
官方提供了两种替换变量的方式
%%xxx%%
的方式XXX
的方式还记得上面提到过的polyfill
和配置不?是通过注入代码参与打包来实现转换的,而这些一般叫做runtime
。
babel
也是需要注入一些runtime
来帮助代码进行兼容的。
我们先在transform
文件夹中创建一个文件test-runtime.js
然后package.json
新增script
接着npm run build-runtime
输出的buildRuntime.js
中多了一堆意义不明的玩意儿。
而这些东西就是async await
的辅助函数。
让我们在创建一个test-runtime2.js
,然后让这个文件也参与编译
发现这些辅助函数每次都会被引入,这也就意味着存在大量的重复代码。
为了处理这个问题,官方就将这些个runtime
都抽出来放到了这个@babel/runtime
包中。
那么要这么做才能处理掉这些重复代码呢?
我们直接在入口引入对应的包即可。
但这样每次都得手动去引入,很麻烦。有没有办法能做到和core-js
一样自动引入?
有的,官方又提供了一个包@babel/plugin-transform-runtime
,这个包能自动引入。
我们先安装
然后在我们的babel.config.js
中配置下
这回就自动引入啦
这个包提供了自定义行列位置的log
,就不多说了,感兴趣的大佬可以点击【19】看下。
随便搞下。
那么babel
的基础就学完了,如果觉得对你有用麻烦点个赞~谢谢!
发布于 2022-10-24 18:17