第15期 - 栀子花香

封面图来源于张圆附近满墙的栀子花,喜欢的花铺满整个面非常的不错。

学习笔记-webpack相关

webpack 原理

devtool 调试 以便于在 index.js 文件中设置断点、查看变量、单步执行代码等

webpack可以将前端各种资源(包括CSS及其预编译方案、JS及其预编译方案)统一打包为.js文件和资源文件。

基本打包流程:
读取文件,分析模块依赖
对模块进行解析执行(深度遍历). 针对不同的模块使用不同的 loader. 编译模块,生成抽象语法树(AST)
遍历 AST,输出 JS.

chunk 在浏览器中加载时,可以通过网络请求获取,用于构建页面和提供功能。
bundle 包含多个 chunk,每个 chunk 可对应多个 module.

初始化项目对浏览器请求项目资源 http://trackercollect.xx.com/appInfo/bundle 为了更好地管理和优化资源加载,开发团队会将不同类型的静态资源(如 JavaScript、CSS、图片等)托管在不同的域名下,这样可以利用浏览器的并行下载机制提升页面加载速度。将特定功能或服务模块托管在单独的域名下,可以增加安全性和隔离性

webpack-dev-server 开发服务器

在本地开发环境中,通常会配置开发服务器(如 webpack-dev-server),它可能会实时打包并提供打包后的资源,负责本地文件的编译和输出以及监听,包括 bundle 文件。 将所有模块打包到一个bundle文件,我们会得到一个很大的文件,然而我们浏览器是可以并行下载多个文件的,这样下载一个大文件没法利用并行下载能力,导致资源加载速度较慢;
整个项目的代码被打包成一个大的 bundle,但在实际运行中,某些功能或页面可能并不需要在初始加载时加载全部代码,而是在用户操作或特定条件下才进行加载。这时就需要单独请求和加载特定的 chunk 文件。
一个bundle里的东西并不需要一次性加载,需要按照路由按需加载,这个时候就需要按需加载,拆分成不同的chunk. 必须通过网络请求去加载还未得到的文件。 如果模块数量很多,加载时间会很长,因此把所有模块都存放在了数组中,执行一次网络加载。

Tree shaking 是一种通过清除多余代码方式来优化项目打包体积,编译的时候正确判断到底加载了那些模块,静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码.

热更新

为了实现热更新, 本地开发环境通常会请求 websocket 实现 允许开发者在不重新加载整个页面的情况下对代码进行更新和调试。
ws://xx.d.xxx.com:8081/sockjs-node/522/mnryftkf/websocket. 启动websocket服务,可以建立服务器和浏览器之间的双向通信。当监听到本地代码发生改变时,主动向浏览器发送新hash以及ok字段。

websocket仅仅用于客户端(浏览器)和服务端进行通信。而真正做事情的活还是交回给了webpack。热更新检查事件是调用reloadApp方法。利用node.js的EventEmitter,发出webpackHotUpdate消息

模块打包

读取webpack的配置参数;
启动webpack,创建Compiler对象并开始解析项目;
从入口文件(entry)开始解析,并且找到其导入的依赖模块,递归遍历分析,形成依赖关系树,组装代码块 chunk. 对不同文件类型的依赖模块文件使用对应的Loader进行编译
整个过程中webpack会通过发布订阅模式,向外抛出一些hooks,而webpack的插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。

其中文件的解析与构建是一个比较复杂的过程,在webpack源码中主要依赖于compiler和compilation两个核心对象实现。

compiler对象是一个全局单例,他负责把控整个webpack打包的构建流程。 compilation对象是每一次构建的上下文对象,它包含了当次构建所需要的所有信息,每次热更新和重新构建,compiler都会重新生成一个新的compilation对象,负责此次更新的构建过程。

