Babel原理浅见

340 阅读2分钟

什么是 Babel?

官方的解释 Babel 是一个 JavaScript 编译器,用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前版本和旧版本的浏览器或其他环境中。简单来说 Babel 的工作就是:

  • 语法转换
  • 通过 Polyfill 的方式在目标环境中添加缺失的特性
  • JS 源码转换

Babel的原理和流程

  • Parser(@babel/parser):词法解析(Lexical Analysis),词法解析器(Tokenizer)江代码分割成(Tokens)语法片段数组

image.png

  • Parser(@babel/parser): 语法解析(Syntactic Analysis):这个阶段语法解析器(Parser)会把Tokens转换为抽象语法树(Abstract Syntax Tree,AST)。语法插件(@babel/plugin-syntax-*)参与

image.png

  • Traverser(@babel/traverse):遍历(访问者模式)AST,并应用转换器
  • Traverser(@babel/traverse):transformer,AST转换器,增删改节点,(@babel/plugin-transform-*)插件参与
  • Generator(@babel/generator):代码生成,将AST转化为字符串形式新代码,同时这个阶段还会生成Source Map。

功能图

image.png

包结构

  • @babel/core核心包,内置一下包
  • 代码解析器Parser(@babel/parser)
  • AST遍历器Traverser(@babel/traverse)
  • 代码生成器Generator(@babel/generator)
  • 语法插件(@babel/plugin-syntax-*)
  • 转换插件(@babel/plugin-transform-*)
  • 预案插件(@babel/plugin-proposal-*)
  • 插件预定义集合(@babel/presets-*)presets-env
  • 模板引擎@babel/template更方便操作AST
  • @babel/types: AST 节点构造器和断言. 插件开发时使用很频繁
  • @babel/helper-*: 一些辅助器,用于辅助插件开发,例如简化AST操作
  • @babel/helper: 辅助代码,单纯的语法转换可能无法让代码运行起来,比如低版本浏览器无法识别class关键字,这时候需要添加辅助代码,对class进行模拟。

工具

  • @babel/node: Node.js CLI, 通过它直接运行需要 Babel 处理的JavaScript文件
  • @babel/register: Patch NodeJs 的require方法,支持导入需要Babel处理的JavaScript模块
  • @babel/cli: CLI工具,命令式操作babel

访问者模式

  • 转换器操作 AST 一般都是使用访问器模式,由这个访问者(Visitor)来
  • 进行统一的遍历操作,
  • 提供节点的操作方法,
  • 响应式维护节点之间的关系;
  • 而插件(设计模式中称为‘具体访问者’)只需要定义自己感兴趣的节点类型,当访问者访问到对应节点时,就调用插件的访问(visit)方法。

写个简单插件

  • 对AST进行操作,实现按需加载代码转换
  • import { Button } from 'antd'前
  • import Button from "antd/lib/Button"后
//插件函数
const pluginImport = function({ types: t }) {
      return {
        visitor: {//返回访问者对象
          ImportDeclaration(path) {
            const { node: { specifiers, source } } = path;
            if (!t.isImportDefaultSpecifier(specifiers[0])) { // 对 specifiers 进行判断,是否默认倒入
              const newImport = specifiers.map(specifier => (
                t.importDeclaration(
                  [t.ImportDefaultSpecifier(specifier.local)],
                  t.stringLiteral(`${source.value}/lib/${specifier.local.name}`)
                )
              ))
              path.replaceWithMultiple(newImport)
            }
          }
        }
      }
    }
 //测试使用 
import * as babel from '@babel/core';
const c = `import { Button } from 'antd'`;

const { code } = babel.transform(c, {
  plugins: [pluginImport]
})

console.log(code); // import Button from "antd/lib/Button";