babel 配置文件

作者: shaokang 时间: January 19, 2022字数:3464

1. 前言

最近开发基础库的时候,发现写的 babel 配置不起作用(后来查到原因是 .babelrc 文件不能作用到 node_modules 下的包,如果有该需求可以使用 babel.config.json)。这里借此机会梳理下每个 babel 配置文件的作用。

2. 配置文件类型

先看下官方文档中的介绍,babel 有两种并行的配置文件方式,可以一起使用,也可以单独使用。

  1. 项目范围的配置
  • babel.config.json 文件,以及不同扩展名的文件(.js, .cjs, .mjs)
  1. 相对文件的配置
  • .babelrc.json 文件,以及不同扩展名的文件(.babelrc, .js, .cjs, .mjs)
  • 带有 “babel” key 的 package.json 文件

整段话的意思就是 babel 有两种作用不同项目范围的配置文件,且文件支持不同的扩展名。那接下来就详细看下。

3. 支持的文件扩展名

本质上有两种不同作用的配置文件:babel.config.json 和 .babelrc.json。文件支持 .json,.js,.cjs 和 .mjs 扩展名,出于兼容性原因,.babelrc 是 .babelrc.json 的别名。

babel.config.json 和 .babelrc.json 被解析为 JSON。比如配置这个样子:

{
    "presets": ["env", "react"],
    "plugins": ["transform-class-properties"]
}

官方文档中建议尽可能使用这种文件类型。尽管在复杂配置需要条件表达式时 js 文件更方便,但 js 配置的静态可分析性较差,因此对可缓存性、代码检测、IDE 自动完成等有负面影响。而 json 文件,可能会带来巨大的构建性能优势。

.cjs 后缀问加你可以定义为 Commonjs;mjs 文件可以使用 ECMAScript 模块;.js 后缀取决于 package.json 文件是否包含 “type”: “module”,包含该选项类似于 .mjs,否则与 .cjs 完全相同。

js 文件还可以使用一些函数 API,这里就不赘述,详见配置函数 API

js 文件配置:

module.exports = {
    presets: ['env', 'react'],
    plugins: ['transform-class-properties'],
};

package.json 文件配置:

{
    "name": "demo",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "",
    "babel": {
      "presets": ["env"],
      "plugins": ["transform-class-properties"]
    }
}

4. 作用不同范围的配置

参考文档中的内容,梳理一下知识点。

4.1 概念

  1. monorepo 结构 一个父项目中包含多个子 npm 包。大概的目录结构是:
|- package1
  |- package.json
|- package2
  |- package.json
|- node_modules
|- babel.config.js
|- package.json
  1. 全局配置 特指 babel.config.json 作用的项目级别的配置,即最外层的父级模板范围。

  2. 局部配置 相对于文件的配置,特指的 .babelrc 或 .babelrc.js。

4.2 项目范围的配置

我把内容大概理解为这三点:

  • 对于整个项目范围的配置,babel 提供了 babel.config.json 文件。在不同的子项目中 babel 会在根目录中自动搜索该文件。
  • 当然如果不想在子项目默认搜索该文件,可以将 configFile 选项设置成 false。
  • 此外如果想指定其 babel 配置文件还可以将 configFile 显示设置该选项为某工作目录。

总结:babel.config.js 配置默认对整个项目生效,包括 node_modules,除非通过 exclude 配置进行剔除。如果不想对子项目生效可以将 configFile 设置成 false。

4.3 相对文件的配置

内容也是大概分为两点:

  • .babelrc.json 则作用于相对文件范围。指的是该配置文件仅应用于它们自己包中的文件。
  • 该文件如果不在 Babel 的 “根” 包中将被忽略,除非你选择使用 “babelrcRoots”。

第二点看起来不是很好理解,直接看一个例子:一个项目的目录结构是下面这样

|- src
  |- util
    |- main.js
    |- package.json
    |- .babelrc
|- package.json

这个时候 babel 收不回编译 main.js 的,即使在 util 这一层的 package.json 同级下存在 .babelrc,但由于这个文件不是位于 babel 的 root 目录,所以会被直接忽略掉。

如果想要解决这个问题,需要在 root 目录下创建一个 babel.config.js 文件,配置 babelrcRoots 选项。

module.exports = {
    babelrcRoots: [
        ".",
        "./util",
    ]

因此对于一个 monorepo 项目,想要子文件的 .babelrc 生效,需要额外设置 babelrcRoots。

|- src
  |- packages
    |- package1
        |- main.js
        |- package.json
        |- .babelrc
    |- package1
        |- main.js
        |- package.json
        |- .babelrc
|- package.json
|- babel.config.js
babelrcRoots: ['.', 'packages/*'];

是不是这样就 ok 了呢?如果单个子文件下编译想要享受全局设置的 babel 配置咋办?
比如在 package1 下进行 babel 编译是不会依赖根目录下的 babel.config.js 呢,babel7 提供了 rootMode 选项,可以将它指定为 “upward”, 这样 babel 会自动向上寻找全局配置,并确定项目的根目录位置。

5. babel6 vs babel7

相对文件的配置在 babel6 中,是已经存在的特性,babel7 对这个特性调整地比较大,这是因为 babel6 下的相对文件的配置行为有以下几个问题:

  • babel6 下的 .babelrc 文件,有时候会出人意料地应用到 node_modules 里面的文件;
  • babel6 下的. babelrc 文件,无法应用到 symlinked 的 node_modules;
  • babel6 下,node_modules 的 package 内包含的 .babelrc 文件也会检测到,但是这些配置内依赖的 presets 和 plugins 可能外部主体包都没有安装,而且这些配置内用到的版本很可能也跟外部主体包安装的版本不一致。

问题三我在最近开发过程中也遇到过,项目中用到的 babel6,babel 配置会编译 node_modules 中的文件,由于 node_modules 下的某个 npm 包中包含了 .babelrc 文件,而这个包用到的预设和插件在当前库中都没有,所以导致最后编译时出了问题: Couldn’t find preset “@babel/env”。
解决办法:1.把这个包下的 .babelrc 删去或者重命名 2.升级到 babel7 3.设置 exclude 选项,node_modules 不过 babel 编译。