而每个模块间的依赖关系,则依赖于AST语法树。每个模块文件在通过Loader解析完成之后,会通过acorn库生成模块代码的AST语法树,通过语法树就可以分析这个模块是否还有依赖的模块,进而继续循环执行下一个模块的编译解析。

异步模块动态导入

采用JSONP的思路,首先,将动态引入模块单独打成一个js文件;其次,在import执行时创建script标签传入src为引入模块地址;从而实现动态加载的效果,注意,JSONP必然是异步的,所以必须要结合Promise;

根据 installedChunks 检查是否加载过该 chunk. 假如没加载过,则发起一个 JSONP 请求去加载 chunk. 设置一些请求的错误处理,然后返回一个 Promise。

  • 如果chunk没有被加载过,会为这个chunk创建一个promise对象.
  • 将promise对象存在promises数组中.
  • 将promise的resolve 和 reject存在installedChunks[chunkId]中.

详解 loader 和 plugin

loader

loader 用于对模块的源代码进行转换。loader 可以使你在 import 或 加载 模块时预处理文件。 webpack 只能处理 JavaScript 和 JSON 文件,通过配置不同的 loaders,可以扩展 Webpack 处理其他类型的文件

告诉 webpack 编译器(compiler) babel-loader 通过 Babel 转译 JavaScript 文件

module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
 }

webpack 对每个 .css 使用 css-loader,以及对所有 .ts 文件使用 ts-loader

module.exports = {
  module: {
    rules: [
      { test: /\.css$/, use: 'css-loader' },
      { test: /\.ts$/, use: 'ts-loader' },
    ],
  },
};

注意点

  • 在 webpack 配置中定义 rules 时,要定义在 module.rules 而不是 rules 中。
  • loader 从右到左(或从下到上)地取值(evaluate)/执行(execute)。

loader 特性

loader 支持链式调用 loader 可以是同步的,也可以是异步的。 插件(plugin)可以为 loader 带来更多特性

loader 解析

遵循特定的规范进行解析,且 每个 loader 会接收前一个 loader 处理后的输出作为输入,最终输出给下一个 loader

module.exports = {
  module: {
    rules: [
    ],
  },
};

每次文件系统访问文件都会被缓存,以便于更快触发对同一文件的请求,启用 Watch 模式 则 webpack 将继续监听任何已解析文件的更改

module.exports = {
  //...
  watch: true,
};

https://webpack.docschina.org/configuration/watch/#watch

JavaScript 文件处理,将现代 JavaScript 语法转换为兼容更旧浏览器的语法。

{
     test: /\.js$/,  // 匹配所有以 .js 结尾的文件
     exclude: /node_modules/,  // 排除 node_modules 文件夹
     use: 'babel-loader',  // 使用 babel-loader 处理匹配的文件
   },

为同一个文件类型指定多个 loaders,形成一个处理链 这段配置会将 .scss 文件依次通过 sass-loader(将 Sass 转换为 CSS)、css-loader(解析 CSS)、style-loader(将 CSS 插入到 DOM 中)。

{
     test: /\.scss$/,  // 匹配所有以 .scss 结尾的文件
     use: ['style-loader', 'css-loader', 'sass-loader'],  // 使用 style-loader 和 css-loader
   },

Loaders 可接受 options 进行配置,或在 .babelrc 或 babel.config.js 文件中配置,根据你指定的浏览器兼容性目标自动决定需要的 Babel 插件和 polyfills。babel-loader 会自动读取 babel.config.js 文件中的配置

{
  test: /\.js$/,
  exclude: /node_modules/,
  use: {
    loader: 'babel-loader',
    options: {
      presets: ['@babel/preset-env'],
    },
  },
}

处理多种文件类型,Webpack 的 loader 系统可以处理多种文件类型,不限于 JavaScript 和 CSS

{
  test: /\.(woff|woff2|eot|ttf|otf)$/,
  use: 'file-loader',
}

编写一个 loader

https://webpack.docschina.org/contribute/writing-a-loader/

plugin

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量 用法:在 webpack 配置中,向 plugins 属性传入一个 new 实例

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 用于访问内置插件

module.exports = {
   plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};

自定义一个 plugin

webpack 插件是一个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用,并且在 整个 编译生命周期都可以访问 compiler 对象。 pluginName 为驼峰式命名的插件名称

const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
  apply(compiler) {
    compiler.hooks.run.tap(pluginName, (compilation) => {
      console.log('webpack 构建正在启动!');
    });
  }
}

module.exports = ConsoleLogOnBuildWebpackPlugin;

Babel 相关

Babel 通过语法转换器支持最新版本的 JavaScript

Babel 是一个工具链,主要用于将 ECMAScript 2015+ 代码转换为在当前和旧版浏览器或环境中向后兼容的 JavaScript 版本。Babel 可以完成以下主要功能:

-变换语法 -目标环境中缺少的 Polyfill 功能(通过第三方 Polyfill,例如core-js) -源代码转换(codemods)

Babel 编译流程

源代码 → Parse(解析)→ Transform(转换)→ Generate(生成)→ 目标代码

Babel 工作流程

Parser 解析

@babel/parser 语法解析器 该解析器先进行词法分析,将代码字符串拆分为一个个 Tokens(令牌),Tokens 包含语法片段、位置信息及类型信息,例如for (const item of items) {}经词法解析后会得到对应 Token 数组。​

接着进行语法解析,把 Tokens 转换为抽象语法树(AST)。AST 是代码的结构化表示,以树形结构展示代码的语法构成,每个节点代表有意义的语法单元,如console.log(‘hello world’)会被解析为包含Program、CallExpression、Identifier等节点类型的 AST。@babel/parser已内置支持多种语法,如 JSX、Typescript、Flow 以及最新的 ECMAScript 规范

Transformation 转换

@babel/core 作为 Babel 的核心引擎,负责遍历第一步生成的 AST。遍历过程中,依据开发者配置的插件,针对不同类型的 AST 节点进行操作 例如@babel/plugin-transform-arrow-functions插件,会将遇到的箭头函数节点(ArrowFunctionExpression)转换为标准的function表达式节点;@babel/plugin-transform-classes插件则会将 ES6 类声明节点拆解成基于prototype的函数定义节点。插件可以对 AST 进行增、删、改操作,完成后 AST 已转变为目标形态(如兼容 ES5 的形态)。

Generation 生成

以转换后的 AST 作为输入。​ 使用@babel/generator将其转换回字符串形式的 JavaScript 代码,同时处理代码格式化(如缩进、空格等),并生成 Source Map(用于调试转换后的代码,方便定位原始代码位置),最终输出兼容性良好的 JavaScript 代码。

Babel 的架构

  1. 内核协调层 @babel/core 架构 “总指挥”:加载配置(.babelrc 等)、调度其他模块、管理插件执行流程
  2. 代码处理层
  • @babel/parser 解析:将源码→Tokens(词法分析)→AST(语法分析),支持 ES6+、JSX 等语法
  • @babel/traverse 转换:基于 “访问者模式” 遍历 AST,触发插件对节点的增 / 删 / 改操作
  • @babel/generator 生成:将修改后的 AST→目标代码,附带代码格式化、Source Map 生成
  1. 插件扩展层
  • 语法插件 @babel/plugin-syntax-* 辅助解析:开启@babel/parser对特殊语法的识别(如 TypeScript)
  • 转换插件 @babel/plugin-transform-* 实现核心转换:如箭头函数→普通函数、ES6 类→prototype 函数
  • 预设 @babel/preset-* 插件集合:简化配置(如 preset-env 包含所有 ES6 + 转换插件,preset-react 对应 React 语法)
  1. 开发辅助层 @babel/types、@babel/template等 插件开发工具:@babel/types用于 AST 节点校验 / 创建,@babel/template简化 AST 生成

