一文读懂 babel7 的配置文件加载逻辑

栏目: JavaScript · 发布时间: 7年前

内容简介:近期,在波洞星球的PC官网项目中,我们采用了新版的 babel7 作为 ES 语法转换器。而 babel7 中的一大变更就是对配置文件的加载逻辑进行了改进,然而实际上对于不熟悉 babel 配置逻辑的朋友往往会带来更多问题。本文就是 babel7 配置文件的中文指南,它是英语渣渣的救星,是给懒人送到口边的一道美味。如有错误 概不负责 欢迎指正。babel7 从 2018年3月开始进入 alpha 阶段,时隔5个月直到 2018年8月份 release 第一个版本,目前的最新版是2019年2月26号发布的 7

近期,在波洞星球的PC官网项目中,我们采用了新版的 babel7 作为 ES 语法转换器。而 babel7 中的一大变更就是对配置文件的加载逻辑进行了改进,然而实际上对于不熟悉 babel 配置逻辑的朋友往往会带来更多问题。本文就是 babel7 配置文件的中文指南,它是英语渣渣的救星,是给懒人送到口边的一道美味。如有错误 概不负责 欢迎指正。

前言

babel7 从 2018年3月开始进入 alpha 阶段,时隔5个月直到 2018年8月份 release 第一个版本,目前的最新版是2019年2月26号发布的 7.3.4. 时光如梭,在这美好的 9012 年,ES2019 都快要发布了的时刻,我想: 是时候用一用 babel7 了。

本文不是 babel7 的升级教程,而是对 babel7 的新变化和配置逻辑的一点心得。babel7 对monorepo 结构项目的优化恰好符合我们目前项目架构的预期,这简化了我们配置的复杂度,但其难以理解的配置加载逻辑,却让我踩了不少坑,这也正是本文的来源。

说点变化

在开始讲 babel7 的配置逻辑之前,我们先从以下几个方面来啰嗦几句 babel7 所做的变更及其逻辑意义。

proposal 语法特性

在历史上(babel6)的时代,人们通常使用 babel 提供的 preset-stage 预设来体验 ES6 之后的处于 建议阶段 的语法特性。例如做如下的 babel 配置:

"presets": ["es2015", "react", "stage-0"]

其中,es2015 预设会包含 ES6 标准中所有语法特性;stage-0预设会包含当前(安装该预设npm包的时刻) 的 ES 语法进展中的 stage 0到3的特性(数字小的包含数字大的)。但事实上 babel 官方这样提供 stage 预设,会有 不少问题

例如:

  • 随着 es 标准的不断发展,大量的新特性几乎已经成为标准。与此同时,stage0-3阶段的特性必然也发生变化。可以说,stage0-3的阶段特性他们是不稳定的,极有可能在某个时机被TC39委员会除名、变更阶段、改变语法。尽管 babel-preset-* 预设会跟随TC39 保持一致的更新, 但这样的用法需要使用者也不断保持更新 才能跟标准一致
  • 历史上的 preset-es2015 配合 preset-stage-0 的做法极易产生疑惑,例如没有人知道他所需要的特性在stage几
  • 一个语言特性如果从 stage3 变更为 stage4,往往会导致以前的 stage0(包含了1、2、3) 的配置出问题。因为特性推进后,新的stage0中就不再包含该特性内容,但使用者可能不知道要把该特性所在的 ES标准 加入到配置中
  • 大量的社区工具 eslint 等等都依赖 babel;babel 的 preset-stage 预设更新就会导致这些社区 工具 频频出现问题。

如今,babel 官方认为,把不稳定的 stage0-3 作为一种预设是不太合理的,因此废弃了 stage 预设,转而让用户 自己选择 使用哪个 proposal 特性的插件,这将带来更多的明确性(用户无须理解 stage,自己选的插件,自己便能明确的知道代码中可以使用哪个特性)。所有建议特性的插件,都改变了命名规范,即类似 @babel/plugin-proposal-function-bind 这样的命名方式来表明这是个 proposal 阶段特性。

ES 标准特性

对于正经的 ES 标准特性,babel从6开始就建议使用 babel-preset-env 这个能根据环境进行自动配置的预设。到了 babel7,我们就可以完全告别这几个历史预设了: preset-es2015/es2016/es2017/latest

为什么 preset-env 要更好呢?

我认为,对于开发者而言,关注目标用户平台(兼容哪些浏览器)要比关注 "编译为哪份ES标准" 要更易理解。把选择编译插件的事情交给 preset-env 就好了。它会根据 compat table 和你设置的目标用户平台选择正确的插件。

polyfill

跟 stage 预设的结局一样,对于处于建议阶段的特性,polyfill里面也移除了对他们的支持。

以前的 babel-polyfill 是这么实现的:

import "core-js/shim"; // included < Stage 4 proposals 

import "regenerator-runtime/runtime"

