Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Babel概述 #6

Open
hoperyy opened this issue Jun 11, 2020 · 0 comments
Open

Babel概述 #6

hoperyy opened this issue Jun 11, 2020 · 0 comments

Comments

@hoperyy
Copy link
Owner

hoperyy commented Jun 11, 2020

Babel是JavaScript编译器。

通过Babel,开发者可以自由使用下一代JavaScript语法。高版本JavaScript语法将被转译为低版本语法,以便顺利运行在各类环境:低版本浏览器、低版本Node.js等。

本文,我们从全局层面理解下Babel的运行原理,如生命周期、生态、开发模式等。

1.1 抽象语法树

抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。

AST是树形对象,以结构化的形式表示编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

之所以说语法树是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。

和抽象语法树相对的是具体语法树(通常称作分析树)。一般的,在源代码的翻译和编译过程中,语法分析器创建出分析树,然后从分析树生成AST。

1.2 Babel的功能

Babel是一个典型的"输入-输出"工具。

Babel接收一段JavaScript代码(通常是高版本JavaScript),经过一系列处理,转为目标代码(通常是低版本JavaScript)并输出。

图解如下:

image

有多种方式可以使用Babel,如:命令行(babel-cli)、方法调用(babel-core)、webpack loader(babel-loader)等。

1.3 Babel生命周期

和多数编译器相同,Babel运行的生命周期主要是3个阶段,如下图:

image

  • 解析(Parsing)

    该阶段,代码字符串被转为抽象语法树(AST)。

    解析过程包括2个环节:词法解析、语法解析。

    词法解析阶段,代码字符串被解析为token令牌数组,数组项包括:代码字符碎片的值、位置、类型等信息。

    语法解析阶段,token令牌数组被解析为抽放语法树(AST)。

    babel-parser完成该阶段的主要功能。

  • 转换(Transformation)

    这个阶段,Babel通过各类语法插件、转换插件对AST进行遍历、增删改等操作,转为目标AST。

    完成该阶段主要功能的包括以下工具:

    • babel-core负责任务调度
    • 语法插件负责开启babel-parser对某些语法的编译
    • 转换插件负责对AST进行遍历和转换,如增删改
  • 代码生成(Generation)

    Babel将修改后的AST转为目标代码字符串。

    babel-generator完成该阶段的主要功能。

1.4 Babel架构

我们把范围扩展一下,了解Babel是怎样运行的,如下图:

image

Babel产品包括多个模块:

  • 对外工具
  • 核心模块(babel-core)
  • 插件
  • 内部工具集

1.5 babel-core

从图1-3-1中可以看出,babel-core作为Babel的核心模块,主要有2个功能:

  • 任务调度

    • 在AST生成阶段,调用babel-parser进行词法和语法解析,生成AST
    • 在AST转换阶段,调用语法插件和转换插件,对AST做增删改查
    • 在生成阶段,调用babel-generator,将AST转为目标代码
  • 对外暴露各类API

    Babel的对外工具babel-cli、babel-loader等均使用了babel-core的一些API,举个例子:

    • babel.parse():将代码字符串转为AST。
    • babel.generate():将AST转为代码字符串。

我们将以一个章节介绍babel-core的各种细节。

1.6 Babel对外工具

面向外部开发者,Babel提供了多种工具,如babel-cli、babel-loader等。

1.6.1 babel-cli

babel-cli帮助开发者以命令行的形式进行文件编译,并支持各种配置,其功能非常强大。

我们专门以一章的篇幅介绍babel-cli,从中可以吸取很多不错的开发经验。

1.6.2 babel-loader

babel-loader是专门为webpack使用的loader,作用也是进行代码编译。

babel-loader为用户提供了多种配置项,且针对webpack环境提供了性能优化等针对性处理。

我们在后续章节专门介绍babel-loader。

1.6.3 babel-core

babel-core有JavaScript代码解析所需的各项功能,可以作为一个独立工具对外提供服务。

const babel = require('@babel/core');

比如:

  • 代码字符串转AST:babel.parse
  • AST转代码字符串:babel.transform

1.7 配置方式

Babel支持多种配置方式。