Babel 的编译过程本质是 “源码→AST→目标代码” 的线性流转,架构通过 “分层解耦 + 插件注入” 实现灵活性

🌰 Babel 中解析阶段的词法解析和语法解析具体有何不同? 词法解析 由词法解析器负责,将源代码字符串拆分为 Tokens,Tokens 包含语法片段、位置和类型信息,例如for (const item of items) {}词法解析后得到对应 Token 数组,主要是从字符串层面按语法规则切分最小单元。语法解析则是语法解析器将 Tokens 转换为 AST,AST 以树形结构展示代码语法构成,每个节点代表有意义的语法单元,如console.log(‘hello world’)会被解析为特定节点类型构成的 AST,侧重于构建代码的结构模型 。

Babel 插件系统中语法插件和转换插件的作用分别是什么,它们如何协同工作? 语法插件(@babel/plugin-syntax-* 主要用于开启或配置@babel/parser的特定功能特性,帮助@babel/parser识别一些特殊语法,一般用户无需直接操作,常被包含在转换插件配置中。转换插件 如@babel/plugin-transform-xxx、@babel/plugin-proposal-*)负责对 AST 进行实际转换操作,实现语法转换、代码压缩、功能增强等。协同工作时,语法插件先助力@babel/parser正确解析代码生成 AST,转换插件再基于此 AST,按照各自功能对节点进行增、删、改,完成代码转换。

Babel 核心模块@babel/core在整个架构中起到怎样的关键作用,它如何协调其他模块工作? @babel/core是 Babel 微内核架构的核心,起着至关重要的协调作用。它负责加载和处理配置文件,获取插件、预设等配置信息;加载各类插件;调用@babel/parser将源代码解析为 AST;调用@babel/traverse遍历 AST,并应用插件对 AST 进行转换;最后调用@babel/generator将转换后的 AST 生成最终代码及 Source Map。通过这一系列有序操作,将各个模块串联起来,确保 Babel 整个编译流程顺利进行 。

Flow

Flow 是 Facebook(现 Meta)开发的一款静态类型检查工具,专门用于 JavaScript 语言。它的核心作用是在代码编写和编译阶段,通过 “给 JavaScript 代码添加类型注解” 的方式,提前检测出潜在的类型错误(如把字符串传给需要数字的函数、访问不存在的对象属性等),避免这些错误在代码运行时才暴露,从而提升 JavaScript 项目的稳定性和可维护性。

Flow 的类型语法(: number、: string 等)浏览器是看不懂的; 所以必须用 Babel(加上 @babel/preset-flow)去掉类型注释; 最终输出纯 JavaScript。

Polyfill

Babel 只能“转换语法”,比如 class、箭头函数; 但 新 API(比如 Promise、Array.includes) 是无法仅靠语法转换实现的; 这时就需要 Polyfill 来在运行时“补上”这些功能

新增 API,语法转换无法替代 引入 Polyfill(比如 core-js)Babel 本身 不会自动注入 Polyfill,需要搭配 @babel/preset-env 来决定注入方式。方式 1:useBuiltIns: “usage” 方式 2:useBuiltIns: “entry”

----------10.9更新------------

Babel 的架构和原理

Babel 处理流程

Tokenizer 进行词法分析,将源代码分割成Token数组;再经过 Parser 进行词法分析,将Token数组转换为AST;然后进行 Traverser 遍历(使用访问者模式)AST 并应用到转换器;接着就是转换(Transform)了,转换阶段会对 AST 进行遍历,在这个过程中对节点进行增删查改。Babel 所有插件都是在这个阶段工作, 比如语法转换、代码压缩。 Transformer AST 转换器,用于增删改查AST节点;最后Generator 进行代码生成,将AST转换为源代码,把 AST 转换回字符串形式的Javascript,同时这个阶段还会生成Source Map。

