前端工程化还包括bable
Babel
Babel主要做的就是这几点:
- 语法转换:将新的语法转换成兼容性更好的旧语法。
- 特性垫片:通过
Polyfill
实现目标环境中缺少的特性,将环境的特性差异垫平,说白了还是做兼容。
比如下面这段代码
1 | // Babel Input: ES2015 arrow function |
babel的使用场景主要分为两种:命令行和构建工具。
babel为命令行提供了@babel/cli
工具,支持在命令行直接执行babel命令。
1 | npm install --save-dev @babel/core @babel/cli |
安装好在项目中执行babel
命令就可以进行转换了。
1 | babel src --out-dir lib # 将src下的文件处理后输出到lib |
在构建工具中使用是我们使用babel更常用的方式。
以webpack为例,babel提供了一个loader:babel-loader
,使用这个loader我们就能很轻松实现代码转换。
1 | module: { |
bable的运行方式:
Babel的运行过程分为三个阶段:解析 — 转换 — 生成
- 解析:解析代码,并生成AST抽象语法树
- 转换:将上一步的AST转换成目标环境支持的AST
- 生成:根据转换后的AST生成目标代码
bable配置
babel7支持多种方式来进行配置:
babel.config.json
(项目范围).babelrc.json
(相对文件)package.json
中添加babel字段
除了json文件之外,babel还支持其他格式的配置文件,比如 .js
,.cjs
(Commonjs)和.mjs
(ESModule),它们相对于json格式更加灵活,可以在配置中进行编程处理,但是会使配置无法静态分析,失去可缓存性。
1 | // babel.config.js |
babel虽然有多种方式来写配置文件,但是配置的内容都是类似的,主要包含plugins
和presets
两部分,不过这里需要注意几点:
配置格式:plugins和presets配置项均为数组,如果每项不需要配置的话,直接放入数组(值为项的字符串名称);如果要配置的话,则需要把格式改成数组,该数组的第一个元素是该项的字符串名字,第二个元素是该项的配置对象。这里填写的plugins和presets,babel会自动检查是否已被安装在node_modules下面,当然,也可以使用相对路径来使用本地文件。
1
2
3
4
5
6
7
8
9
10"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
],
"@babel/plugin-transform-runtime"
]执行顺序:
plugins
会从前到后执行,presets
会从后向前执行,且plugins
会比presets
先执行presets
逆序执行的目的是为了保证向后兼容,我们在配置的时候按照规范的时间顺序列出即可,如['es2015', 'stage-1']
,这样就会先使用stage-1
,否则就会出现错误。短名称:在配置plugin和preset名字的时候,可以省略名字中的
babel-plugin-
和preset-
字样,仅使用插件的名字就可以了。1
2
3
4
5
6
7
8
9
10{
"plugins": [
"myPlugin", // 完整名字:"babel-plugin-myPlugin"
"@org/myPlugin" // 完整名字:"@org/babel-plugin-myPlugin"
],
"presets":[
"myPreset", // 完整名字:"babel-preset-myPreset"
"@babel/myPreset" // 完整名字:@babel/preset-myPreset
]
}
plugin
babel的转换完全依靠插件,babel拥有的能力完全取决于给它配置了哪些插件。
babel的插件分为两种:
语法插件:赋予babel解析特定语法的能力,可以理解一个为babel服务的翻译官,仅做翻译解析工作,工作在
AST
转换阶段。转译插件:转译插件即把特定的语法转换成指定标准的语法。
比如将箭头函数
(x) => x
转换成function (x) {return x}
,只需要配置箭头函数转译插件即可。
插件的使用大概分为两步:
- 将插件添加到配置文件的
plugins
属性中,可根据插件文档做相应配置。 - 安装插件到项目中,使用npm或者yarn均可
每个插件的功能是单一的,所以一般转换需求要使用的插件数量比较多,这时候可以考虑presets
编写插件
和webpack一样,bable可以使用自定义插件实现功能
1 | module.exports = function() { |
preset
在实际的开发中,我们如果用ES6来进行开发的话,那需要转换的语法是非常多的,难道要一个一个去配置吗?还要不要配陪妹子了?
这个时候,就该presets登场了。presets可以理解为一组插件的集合,就像你去德克士直接点一个全家桶,而不必一个一个给服务员说要鸡腿、鸡翅、薯条…(如果想和服务员小姐姐多待一会,也不是不可以)
配置文件:通过配置文件的targets属性指定目标环境
1
2
3
4
5
6
7
8
9
10{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "safari >= 7"],
"node": "12.10"
}
}]
]
}browserslistrc:如果是浏览器或者
Electron
,官方推荐使用.browserslistrc文件来指定目标环境(可以和autoprefixer、stylelint等其他工具共享配置)。
不过如果在配置文件中设置了
targets
或ignoreBrowserslistConfig
,.browserslistrc
中配置的内容将不会生效。
presets中的其他配置:
modules: 用于指定转换后的模块规范,可设置为
"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false
,默认为"auto"
。useBuiltIns
:此选项配置如何@babel/preset-env
处理polyfill,在后面介绍polyfill我们会详细介绍。corejs
:用于指定corejs的版本,仅当使用了useBuiltIns: usage
或者useBuiltIns: entry
此配置才有效,使用时一定要配置正确的版本,否则会报错。当前默认的版本是
2.0
,不过建议使用3.0
版本,因为2.0已经不会再添加新的特性了,如果使用了一些较新的语法,将会无法转换。
@babel/preset-env
这个是我们最常用的,也是官方现在推荐的preset,它是一个智能预设,我们无需再关心特定环境下所需的转换插件(只需要指定目标环境),就可以使用最新的语法。除了能够减少使用难度,还能够使转换出来的代码体积更小。
preset-env会根据配置的目标环境中缺少的功能,选择对应的插件进行必要的转换和polyfill
,而目标环境支持的特性就不会转换,而不是无脑的一把梭哈。
polyfill
前面我们的配置其实是不完美的,转换出来的代码直接在浏览器运行是可能会出问题的。这是因为babel默认只转换了语法,而对于一些新的API是并没有处理的,如Generator、Set、Maps、Proxy、Promise 等全局对象及其方法都不会转码,如果我们在代码中使用到了就会报错。
此时,我们就需要做Polyfill,也就是所谓的’垫片’,作用就是把浏览器的差异垫平,人话就是通过模拟为这些浏览器补全缺失的全局对象及API。
常用插件
@babel/plugin-transform-runtime
是一个可重复使用Babel注入的帮助程序的插件,避免重复注入,以节省代码大小。通常还要搭配@babel/runtime
一起使用。
安装
1 | npm install --save-dev @babel/plugin-transform-runtime |
配置
1 | // babel.config.json |
Babel-plugin-import
动态引入插件,从而不必全局引入antd、lodash等
安装
1 | npm install babel-plugin-import --save-dev |
在babelrc中配置
1 | 'plugins': [ |
SWC
swc是一个类似babel的JavaScript/TypeScript编译器,但是由于swc使用Rust实现,所以具备比babel出色的性能。
webpack与bable的性能瓶颈都在于JS语言,出现了go实现的esbuild与Rus实现的swc等工具,esbuild想要开辟一个构建工具性能的新时代,创建一个易用的现代打包器,swc的目标则是是替代babel,在官方文档的Comparison with babel这个单元,我们可以看到swc团队将相当一部分bable的插件重写了以增强swc的能力。
对比 babel,swc 有至少 10 倍以上的性能优势
同时swc也提供了一个构建打包工具spack,这个工具并不是一个全面的构建工具,但是我们可以通过这个工具简单的体验一下swc的能力
我们在简单的工具库编写时,可以使用swc提供的spack命令来快速打包出commonjs, es6, amd, umd
安装/使用
安装
1 | npm i -D @swc/core @swc/cli |
在项目的根目录创建一个 .swcrc
文件,配置信息如下
1 | [ |
配置spack
1 | const { config } = require("@swc/core/spack"); |
在package.json中增加
1 | "scripts": { |
与webpack一起使用
SWC 也提供了 Webpack Loder,可以和 Webpack 一起使用
安装 @swc/core
和 swc-loader
1 | cnpm install --save-dev @swc/core swc-loader |
安装完成后在 Webpack 的配置文件中添加 SWC 的 Loder 配置
1 | const path = require('path'); |
SWC 的配置还是和上面一样的使用 .swcrc
的配置
运行打包后 swc-loder 就会调用 swc-core 转码
SWC 和 Webpack 一起使用也还是不能转换 ES6 中新增的 Promise
之类的功能,所以还需要使用 polyfill 或 core-js
因为 core-js 可以按需引入,而 polyfill 需要配合 Babel 才能实现按需引入,所以这里就使用 core-js
1 | cnpm install --save-dev core-js |
安装完成后在 main.js
中引入需要的库,比如我用到了 Promise
,我就需要引入 core-js 的 Promise
转换库
1 | import 'core-js/features/promise'; |
core-js 的转换模块就在 core-js/features
目录中
打包完成后就可以在 IE 浏览器中使用 Promise
之类的功能了
Rome
Rome是 Babel 作者做的基于 Nodejs 的前端基建全家桶,包含但不限于 Babel, ESLint, webpack, Prettier, Jest。
rome
是个全家桶 API,所以你只需要 yarn add rome
就完成了所有环境准备工作。
rome bundle
打包项目。rome compile
编译单个文件。rome develop
调试项目。rome parse
解析文件抽象语法树。rome analyzeDependencies
分析依赖。
Rome 还将文件格式化与 Lint 合并为了 rome check
命令,并提供了友好 UI 终端提示。