2023-08-15
前端工程化
00
请注意,本文编写于 310 天前,最后修改于 106 天前,其中某些信息可能已经过时。

目录

Babel是什么
配置文件
配置文件的种类
配置文件的优先级
plugin与preset的关系及异同
基本使用
preset-env的配置
polyfill
core-js 版本2与3的区别
配置示例
为什么类库和应用的babel配置不一样?
babel7主要变更
关于browserslist如何配置
配置示例
如何验证配置是否有效
Babel编译TypeScript
编译TS代码用babel还是tsc?
tsc与babel编译TS的异同
编写Babel插件

本文将系统梳理Babel相关知识。包括Babel的基本概念,配置文件种类,应用和类库的配置案例讲解,Babel7的主要变更,browserslist配置,编译TS代码适用tsc还是babel等内容。

Babel是什么

  • Babel是一个JavaScript编译器
  • Babel是一个工具链,主要用于将ECMAScript 2015+版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

注:ECMAScript是规格,JS是其规格的实现,不仅包含ECMAScript,还包含浏览器下的JS。如果想给浏览器下的JS打补丁要靠其他工具了(比如observe-webpack-plugin)。

配置文件

配置文件的种类

  • pkg.babel字段
  • 局部配置 .babelrc.json(或者.babelrc.js.cjs.mjs)文件
    • babel6 可以在.babelrc引用 .babelrc.js, babel7不允许
  • 全局配置 babel.config.json(或者.js.cjs.mjs)文件; 7.x 新增的

配置文件的优先级

  1. .babelrc.babelrc.json.babelrc.js 不能同时使用,但babel6中可以在.babelrc引入.babelrc.js
  2. 同样babel.config.jsonbabel.config.js 也不能同时使用
  3. .babelrc.jsbabel.config.js 可以共存,.babelrc会merge到babel.config.js的配置
  4. merge规则 merge({babel.config}, {babelrc}, {rullup/gulp/webpack中的配置})

plugin与preset的关系及异同

  • plugin babel的插件翻译某一块功能
  • preset babel插件集合的预设,包含某一部分的插件
  • plugins先于presets之前执行,plugins从前到后执行,presets从后往前执行

基本使用

Babel默认只转换新的JavaScript句法,而不转换API,比如IteratorGeneratorSetMapsProxyReflectSymbolPromise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。

preset-env的配置

useBuiltIns字段

  • false 不引用polyfill(默认值)
  • entry 整体导入补丁
  • usage 按需导入补丁

useBuiltInsentryusage 的时候,需要安装 core-js@2 或者 core-js@3

polyfill

  • babel version < 6 项目入口引入 babel-polyfill
  • >= 7 < 7.4 项目入口引入 @babel/polyfill
  • > 7.4 配置@babel/preset-env

注: polyfill 依赖 corejs

core-js 版本2与3的区别

  • 新项目肯定用core-js@3, 因为corejs@2已经被冻结好几年了,所有新功能仅添加到corejs@3,包括提案
  • corejs@3也删除了一些过时的功能,不再有非标准非提议功能
  • corejs@3 为浏览器错误/问题添加了许多修复程序。例如,已修复 Safari 12.0 Array.prototype.reverse 错误。

corejs组成

  • core-js,它定义了全局 polyfill。 (~500KB,40KB 压缩和压缩)
  • core-js-pure,提供polyfills而不污染全局环境。它相当于 core-js@2 中的 core-js/library。 (~440KB)
  • core-js-bundle:定义全局 polyfillcore-js 的捆绑版本。

ecma提案的4个阶段

  • Stage 0 - 稻草人(strawman): 只是一个想法,经过 TC39 成员提出即可。
  • Stage 1 - 提案(proposal): 初步尝试。
  • Stage 2 - 初稿(draft): 完成初步规范。
  • Stage 3 - 候选(candidate): 完成规范和浏览器初步实现。

配置示例