从源码 解析(Parsing) 开始,解析包含了两个步骤: 词法解析器(Tokenizer)在这个阶段将字符串形式的代码转换为Tokens(令牌). Tokens 可以视作是一些语法片段组成的数组 语法解析(Syntactic Analysis):这个阶段语法解析器(Parser)会把Tokens转换为抽象语法树(Abstract Syntax Tree,AST)

Babel 架构

它们的核心非常小,大部分功能都是通过插件扩展实现的。

核心

@babel/core ‘微内核’架构中的‘内核’。对于Babel来说,这个内核主要干这些事情: -加载和处理配置(config) -加载插件 -调用 Parser 进行语法解析,生成 AST -调用 Traverser 遍历AST,并使用访问者模式应用’插件’对 AST 进行转换 -生成代码,包括SourceMap转换和源代码生成

核心周边支撑

Parser(@babel/parser): 将源代码解析为 AST 就靠它了。 它已经内置支持很多语法. 例如 JSX、Typescript、Flow、以及最新的ECMAScript规范。目前为了执行效率,parser是不支持扩展的,由官方进行维护。

Traverser(@babel/traverse): 实现了访问者模式,对 AST 进行遍历,转换插件会通过它获取感兴趣的AST节点,对节点继续操作

Generator(@babel/generator): 将 AST 转换为源代码,支持 SourceMap

插件

babel 有多种类型插件 语法插件 @babel/plugin-syntax-* @babel/parser 已经支持了很多 JavaScript 语法特性,Parser也不支持扩展. 因此plugin-syntax-* 实际上只是用于开启或者配置Parser的某个功能特性。一般用户不需要关心这个,Transform 插件里面已经包含了相关的plugin-syntax-* 插件了。用户也可以通过parserOpts配置项来直接配置 Parser

转换插件 用于对 AST 进行转换, 实现转换为ES5代码、压缩、功能增强等目的.Babel仓库将转换插件划分为两种: @babel/plugin-transform-: 普通的转换插件 @babel/plugin-proposal-: 还在’提议阶段’(非正式)的语言特性

预定义集合 @babel/presets-* 插件集合或者分组,主要方便用户对插件进行管理和使用。比如preset-env含括所有的标准的最新特性; 再比如preset-react含括所有react相关的插件.

插件开发辅助

@babel/template: 某些场景直接操作AST太麻烦,就比如我们直接操作DOM一样,所以Babel实现了这么一个简单的模板引擎,可以将字符串代码转换为AST。比如在生成一些辅助代码(helper)时会用到这个库 @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工具

访问者模式

转换器会遍历 AST 树,找出自己感兴趣的节点类型, 再进行转换操作。AST 遍历和转换一般会使用访问者模式。

Babel 有那么多插件,如果每个插件自己去遍历AST,对不同的节点进行不同的操作,维护自己的状态。这样子不仅低效,它们的逻辑分散在各处,会让整个系统变得难以理解和调试

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

访问者会以深度优先的顺序, 或者说递归地对 AST 进行遍历

Babel 插件应用

Babel 会按照插件定义的顺序来应用访问方法,比如你注册了多个插件,babel-core 最后传递给访问器

{
  Identifier: {
    enter: [plugin-xx, plugin-yy,] // 数组形式
  }
}

当进入一个节点时,这些插件会按照注册的顺序被执行。大部分插件是不需要开发者关心定义的顺序的,有少数的情况需要稍微注意以下,例如plugin-proposal-decorators

{
  "plugins": [
    "@babel/plugin-proposal-decorators",     // 必须在plugin-proposal-class-properties之前
    "@babel/plugin-proposal-class-properties"
  ]
}

所有插件定义的顺序,按照惯例,应该是新的或者说实验性的插件在前面,老的插件定义在后面。因为可能需要新的插件将 AST 转换后,老的插件才能识别语法(向后兼容)

{
  "presets": ["es2015", "react", "stage-2"]
}

