研究了 babel.config.js 和 babelrc,理解了为什么ES6代码没被转化

前言
之前遇到过一个 babel 转换的问题,在下面这个 monorepo 的目录结构中
.
├── common
│   └── utils.js
├── package.json
└── packages
    └── modulea
        ├── .babelrc
        ├── index.js
        ├── package.json
        └── webpack.config.js

3 directories, 6 files
modulea 引用了外层 common/utils.js,结果在打包时发现common/utils.js 的代码并没有被翻译,但是 modulea 目录内的代码却能正常转换,觉得很奇怪,直到我将 babel 的配置文件由 .babelrc 改成了 babel.config.js 才能正常工作。于是就深究了一下两个配置文件的区别,觉得有点东西理解起来有点绕,于是写下这篇博客记录一下。

babel 7.x 以上开始支持两种类型的配置文件, 分别是.babelrc 和 babel.config.js ,在官方的描述里:

babel.config.js 当前项目维度 (Project Wide)的配置文件,相当于一份全局配置,如果 babel 决定应用这个配置文件,则一定会应用到所有文件的转换。

.babelrc 相对文件(File Relative)的配置文件, babel 决定一个 js 文件应用哪些配置文件时,会执行如下策略: 如果要转换的这个 js 文件在当前项目内,则会先递归向上搜索最近的一个 .babelrc 文件(直到遇到package.json目录),将其与全局配置合并。如果这个 js 文件不在当前项目内,则只应用全局配置,忽略与这个文件相关的 .babelrc 。这两个我更愿意将其称为全局配置 (babel.config.js) 和局部配置 (.babelrc) 便于理解一些。

由此便引入了一个非常关键的对 当前项目 的定义问题。

这个定义在官方文档就有说明

New in Babel 7.x, Babel has a concept of a "root" directory, which defaults to the current working directory. For project-wide configuration, Babel will automatically search for a babel.config.json file, or an equivalent one using the supported extensions, in this root directory.

大白话就是: 当前所在目录即为*当前项目*根目录,babel 会读取当前所在目录的 babel.config.js 作为全局配置。

下面我们分为几种 case 分别看一下 babel 的应用路径.

Case 1 所有代码都在一个单一工程中
这种情况下 babel.config.js 和 .babelrc 作用几乎一样,唯一不同是 babelrc 可以针对子目录分别配置.

.
├── .babelrc
├── babel.config.js
├── package.json
└── src
    ├── index.js
    └── subdir
        ├── .babelrc
        └── utils.js

2 directories, 6 files
这里, 我们在顶层目录运行 babel src/**/*.js --out-dir dist 。此时会读取当前目录的 babel.config.js 作为全局配置,根据前文所述规则应用的配置如

src/index.js 会应用 babel.config.js + .babelrc。
src/subdir/utils.js 会应用 babel.config.js + src/subdir/.babelrc
如果我们在 src 目录执行 babel **/*.js --outdir ,此时则不会应用 babel.config.js 的规则,但会应用 src 外层 .babelrc 的配置文件。

此时:

index.js 会应用 ../.babelrc
subdir/index.js 会应用 subdir/.babelrc





这里貌似有点不对劲, index.js 为什么会应用外层 .babelrc , 这个配置文件明明在当前项目"外"。

这里再回顾一下之前说的。

babel 决定一个 js 文件应用哪些配置文件时,会执行如下策略: 如果这个 js 文件在当前项目内,则会先递归向上搜索最近的一个 .babelrc 文件(直到遇到package.json目录),将其与全局配置合并。如果这个 js 文件不在当前项目内,则只应用全局配置,并忽略与这个文件相关的 .babelrc 。

babel决定是否执行向上搜索 .babelrc 这个策略时依据的是当前 js 文件的位置,而不是 .babelrc 的位置。此时的 index.js 在项目范围内,所以会执行向上搜索 .babelrc 的策略。

Case 2 多工程下的项目配置问题
对于 monorepo 项目,全局配置文件就有用的多了。首先看我开头遇到的问题

.
├── common
│   └── utils.js
├── package.json
└── packages
    └── modulea
        ├── .babelrc
        ├── index.js
        ├── package.json
        └── webpack.config.js

3 directories, 6 files
这里我在 packages/modulea 目录里执行打包命令时, .babelrc 只会应用 packages/modulea 里的文件,对于 common 目录下的文件则无法执行转换。即便我们在 common 目录中添加一个 .babelrc 也无济于事,因为 common 里的代码在当前的项目范围外面,只会应用全局配置。所以这种场景就非常适合使用 babel.config.js 。
.
├── common
│   └── utils.js
├── package.json
└── packages
    └── modulea
        ├── babel.config.js <---
        ├── index.js
        ├── package.json
        └── webpack.config.js

3 directories, 6 files
改成 babel.config.js 之后 common 目录就能正确进行转换了。

为了清晰记忆上面的规则我来稍微总结一下:

babel 会在当前执行目录搜索 babel.config.js, 若有则读取并作为全局配置,若无则全局配置为空。然后在转换一个具体的js文件时会去判断,如果这个文件在当前执行目录外面,则只应用全局配置。如果这个文件在当前执行路径内,则会去基于这个文件向上搜索最近的一个 .babelrc ,将其与全局配置合并作为转换这个文件的配置。

作者:前端阳光


欢迎关注微信公众号 :前端阳光