以Babel为例,讲述插件的机制

382 阅读5分钟

Babel内容总览:

  • 以Babel为例,讲述插件的机制 (本期)
  • 以Babel为例,讲述浏览器的兼容性

Babel 是一个 JavaScript 编译器

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

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的功能(通过引入第三方 polyfill 模块,例如 core-js
  • 源码转换(codemods)

Babel 和 AST 的关系

  • 核心关系:Babel 通过生成和操作 AST 来实现代码的转换和编译。AST 作为 Babel 处理代码的中间表示,是 Babel 工作流程中不可或缺的一部分。
  • 插件系统:Babel 的强大之处在于其插件系统,这些插件通过分析和修改 AST 来实现具体的语法转换和优化。开发者可以根据需要选择或创建插件,以支持新的语言特性或实现特定的编译任务。

AST 抽象语法树定义 

webpackVite等很多的工具和库的核心都是通过Abstract Syntax Tree抽象语法树这个概念来实现对代码的检查、分析等操作的,这些工具的原理都是通过JavaScript Parser把代码转化为一颗抽象语法树(AST),这颗树定义了代码的结构,通过操纵这颗树,我们可以精准的定位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作

image.png

在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。

Javascript的语法是为了给开发者更好的编程而设计的,但是不适合程序的理解。所以需要转化为AST来使之更适合程序分析,浏览器编译器一般会把源码转化为AST来进行进一步的分析等其他操作。

抽象语法树用途 

  • 代码语法的检查、代码风格的检查、代码的格式化、代码的高亮、代码错误提示、代码自动补全等等
    • 如JSLint、JSHint对代码错误或风格的检查,发现一些潜在的错误
    • IDE的错误提示、格式化、高亮、自动补全等等
  • 代码混淆压缩
    • UglifyJS2等
  • 优化变更代码,改变代码结构使达到想要的结构
    • 代码打包工具webpack、rollup等等
    • CommonJS、AMD、CMD、UMD等代码规范之间的转化
    • CoffeeScript、TypeScript、JSX等转化为原生Javascript

3.babel插件

  • 访问者模式Visitor 对于某个对象或者一组对象,不同的访问者,产生的结果不同,执行操作也不同
  • @babel/core Babel 的编译器,核心 API 都在这里面,比如常见的 transform、parse
  • [@babel/parser] Babel 的解析器
  • babel-types 用于 AST 节点的 Lodash 式工具库, 它包含了构造、验证以及变换 AST 节点的方法,对编写处理 AST 逻辑非常有用
  • babel-traverse用于对 AST 的遍历,维护了整棵树的状态,并且负责替换、移除和添加节点
  • babel-types-api
  • Babel 插件手册
  • babeljs.io babel可视化编译器

转换箭头函数 

转换前

const sum = (a,b)=>a+b

image.png

转换后

var sum = function sum(a, b) {
  return a + b;
};

image.png

let babel = require('@babel/core');
let t = require('babel-types');
const code = `const sum = (a,b)=>a+b`;
let transformArrowFunctions = {
    visitor: {
        ArrowFunctionExpression: (path) => {
            let node = path.node;
            let id = path.parent.id;
            let params = node.params;
            let body=t.blockStatement([
                t.returnStatement(node.body)
            ]);
            let functionExpression = t.functionExpression(id,params,body,false,false);
            path.replaceWith(functionExpression);
        }
    }
}
const result = babel.transform(code, {
    plugins: [transformArrowFunctions]
});
console.log(result.code);

Babel 在真实项目中遇到的问题有哪些 ?

{
  test: /\.js$/, // 匹配 JavaScript 文件
  exclude: /node_modules/, // 排除 node_modules 文件夹
  use: {
    loader: 'babel-loader', // 使用 babel-loader 转译 ES6/ES7 语法
    options: {
      cacheDirectory: true, // 开启缓存,提高编译速度
      presets: [ // 预设环境
        '@babel/preset-env', // 转译ES6+语法
        {
          useBuiltIns: 'usage', // 按需引入polyfill
          corejs: 3, // 指定core-js版本
          targets: { // 指定目标浏览器
            chrome: '49', // 例如,Chrome 49版本
          },
        },
        '@babel/preset-react', // 转译React JSX
        '@babel/preset-typescript', // 支持TypeScript
      ],
      plugins: [ // 插件列表
        '@babel/plugin-proposal-nullish-coalescing-operator', // 支持空值合并运算符
        '@babel/plugin-transform-optional-chaining', // 支持可选链式调用
      ],
    },
  },
}

我配置了兼容 可选链 了,但是打包后并没有进行编译,后来发现是因为读取了 tsconfig.json 文件的 target 属性

  • 这里的"esnext"指的是最新的ECMAScript标准的特性集,它包括了所有已经被ECMAScript标准委员会接受的最新特性,但可能还没有被所有浏览器或环境完全支持。使用esnext作为目标意味着编译器会尽可能地保留最新的语言特性,而不会转换为旧版本的ECMAScript标准。

  • 选择esnext作为目标可以让开发者使用最前沿的JavaScript特性,但同时也需要注意,这可能会导致在一些不支持这些最新特性的环境中出现兼容性问题。因此,在决定将target设置为esnext时,需要确保你的代码运行环境支持这些新特性,或者你已经有了相应的polyfill策略来填补缺失的功能。

image.png