image.png

babel是什么?

一个 JavaScript 编译器,可将 codeA 转换成 codeB。比如通 过 @babel/preset-react,将jsx转换为Vdom的书写形式

  1. // jsx
  2. <div>babel</div>
  3. // 编译后
  4. React.createElement("div", null, "babel");

核心工具

既然是转换代码,我们就需要一些工具,下面介绍下babel工具包

@babel/core

提供babel的核心功能,可将code转化为ast,并生成代码

  1. const babel = require("@babel/core");
  2. babel.transform("code", optionsObject);

@babel/cli

babel 命令工具

@babel/polyfill

对一些低版本浏览器进行api的polyfill

  1. Promise.resolve().finally();
  2. // 转换后
  3. "use strict";
  4. require("core-js/modules/es.object.to-string.js");
  5. require("core-js/modules/es.promise.js");
  6. require("core-js/modules/es.promise.finally.js");
  7. Promise.resolve()["finally"]();

注意:@babel/polyfill 合拼到了 @babel/cli中,所以不需要单独安装

@babel/plugin-transform-runtime

在代码 polyfill 时,可引入 core.js的依赖进行降级

配置文件

babel.config.json 可配置 babel 转换选项,也可设置成 .babelrc 与 babel.config.js

  1. {
  2. "presets": [
  3. [
  4. "@babel/env",
  5. // 配置编译环境,如typescript,flow
  6. {
  7. // @babel/plugin-transform-runtime + core.js 相似
  8. "useBuiltIns": "usage",
  9. "corejs": "3.6.5"
  10. }
  11. ]
  12. ],
  13. "plugins": [// babel插件]
  14. }

Preset vs Plugin

Preset

翻译为预设,可根据开发环境配置,在config中的preset中配置

除了正常环境意外,还有功能的stage过程,主要是实验性的一些功能,未审批通过:
TC39分类的建议分为以下几个阶段:

  • 阶段0-Strawman:一个想法,可能是Babel插件。
  • 第1阶段-提案:有点搞头。
  • 第2阶段-草稿:雏形。
  • 第3阶段-候选:完整的规范并开始在浏览器试用。
  • 第4阶段-已完成:将添加到下一个版本中。

    Plugin

    比preset更加丰富
    如果我们使用过 mob 6 一下版本,肯定会用到装饰器

    1. {
    2. "plugins": ["transform-decorators-legacy", "transform-class-properties"]
    3. }

    我们需要 transform-decorators-legacytransform-class-properties 来编译装饰器与class语法
    所以 Plugins 更像是补充 Preset 缺失的功能,更加轻量灵活

    工具包

    @babel/parser

    code 转 AST

    @babel/generator

    AST 转 code

    @babel/traverse

    遍历 AST

    @babel/types

    用来验证、构建和修改 AST 节点

    @babel/register

    用来通过绑定 node.js 的 require 自动使用 babel 编译文件

    @babel/template

    用于从字符串表示的代码构建 AST 节点,简化了使用@babel/types 构建节点的过程。

    @babel/helpers

    用于插件中的@babel/template 函数

    运行过程

  • 源码解析阶段:@babel/parser

  • 转换阶段:在这个阶段所有插件使用 @babel/traverse 遍历 AST,遍历过程中可以对节点进行一些自定义操作
  • 目标代码生成阶段:@babel/generator

    实现一个 babel Plugin

    现在我们实现一下 babel-plugin-replace,主要功能是在开发中通过缩写的形式来替换一些变量书写,比较经典的应用是利用缩写在判断环境变量
    webpack 配置如下 ```javascript // webpack.config.js const path = require(“path”);

module.exports = { entry: “./index.js”, mode:’production’, output: { path: path.resolve(dirname, “dist”), filename: “bundle.js”, }, module: { rules: [ { test: /.js$/, exclude: /node_modules/, use: { loader: “babel-loader”, options: { plugins: [ [ “./babel-plugin”, { replace:{ // 区别开发环境 Dev: process.env.NODE_ENV === 'development', Prod: process.env.NODE_ENV === 'production', TEST__: process.env.NODE_ENV === 'test', } }, ], ], }, }, }, ], }, };

  1. 我们可以更加不同的变量环境来判断是否打包
  2. ```javascript
  3. // index.js
  4. if(__Dev__){
  5. console.log('开发环境')
  6. }
  7. if(__PROD__){
  8. console.log('开发环境')
  9. }
  10. if(__TEST__){
  11. console.log('测试环境')
  12. }

我们可以通过 https://astexplorer.net/ 来查看ast的情况如下
image.png

通过 ast 看查到 Identifier 下 name 属性值是需要替换的
代码实现步骤

  1. 在外部配置中获取配置项
  2. 如果存在配置项,我们进行替换,反之直接返回
  3. 拿到遍历 Identifier 节点,获取对应 name 属性值进行匹配,如果匹配进行节点属性值替换 ```javascript function visitor(path, state) { // opts获取外部配置 const opts = state.opts; if (Object.keys(opts).length === 0) { return; } if (opts.replace && Object.keys(opts.replace).includes(path.node.name)) { // 替换节点 path.node.name = opts.replace[path.node.name]; } }

module.exports = function () { return { visitor: { Identifier: visitor, }, }; }; ``` 注意:babel的常用工具包已经集成在 babel-loader中 ,所以不需要再次安装了

参考:
https://babeljs.io/
https://astexplorer.net/
https://www.webpackjs.com/loaders/babel-loader/