注意Preset的执行顺序相反!! 访问者在访问一个节点时, 会无差别地调用 enter 方法, 每个visit方法都接收一个 Path 对象, 你可以将它当做一个‘上下文’对象,这里面包含了很多信息:

当前节点信息 节点的关联信息。父节点、子节点、兄弟节点等等 作用域信息 上下文信息 节点操作方法。节点增删查改 断言方法。isXXX, assertXXX

我们可以对 AST 进行任意的操作,比如删除父节点的兄弟节点、删除第一个子节点、新增兄弟节点… 当这些操作’污染’了 AST 树后,访问者需要记录这些状态,响应式(Reactive)更新 Path 对象的关联关系, 保证正确的遍历顺序,从而获得正确的转译结果。

在词法区块(block)中,由于新建变量、函数、类、函数参数等创建的标识符,都属于这个区块作用域. 这些标识符也称为绑定(Binding),而对这些绑定的使用称为引用(Reference)

在Babel中,使用Scope对象来表示作用域。 我们可以通过Path对象的scope字段来获取当前节点的Scope对象 Scope 对象和 Path 对象差不多,它包含了作用域之间的关联关系(通过parent指向父作用域),收集了作用域下面的所有绑定(bindings), 另外还提供了丰富的方法来对作用域仅限操作。

------------------10.10更新------------------------

babel 使用

.babelrc 文件配置 用来让 Babel 做你要它做的事情的配置文件 定义 JavaScript 代码的转译规则,确保新语法能在主流环境(如旧浏览器、低版本 Node.js)中正常运行

  {
    "presets": [
      "es2015", // 将 ES2015(即 ES6)语法 转译为 ES5 语法
      "react", // 处理 React 框架的专属语法,确保 React 代码能被 Babel 正确转译
     "stage-x" // 草案语法
    ],
    "plugins": ["transform-es2015-classes"], // 插件集合
    // 基于环境自定义 Babel
    "env": {
    "development": {
       "plugins": [...]
     },
     "production": {
       "plugins": [...]
     }
    }
  }

babel-core

babel-core 是 Babel 生态的核心依赖包,本质是提供 “代码转译的底层能力”—— 无论是通过 babel-cli 命令行编译、babel-loader 配合 Webpack 构建,还是在 Node.js 代码中手动调用 Babel 转译,最终都会依赖 babel-core 实现 “将新语法代码(如 ES6、JSX、stage 草案)转换成目标环境可执行代码(如 ES5)” 的核心逻辑

babel-register

Node 环境下的 “实时转译工具”,解决 “Node 不认识新语法” 的问题 babel-register 是 Babel 生态中一款开发环境专用工具,核心作用是 “拦截 Node.js 的模块加载机制(require),对加载的文件实时进行 Babel 转译”—— 简单说,它能让 Node 直接运行 ES6+、JSX 等 “非原生支持” 的语法,无需提前手动编译文件。本质就是 “拦截 Node 通过 require 加载的文件内容,把内容交给 babel-core 处理,再把处理后的结果还给 Node 执行

babel-polyfill

babel-polyfill 是 Babel 生态中解决 “运行时 API 兼容性” 的核心工具 —— 它的作用不是 “转译语法”(如箭头函数、class),而是 “补充 ES2015+ 新增的全局 API、实例方法”(如 Promise、Array.from、String.prototype.padStart),让这些新 API 能在不支持它们的旧环境(如 IE11、Node.js 4.x)中正常运行。

babel-runtime

解决 “转译代码冗余” 与 “全局污染” 问题 babel-runtime 是 Babel 生态中用于 “减少转译代码冗余、避免全局变量污染” 的核心工具,本质是一个 “可复用的辅助函数库”—— 它会将 Babel 转译新语法时生成的重复辅助代码(如 _classCallCheck、_extends)提取到独立模块中,供所有转译文件复用,同时避免在全局环境注入变量。