现在的 @babel/polyfill 就直接引入 core-js v2 的属于ES正式标准的模块。这意味着,如果你需要使用处于 proposal 阶段的语法特性,你需要 手工 import core-js 中的对应模块。

命名空间

从 babel7 开始,所有的官方插件和主要模块,都放在了 @babel 的命名空间下。从而可以避免在 npm 仓库中 babel 相关名称被抢注的问题。有必要说一下的,比如 @babel/node @babel/core @babel/clil @babel/preset-env

transform-runtime

以前的 babel-transform-runtime 是包含了 helpers 和 polyfill。而现在的 @babel/runtime 只包含 helper,如果需要 polyfill,则需主动安装 core-js 的 runtime版本 @babel/runtime-corejs2 。并在 @babel/plugin-transform-runtime 的插件中做配置。

说重点: 配置

这是本文的重点,先来看一段 babel7 对配置的变更说明

Babel has had issues previously with handling node_modules, symlinks, and monorepos. We've made some changes to account for this: Babel will stop lookup at the package.json boundary instead of looking up the chain. For monorepo's we have added a new babel.config.js file that centralizes our config across all the packages (alternatively you could make a config per package). In 7.1, we've introduced a rootMode option for further lookup if necessary.

段落的意思大概有这么几点:

  • Babel将停止在package.json边界查找而不是查找链。译者注:这说明以前babel会递归向上查找babelrc 而现在检索行为会停在package.json所在层级。这可以解决部分符号链接的js向上查找babelrc错乱的问题。
  • 添加了一个新的项目全局babel.config.js文件,可以将整个项目的配置集中在所有包中。译者注:除了新增的这个全局配置,也可以同时支持以前的基于文件的.babelrc的配置
  • 引入了一个rootMode选项,以便在必要时按一定策略查找 babel.config.js

除此之外,babel7 还有一个特性是:

  • 默认情况下,不会加载monorepo项目的任何独立子项目中的 .babelrc 文件

然而,对上面的解释,你可能: 每个字都认识,连在一起却不知道在说什么 。下面我们来剖析一下

概念

为了理解 babel7 的配置逻辑,我们就以 babel7 真正所解决的痛点 [monorepo 类型的项目] 为例来剖析。在此之前,我们需要预先确定几个概念。

  • monorepo。这是个自造词。按我的理解,它的含义是说 单个大项目但是包含多个子项目 的含义。如果还是不能理解的话,就把 项目 二字 换成 npm模块包 (以package.json文件作为分界线)。即 单个npm包中又包含多个子npm包 的项目。

    例如,波洞的 PC 版采用的是 Node.js 作为前端接入层的方式,在我们的项目结构组织上,是这样的:

    |- backend
      |-package.json
    |- frontend
      |-package.json
    |- node_modules
    |- config.js
    |- babel.config.js
    |- package.json

    这就是典型的 monorepo 结构。

  • 全局配置。在 babel 文档中又叫 项目级别的配置,特指 babel.config.js。如上图的monorepo结构,其 babel.config.js 就是全局配置/项目配置,该 babel 配置对 backend、frontend、甚至 node_modules 中的模块全部生效。
  • 局部配置。在 babel 文档中可能叫 相对于文件的配置。这种配置就是特指的 .babelrc 或 .babelrc.js 了。他们的生效范围是与待编译文件的位置有关的。

规则

懂了几种配置文件的概念和作用范围之后,我们就可以来根据文档和代码测试结果来精确描述 babel7 的配置规则。这里我们直接以 monorepo 类型项目为例来说,因为普通项目会更简单。

下文中可能用到的名词解释:

我们用 package 来代指一个具有独立 package.json 的项目,如上面案例中的 frontend 可以称作一个 package,backend也可以称作一个package; 我们用 相对配置 这个名词来表达所谓的 .babelrc 和 .babelrc.js,用全局配置来代指 babel.config.js这份配置

对monorepo类型项目,babel7 的处理逻辑是:

【全局配置】全局配置 babel.config.js 里的配置默认对整个项目生效,包括node_modules。除非通过 exclude 配置进行剔除。

【全局配置】全局配置中如果没有配置 babelrcRoots 字段,那么babel 默认情况下不会加载任何子package中的相对配置(如.babelrc文件)。除非在全局配置中通过 babelrcRoots 字段进行配置。

【全局配置】babel 全局配置文件所在的位置就决定了你的项目根目录在哪里,默认就是执行babel的当前工作目录,例如上面的例子,你在根目录执行babel,babel才能找到babel.config.js,从而确定该monorepo的根目录,进而将配置对整个项目生效

【相对配置】相对配置可被加载的前提是在 babel.config.js 中配置了 babelrcRoots. 如 babelrcRoots: ['.', './frontend'],这表示要对当前根目录和frontend这个子package开启 .babelrc 的加载。(注意: 项目根目录除了可以拥有一个 babel.config.js,同时也可以拥有一个 .babelrc 相对配置)