javascript
module.exports = { presets: [ [ "@babel/preset-env", { // 推荐使用 .browserslistrc文件,因为该文件已被生态系统中的许多工具所使用,例如postcss-preset-env,stylelint,eslint-plugin-compat等 // 写在这里(babe配置里),是为了方便演示 "targets": "> 0.25%, not dead", // 仅包括浏览器市场份额超过0.25%的用户所需的polyfill和代码转换(忽略没有安全更新的浏览器,如IE 10和BlackBerry) // "targets": { // 有这么多选项 chrome,opera,edge,firefox,safari,ie,ios,android,node,electron // "edge": "17", // "firefox": "60", // "chrome": "67", // "safari": "11.1", // }, // targets 如果未指定目标,则默认转换所有ECMAScript 2015+代码 // 还可以写在package.json文件里面 "browserslist": "> 0.25%, not dead" // useBuiltIns false-不引入polyfill(默认值) entry-入口引入(整体引入) usage-按需引入 "useBuiltIns": "usage", // "corejs": 3 // useBuiltIns为entry或usage的情况下需要配置这个选项 "corejs": { version: "3.24", proposals: true } // useBuiltIns为entry要在入口文件引入 `import "core-js"` // 如果corejs@3 @babel/preset-env还会优化,只导入针对目标浏览器所需要的全部补丁 } ], // 从 v7.0.0-beta.55 开始废弃了,需要用户自己配置plugins // 废弃原因 提案不稳定 理由是 https://segmentfault.com/a/1190000018358854 // "@babel/preset-stage-1" ], // babel7 提案句法 转换需要自己手动配置,可在 node_modules/@babel/preset-stage-0 查看一下 内容不多 plugins: [ "@babel/plugin-proposal-do-expressions", // 编译多个文件时,如果不想这些公共辅助函数在每个文件都注入一遍,就需要这个插件配合。 "@babel/plugin-transform-runtime" ] }

类库的配置

js
module.exports = { presets: ['@babel/preset-env'], plugins: [ [ '@babel/plugin-transform-runtime', { corejs: 3 // cnpm i -S @babel/runtime-corejs3 } ] ] };

为什么类库和应用的babel配置不一样?

babel中有个概念, es6+ 分为 句法(新语法)和 API 两个概念。
一般对于项目来说, API 的转换我们通过配置@babel/preset-envuseBuiltIns字段为usageentry
但是该方式不适用于类库,因为会造成污染。(类库的垫片与应用的垫片功能可能存在差异)。
打包类库,我们通常使用runtime方案(为es6 API创建一个沙盒环境)。
rumtime方案如下:

  • @babel/runtime 抽离公共辅助函数
    • 一些句法如async... class等转成es5需要一些辅助函数,该插件会将辅助函数注入到打包文件中。
  • @babel/runtime-corejs3 core-js是直接补全浏览器的es6+API,会带来环境污染问题,该包提供一种fuction引用的方式来避免es6+ API污染全局环境
  • @babel/plugin-transform-runtime 编译多个文件,如果不想这些公共辅助函数在每个文件都注入一遍,就需要这个插件配合。(应用也需要这个插件) 该包依赖上述两个包

参考资料: 一文搞懂 core-js@3、@babel/polyfill、@babel/runtime、@babel/runtime-corejs3 的作用与区别

注意: 因为babel并不处理模块化,所以构建后的文件polyfillruntime仍需要在特定的环境(node)下运行,想要在其他环境(浏览器)中运行还需要借助模块化工具(如webpack)

babel7主要变更

  1. 命名空间规范化,防止相关名称被抢注的问题
    1. babel7软件包 @babel/node @babel/core @babel/cli @babel/preset-env
    2. 对应babel6软件包babel-node babel-core babel-cli babel-preset-env
  2. polyfill变更
    1. babel6 babel-polyfill 对应 babel7的@babel/polyfill,但是babel从7.4.0开始,不推荐使用此软件包,而直接包括core-js
  3. 新增全局配置 babel.config.js babel.config.json
    1. 全局配置 babel.config.js 局部配置 .babelrc 用于monorepo项目
    2. babel.config.js可以定义全局配置的作用范围以及局部配置的开启关闭
  4. stage(提案)预设变更, 不在提供preset而是手动配置plugin
    1. babel6有 babel-preset-stage-x babel7废除了这些预设,需要手动引入plugin,预设语法并不多可以在node_modules/@babel/preset-stage-0看一下
    2. 提案plugin更名
      1. 统一更名为-proposal,如@babel/plugin-transform-function-bind现在@babel/plugin-proposal-function-bind
      2. 从包裹名称中删除年份 如@babel/plugin-transform-es2015-classes成了@babel/plugin-transform-classes
  5. 反应和流程预设的分离
javascript
{ - "presets": ["@babel/preset-react"] + "presets": ["@babel/preset-react", "@babel/preset-flow"] // parse & remove flow types + "presets": ["@babel/preset-react", "@babel/preset-typescript"] // parse & remove typescript types }

以上内容个人写了一些案例,如有需要请移步

关于browserslist如何配置

browserslist可以在构建工具(Webpack/babel/...)、.browserslist文件pkg.browserslist中配置

配置示例

javascript
// 1. 在构建工具如webpack中配置 // webpack.config.js module.exports = { module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, // use: 'babel-loader', // 走babel配置 use: { // babel配置写在这里也可以, 如果项目根目录下还有配置文件如babel.config.js会把这里的配置项merge到配置文件里面 loader: 'babel-loader', options: { presets: [['@babel/parset-env', {target: '这里配置'}]], } }, } ] }, } // 2. 在babel中配置如 babal.config.js module.exports = { presets: [ ['@babel/env', { "useBuiltIns": "usage", "corejs": 3, }] ] } // 3. browserslist配置 // 如果 前一步没有配置parset-env的target字段, // 再会寻找 .browserlists文件 或pkg.browserlists字段 /* pkg.browserlists 移动端配置示例 "browserslist": [ "Android >= 4.4", "ios >= 10", "> 0.5%" ] android 4.4以上browserlist配置参考这篇文章 https://www.yuque.com/guojw/fe-project/rg6nsk#Rd7ho */

如何验证配置是否有效

  • npx browserlists通过控制变量法,检测是否生效
  • postcss同样支持browserlists,(如postcss-presets-env,想要看到flex前缀,要调整到android 4.0)

查看各浏览器及各版本市场份额 点这里

Babel编译TypeScript

编译TS代码用babel还是tsc?

tsc的编译流程

image.png

babel的编译流程

image.png

babel对比tsc 的编译流程,差别并不大

  • Parser 对应 tsc 的 ScannerParser,都是做词法分析和语法分析,只不过 babel 没有细分。
  • Transform 阶段做语义分析和代码转换,对应 tsc 的 BinderTransformer。只不过 babel 不会做类型检查,没有 Checker
  • Generator 做目标代码和 sourcemap 的生成,对应 tscEmitter。只不过因为没有类型信息,不会生成 d.ts

tsc与babel编译TS的异同

  1. babel的劣势(@babel/preset-typescript
    1. 无法做到类型检查也就是生成 .d.ts文件
      1. 解决方案: 单独使用tsc生成声明文件
        npx tsc --declaration -p ./ -t esnext --emitDeclarationOnly --outDir types
    2. 部分语法不支持(主要是一些 TypeScript 不推荐的旧的语法,因为类型检查太重要了,所以可以忽略这一点) babel不支持的ts语法
  2. babel的优势
    1. 灵活性:babel能根据目标浏览器转译指定语法;而ts只能配置指定的ecma版本
    2. polyfill (es6API) Babel 能够根据目标环境自动添加 polyfillTS 编译器则不处理API需要手动引入corejsruntime (致命缺点)
    3. 插件系统:babel有插件机制,有丰富的插件生态,TS编译器没有插件系统
    4. ts只支持最新的es规范,和部分提案,想要支持新语法就要升级ts版本;babel通过插件支持es句法 API 及 提案

综上: babel支持ts是最好的选择,@babel/preset-typescript也是ts开发团队的人实现的

参考资料:

编写Babel插件

请移步这里 《抽象语法树AST》

本文作者:郭敬文

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!