痛点 1:代码体积冗余 例如,当你用 ES6 类语法(class User {})时,Babel 转译后会生成一个 _classCallCheck 辅助函数,用于确保类通过 new 关键字调用,如果你的项目有 100 个文件都用了 class,Babel 会在这 100 个文件中重复注入 100 次 _classCallCheck—— 这些重复代码会显著增加最终打包体积(尤其大型项目)

痛点 2:可能污染全局环境 部分辅助函数(如早期转译 Promise 时的 Promise polyfill)会直接在全局环境注入变量(如 window.Promise),如果项目中其他库也定义了 Promise,可能导致变量冲突(全局污染)。 babel-runtime 把这些 “重复的辅助函数” 提前打包成独立模块(如 @babel/runtime/helpers/classCallCheck),转译时不再重复注入,而是通过 require/import 引用这个模块 —— 相当于 “把重复代码抽成公共函数库,所有文件共用”。

基于环境自定义 Babel

帮助你调试代码或是与工具集成

babel 插件手册

Babel 是 JavaScript 编译器,源码到源码的编译器,通常也叫做“转换编译器(transpiler)”。 意思是说你为 Babel 提供一些 JavaScript 代码,Babel 更改这些代码,然后返回给你新生成的代码

Babel 的处理步骤

Babel 的三个主要处理步骤分别是: 解析(parse),转换(transform),生成(generate)。

解析

解析步骤接收代码并输出 AST。 这个步骤分为两个阶段:词法分析(Lexical Analysis)和 语法分析(Syntactic Analysis)

词法分析 词法分析阶段把字符串形式的代码转换为 令牌(tokens) 流

语法分析 语法分析阶段会把一个令牌流转换成 AST 的形式,这个阶段会使用令牌中的信息把它们转换成一个 AST 的表述结构,这样更易于后续的操作

转换

转换步骤接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。这是 Babel 或是其他编译器中最复杂的过程

遍历 转换 AST 需要进行递归的树形遍历

Visitors 访问者 在 Babel 生态中,Visitors(访问者) 是操作 AST(抽象语法树)的核心机制,也是实现代码转换(如 ES6+ 转 ES5、语法扩展、代码检查)的核心工具。它本质上是一套 “匹配 - 处理” 规则:通过定义特定 AST 节点的 “访问方法”,让 Babel 在遍历 AST 时,自动触发对应方法来修改节点、新增节点或删除节点,最终实现代码的自定义转换。

通过 @babel/traverse 遍历 AST,Visitors 就是在这一步生效—— 它定义了 “遇到哪种节点要做什么”,比如把 “箭头函数节点” 转为 “普通函数节点”;Visitors 的核心原理:“节点类型匹配” 与 “遍历触发“

AST 是一棵由不同类型节点组成的树(比如 VariableDeclaration 表示变量声明、ArrowFunctionExpression 表示箭头函数、Identifier 表示标识符)。Babel 遍历 AST 时会遵循 “深度优先” 原则,而 Visitors 的核心逻辑是: 定义 “节点类型 - 处理函数” 的映射:你告诉 Babel“当你遇到 ArrowFunctionExpression 节点时,执行这个函数”; 自动触发处理函数:Babel 遍历到对应类型的节点时,会自动调用你定义的处理函数,并将当前节点、遍历上下文等信息传入函数,供你操作。

Visitors 的本质是一个对象,对象的 “键” 是 AST 节点类型(如 ArrowFunctionExpression),“值” 是处理该节点的函数(称为 “访问器函数”)。

在访问器函数中,参数不是直接的 node(节点),而是 path(路径对象)—— 这是 Visitors 的核心设计,因为单个节点的修改往往依赖于它在 AST 中的 “位置”(如父节点、兄弟节点)。

生成

代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps) 代码生成其实很简单:深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。

附:

AST 工具 https://astexplorer.net/

Babel 用户手册 https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/user-handbook.md

Babel 插件手册 https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md