下文中所述的"项目根目录",指的是package.json文件所在的目录。

  • babel.config.js

    在项目根目录创建babel.config.js文件,其内容如下:

    module.exports = function(api) {
        return {
            presets: [ ... ],
            plugins: [ ... ]
        };
    };
  • .babelrc

    在项目根目录创建.babelrc文件,其内容如下:

    {
        "presets": [ ... ],
        "plugins": [ ... ]
    }
  • .babelrc.js

    在项目根目录创建.babelrc.js文件,其内容如下:

    module.exports = {
        presets: [ ... ],
        plugins: [ ... ]
    };
  • package.json

    Babel的配置信息可以写在package.json内:

    "babel": {
        "presets": [ ... ],
        "plugins": [ ... ]
    }
    

1.8 插件

丰富的插件,帮助Babel成为一个非常成功的编译工具。

对AST的遍历、转换是Babel编译的核心功能,但Babel本身并不参与该过程,将这些功能作为插件引入到运行时。

具体来说,babel-core作为核心工具,不提供对AST的修改逻辑,通过调用各类插件,实现对AST的修改。

1.6节所述的配置项,也基本围绕Babel的插件生态进行。

1.8.1 语法插件和转换插件

Babel的插件分为语法插件和转换插件。

  • 语法插件

    Babel有一个细节,babel-parser负责将JavaScript代码转为抽象语法树(AST),它支持全面识别ESNext、TypeScript、JSX等语法,目前由Babel团队开发维护,不支持插件化。

    Babel插件生态中的语法插件,其功能就是作为"开关",配置是否开启babel-parser的某些语法编译功能。

    语法插件在Babel源码中,以babel-plugin-syntax开头。

    举个例子:

    • babel-plugin-syntax-decorators

      负责开启babel-parser对装饰器的语法支持。

    • babel-plugin-syntax-dynamic-import

      负责开启babel-parser对import语句的语法支持。

    • babel-plugin-syntax-jsx

      负责开启babel-parser对jsx语法的支持。

  • 转换插件

    转换插件就是社区里常说的Babel插件,负责对AST进行遍历和转换。

    转换插件在Babel源码中,以babel-plugin-transform开头。

    举个例子:

    • babel-plugin-

1.8.2 preset

Babel插件的功能是细粒度的,大部分插件承担了单一功能。

而在实际开发过程中,往往需要支持对各类语法的支持。此时,有两种做法:

  1. 需要支持哪些语法功能,就引入哪些插件
  2. 直接引入一个插件集合,涵盖所需的各类插件功能

很显然,第一种做法是相对麻烦的。针对第二种做法,Babel提供了插件集preset。

preset在Babel源码中,以babel-preset开头。

例如,Babel已经提供了几种常用的preset供开发者使用:

  • babel-preset-env
  • babel-preset-react
  • babel-preset-flow
  • babel-preset-typescript

1.7.3 插件运行顺序

Babel配置项中,plugins和presets均以数组的形式配置,执行时有先后顺序。

  • plugins在presets之前运行
  • plugins按照数组正序执行
  • presets按照数组倒序执行

1.9 Babel工程管理方式

如果一个大型项目由多个独立的工程组成,该如何管理呢?

通常来说,有两种方式:

  • monorepo

    monorepo指的是,该项目所有工程统一在一个仓库中,统一维护、发布。

    目前React、Angular等均采用该方式。

  • multirepo

    和monorepo相反,该项目的每个子工程独立运行,每个子工程相互独立,自由选择构建工具,独立发布。

二者对比如下:

monorepo multirepo
所有项目在同一个分支内开发 所有项目独立开发
统一的issue管理 分散的issue管理
统一的开发环境 分散的开发环境
统一管理所有项目的版本 各项目难以统一版本
项目所需存储空间大 项目分散,存储空间较小

Babel项目涉及的子工程很多,其采用了monorepo方式开发,并且沉淀出了自动化工具lerna,当然也有其他工具实现了monorepo。

Babel使用的lerna命令行工具中,lerna bootstrap命令负责统一安装依赖,lerna publish命令负责统一发布工程内的子项目。

Babel的大部分package(如babel-core、babel-parser等)的版本号是始终一致的,这也是monorepo项目的一个特点。

monorepo项目中,开发者和使用者可以快速地查看当前工具集的统一版本,无需深入每个package查看版本并理解各自的关联关系。

1.10 总结

本文我们从宏观层面了解了Babel的编译过程及其生态中各个模块的作用,能够初步理解Babel是怎样运行的。

其中涉及的实现原理,我们拆解到其他文章中。

@hoperyy hoperyy mentioned this issue Feb 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant