一起来看看 Babel 做了什么

前言

在进行 Webpack 或者 Rollup 配置的时候,难免会进行 Babel 的相关配置,因为代码中往往会使用比较新的 JS 语言的特性,然而可能运行环境并不支持这些特性,这个时候就需要 Babel 去帮助实现转换。

Babel 的作用


第一眼看到 Babel 时,难免会不知道如何下手去了解,需要花一段时间去理清各个新名词的意思和作用,本人自己一开始也没少看资料,但是一旦理解 Babel 主要的作用,就知道工具链上的各个名词就是为了实现这些主要功能:

  • 语法转换

  • 对目标环境进行特性补充 polyfill

  • 代码替换

这里最常用可能就是语法转换了,开发过程中时常会使用 ES 2015+ 的语法,这个时候就需要使用 babel 来进行语法的转换,转换到运行环境支持的语法特性,另外还有一些运行环境不支持的特性,例如 PromiseArray.prototype.includes 等,也需要使用 polyfill 进行补充。

每一个上述的功能都牵涉到几个 babel 的工具包,下面就一层层地去介绍这些工具包:

命令行和核心包

babel 进行编译解析,是存在两种方式的:

1. 一种是通过编程的方式,也就是@babel/core,在你的 js 文件中引入 @babel/core,然后就可以调用使用其中的方法进行编译解析。

import { transform } from '@babel/core';  
  
babel.transform("code();", options, function(err, result) {  
  console.log(result.code);  
});

除了 babel.transform 方法,@bable/core 提供很多其他 transform 的 API 以供使用。

2. 另一种是通过命令行进行触发,babel 提供 @babel/cli 工具包,开发者可以使用命令行的方式对代码进行转换,需要注意的是 @bable/cli 需要同时依赖 @babel/core 提供核心功能,cli 提供多种参数以达到开发想要的编译效果:

babel script.js --out-file script-compiled.js

配置文件

如果了解编译原理的话,都知道编译的过程一般都经过词法分析、语法解析、生成中间代码、生成目标代码,而 babel 也类似,经过三个主要的流程解析、转换、生成。

  • 解析阶段主要进行词法和语法解析,生成 AST 语法树,主要由 babyIon 完成;

  • 转换阶段将 AST 语法树转换为目标的 AST 语法树,主要由 @babel/traverse 完成;

  • 生成阶段将目标语法树生成最终代码,主要由 @babel/generator 完成;

这里既然需要进行转换,那就必须知道如何转换,遵循怎样的转换规则,这里就需要配置文件与定义这些规则了。

babel 的配置文件中包含 presets 和 plugins 两个配置项:

{  
  "presets": ["@babel/preset-env"]  
  "plugins": ["@babel/plugin-transform-runtime"]  
}

在语法解析时,我们需要知道一些高级的语法应该怎么去转换,这个时候,就可以在 plugins 配置项中添加规则去告诉 babel 应当怎么去转,例如,我们需要转换箭头函数时:

const arrowFunc = () => {  
  console.log('hello');  
}

就需要在 .babelrc 中这么配置:

{  
  "plugins": ["@babel/plugin-transform-arrow-functions"]  
}

@babel/plugin-transform-arrow-functions 中实现了转换箭头函数的方法,这样 babel 就能够转换了。当有新增的语法需要解析的时候,在 plugins 中继续添加规则即可。

实际上,plugins 分为语法插件(syntax plugin)和转换插件(transform plugin),这两种插件的差别在于语法插件用于识别语法,但不包含转换语法,语法插件一般以 @babel/plugin-syntax- 开头;而转换插件可以对语法进行转换,一般以 @babel/plugin-transform- 开头。

随着代码中使用的 ES2015+ 语法越来越多,plugins 中的规则也将会越来越多,这个时候 presets 就起作用了。

预设(presets)帮助我们简化 plugins 配置的数量,例如比较经典的 @babel/preset-env,就能帮助将语法转换到目标环境能支持的语法:

{  
  "presets": ["@babel/presets-env"]  
}

每一个 presets 都支持各自的配置,例如 @babel/preset-env 需要指定目标环境时,可以设置 targets 参数:

{  
  "presets": [  
    ["@babel/presets-env", {  
      targets: {  
        browsers: ["iOS >= 7", "Android >= 4"]  
      }  
    }]  
  ]  
}`

但是有时候,会发现 @babel/preset-env 可以帮助我们实现高级语法的转换,但是有一些新的内置函数 Promise,或者实例方法 Array.prototype.includes 无法进行转换,这个时候就需要使用 @babel/polyfill 来帮助填补这部分的缺陷了。

执行顺序

了解了 presets 和 plugins 的具体功能以后,那它们之间的执行顺序是怎样的呢?

这里就直接说结论:

  • plugins 会在 presets 前执行;

  • plugIns 间的执行顺序是从靠前声明的往后执行;

  • presets 间的执行则是从靠后声明的往前执行

Polyfill

介绍 @babel/polyfill 前,先从最简单的场景开始:

当代码运行的环境缺少类似 Promise、Array.from 时,这个时候在代码头部中引入 @babel/polyfill,可以帮助我们填补这部分的缺陷。

import '@babel/polyfill';  
  
const pm = function() {  
  return new Promise(function(resolve) {  
    resolve();  
  });  
}

但是如上述方式引入 @babel/polyfill,不仅会污染全局对象,因为其会修改原型方法,而且它的体积过大,无法做到按需引入。因此实际应用中不太建议这种方式。

按需引入

@babel/preset-env 提供 useBuiltIns 配置,当设置为 usage 的时候,就会按需添加代码中需要的 Polyfill,另外还需要配置 corejs 的版本,一般可以设置为 23,但是这里建议选择 3,因为 2 已经不会再添加新特性。

{  
  "presets": [  
    ["@babel/presets-env", {  
      useBuiltIns: 'usage',  
      corejs: 3  
    }]  
  ]  
}

有了按需添加,这样打包后整体的体积就会小很多。

这里需要补充一点的是,@babel/polyfill 包括 core-js 模块和一个自定义的 regenerator-runtime 模块,这两个模块都可以在 @babel/polyfill的package.json 文件的 dependencies 中找到。

regenerator-runtime

这里需要介绍一下 regenerator-runtime,当代码中使用到 async/await 语法的时候,@babel/preset-env 会帮助代码转换为一个 regeneratorRuntime 的函数,但是转换后的代码仅仅存在这个函数的调用,而这个函数具体的实现,在没有声明 useBuiltIns: 'usage' 的情况下,是不会引入的!

很多时候,我们看到 regeneratorRuntime is undefined 的报错,也是因为这个 regeneratorRuntime 函数没有被引入而导致的。

@babel/runtime

@babel/plugin-transform-runtime

文章介绍到这里,我们可以看到 @babel/preset-env@babel/polyfill 的配合,似乎已经解决绝大部分的转换和运行环境的问题,但是不是到这里就结束呢?显然答案是否定的。

这里就介绍一下 @babel/runtime@babel/plugin-transform-runtime 进一步能优化些什么?

事实上,babel 会生成很多小的辅助函数(helpers)去实现类似 _createClass 的功能,而默认情况下,每个文件都会注入这些辅助函数,这样就导致多个文件都会有重复的辅助函数,这不利于体积的优化,所以 @babel/plugin-transform-runtime 将会转换这些辅助函数,使其都引用 babel/runtime 里面的辅助函数:

{  
  "presets": [  
    ["@babel/presets-env"]  
  ],  
  "plugins": [  
    ['@babel/plugin-transform-runtime']  
  ]  
}

使用上述配置进行代码转换,会发现类似 _createClass 的辅助函数都会直接引用 @babel/runtime/helpers/createClass 中的函数。

然而 @babel/plugin-transform-runtime 的功能不仅仅如此,事实上它可以做如下事情:

  • core-js aliasing:这类似于 @babel/polyfill

  • helper aliasing;

  • regenerator aliasing;

前面我们介绍 @babel/polyfill 的时候,还提到了它的一个缺点:污染全局方法或者函数,core-js aliasing 可以帮助解决这个问题,类似引入辅助函数的方式,从 @babel/runtime-corejs3 包中按需引入函数,实现这个优化,只需要如下配置,并且安装 @babel/runtime-corejs3 的依赖包:

{  
  "presets": [  
    ["@babel/presets-env"]  
  ],  
  "plugins": [  
    ['@babel/plugin-transform-runtime', {  
      corejs: 3  
    }]  
  ]  
}

babel-loader

rollup-plugin-babel

这两个包分别是 WebpackRollup 两个打包工具上与 babel 相关的 loaderplugin,这里就不多介绍了,大家可以看具体文档,内部实现其实也是使用了 babel 工具链上的包。


结尾

至此,有关的 Babel 工具链上一些概念和包已经介绍得差不多了,内容主要是从 Babel 官网翻译以及自己理解后总结出来的。如有错漏,欢迎指正和讨论哈!

最后

从0到1构建完整的数据结构与算法体系!

在这里,瓶子君不仅介绍算法,还将算法与前端各个领域进行结合,包括浏览器、HTTP、V8、React、Vue源码等。

在这里,你可以每天学习一道大厂算法题(阿里、腾讯、百度、字节等等)或 leetcode,瓶子君都会在第二天解答哟!

往期算法系列文章:

快来加入我们吧,一起进阶拿offer,扫码回复「算法」自动加入

》》面试官都在用的题库,点击学习《《

“在看转发”是最大的支持

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值