【相对配置】相对配置加载的边界是当前package的最顶层。假设上文案例中要编译 frontend/src/index.js 那么,该文件编译时可以加载 frontend 下的 .babelrc 配置,但无法向上检索总项目根目录下的 .babelrc

实战

还是以上面的代码结构为例。

|- backend
  |-package.json
|- frontend
  |-package.json
|- node_modules
|- config.js
|- babel.config.js
|- package.json

该案例中,我们思考发现, 我们需要利用 babel7 的全局配置能力 。原因在于,monrepo 中存在多个 子 package。由于 babel7 默认检索 babelrc 的边界是 当前package。因此每个package中撰写的babelrc只会对当前package生效,这会导致我们的frontend中依赖根目录的config.js时无法得到正确的编译;另一个问题是: frontend和backend中的相同的babel配置部分无法共享 存在一定冗余。为此,我们需要在项目根目录设置一个 babel.config.js的配置,用它再配合babelrc来做babel配置的共享和融合。

但是,问题很快来了: 当工作目录不在根目录时,无法加载到全局配置 。我们的前端编译脚本通常放置在 frontend目录下,(我们执行编译的工作目录是在 frontend 中),此时 babel build 行为的 工作目录 便是 frontend. 由于 babel 默认只在当前目录寻找 babel.config.js 这个全局配置,因此会导致无法找到根目录的 babel.config.js,这样我们所设想的整个项目的全局配置就无法生效。 幸好,babel7 提供了 rootMode 选项,可以将它指定为 "upward", 这样babel 会自动向上寻找全局配置,并确定项目的根目录位置。

设置方法:

CLI: babel --rootMode=upward

webpack: 在 babel-loader 的配置上设置 rootMode: 'upward'

现在,全局配置有了,我们可以在里面配置 babel 转译规则,它可以对全项目生效,frontend下的 vue.js 编译自然没有问题了。

不过,假设我们 backend 项目中也要使用 babel 转译(目前我们实际在 backend 中并没有使用,因为我们认为只图esmodule而多加一层编译得不偿失),那么 必然 backend 与 frontend 中的编译配置是不同的 ,frontend 需要加载 vue 的 jsx 插件和polyfill (useBuiltIns: usage,modules: false),而backend只需要转译基本模块语法(modules: true, useBuiltIns: false)。该场景的解决方案便是,为每个子 package 提供独立的 .babelrc 相对配置,在全局 babel.config.js 中设置共用的配置。此时项目组织结构如下:

|- backend
  |- .babelrc.js
  |-package.json
|- frontend
  |- .babelrc.js
  |-package.json
|- node_modules
|- config.js
|- .babelrc.js // 这份配置在本场景下不需要(如果根目录下的代码有区别于子package的babel配置,则需要使用)
|- babel.config.js
|- package.json

根目录的 babel.conig.js 配置应该如下:

const presets = [
 // 根、frontend、backend 的公共预设
]
const plugins = [
 // 根、frontend、backend 的公共插件
]

module.exports = {
  presets,
  plugins,
  babelrcRoots: ['.', './frontend', './backend'] // 允许这两个子 package 加载 babelrc 相对配置
}

以为此时已经高枕无忧了?navie,由于我们前端 Vue.js 采用 webpack 打包。实际开发过程中发现,这种配置会造成 webpack 打包模块时出现故障,故障原因在于: 同一个模块中错误混用 esmodule 和 commonjs 语法会造成 webpack故障

前文讲到 全局配置 global.config.js 会作用到 整个项目 ,甚至包括 node_modules。因此babel编译时会同时编译 node_modules 下的模块,虽然模块作者不可能在一个js文件中混用不同模块语法,但他们作为释出包 通常是commonjs的模块语法。 而preset-env预设在编译时会通过 usage 方式 默认注入import语法 的 polyfill

Since Babel defaults to treating files are ES modules, generally these plugins/presets will insert import statements

这便是蛋疼的来源:babel加载过的node_modules模块会变成 同一个js文件里既有commonjs语法又有esmodule语法。

解决方案:不要对 node_modules 下的模块采用babel编译。我们需要在 babel.config.js 配置中增加选项:

exclude: /node_modules/

总结

至此,我们的 monorepo 项目就可以使用一份 全局配置+两份相对配置,实现分别对 前端和后端 进行合理的ES6+语法的编译了。这是我们配置工程师的一小步,但是前端走向未来语法的一大步。

总结 babel7 的配置加载逻辑如下:

待编译文件

以上所述就是小编给大家介绍的《一文读懂 babel7 的配置文件加载逻辑》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

How to Solve It

How to Solve It

Zbigniew Michalewicz、David B. Fogel / Springer / 2004-03-01 / USD 59.95

This book is the only source that provides comprehensive, current, and detailed information on problem solving using modern heuristics. It covers classic methods of optimization, including dynamic pro......一起来看看 《How to Solve It》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具