Gahing's blog Gahing's blog
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Gahing / francecil

To be best
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 前端基础

  • 应用框架

  • 工程能力

    • 工程效率

    • 编译构建

      • AST解析
      • Bun使用总结
      • ESBuild使用总结
      • Rollup使用总结
      • SWC使用总结
      • Vite使用总结
      • 前端热更新原理剖析
      • babel

        • Babel 使用总结
          • 前言
          • Babel 的组成
            • @babel/preset-env
            • @babel/plugin-transform-runtime
            • 总结
          • Babel 与 webpack
            • 示例 Babel 项目
            • Babel 与第三方依赖
            • 将第三方依赖进行 babel 处理
          • Babel 与 TypeScript
          • 常见问题
            • Q:为什么通常不对第三方依赖做 babel 处理?
            • Q: 如果第三方依赖没有转译 es5,webpack 打包时默认会处理么?会出现什么问题?怎么解决?
            • Q:如果项目 useBuiltIns 配置按需加载,且项目代码中未使用 Map,而第三方库使用了 Map ,目标兼容 ie11 ,会出现什么问题?怎么解决?
            • Q:如何避免不必要的 Polyfill 加载?
            • Q: @vitejs/plugin-legacy 方案还会对第三方库进行处理,具体怎么做的?
            • Q:部署 polyfill.io 这类方案,需要注意什么?
            • Q: 有哪些常用的 browserslist 配置
          • 最佳方案是什么
          • 拓展阅读
      • webpack

    • 工程管理

    • JS 模块化

    • CSS 模块化

    • 工程质量

    • 前端测试

    • CI&CD

  • 应用基础

  • 专业领域

  • 业务场景

  • 大前端
  • 工程能力
  • 编译构建
  • babel
gahing
2022-10-08
目录

Babel 使用总结

# 前言

Babel 是什么?

Babel 是将我们写的 ES6+ 代码,包括语法层(比如 let、class) 和 api 层(比如 Promise、Array.prototype.flat ),转换为向后兼容的代码的工具

在使用 Babel 的过程中,或多或少会有这些疑问:

  1. Babel 怎么用?它有哪些主要模块?
  2. Babel 与 webpack 什么关系?如何单独使用 Babel?如何处理第三方依赖的?
  3. Babel 与 TypeScript 是怎么配合的?

我们带着这些问题开始本文~

# Babel 的组成

  • @babel/preset-env
  • @babel/plugin-transform-runtime

# @babel/preset-env

详见官方文档:https://babeljs.io/docs/babel-preset-env

智能预设,以供项目使用最新的 JavaScript ,包括语法以及可选的 api polyfill。

包含三个关键配置:

  • targets: 指定目标浏览器环境配置,表示这些语法和 polyfill 需要在目标浏览器上可以无错运行。实际项目推荐使用 .browserslistrc 文件配置
  • useBuiltIns: 配置 polyfill 导入策略,包括:
    • false:不引入 polyfill,只转语法,默认值
    • entry:全量导入,严格来说,是导入 targets 所需 polyfill。
      • 使用:除了配置外,还需要入口文件引入 core-js
      • 缺点:包体积较大
      • 优点:可以避免第三方包没有处理 polyfill 或者处理不当,导致引用异常
    • usage:按需导入。
      • 使用:配置即可,无需手动引用模块,babel 会根据用户代码自动导入 polyfill
      • 优点:按需导入包体积较小
      • 缺点:无法处理第三方包的 polyfill 问题
      • 注意:按需引入时,如果无法确定具体的原型方法,则都导入。比如 t.includes 这段代码,不知道是 string 还是 array ,babel 会都导入
  • corejs:配置 corejs 的版本,是否使用提案特性(proposals)

# @babel/plugin-transform-runtime

详见官方文档:https://babeljs.io/docs/babel-plugin-transform-runtime

该插件主要为库开发而生,有两个特点:

  1. 为待 polyfill 的 api 创建沙盒环境,避免污染全局范围:比如库中使用了 Promise ,与外部项目的 Promise Polyfill 不兼容,那么就可以利用该插件给库用到的 Promise 进行沙盒转换,同时不影响外部的 Promise Polyfill
  2. 引用模块 @babel/runtime 以避免编译重复输出:减少包体积

然而,大多数第三方包并没有做 transform-runtime 这个处理,主要有两个原因:

  1. 导致产物体积变大
  2. 三方包并不知道外部项目的目标环境,不知道哪些该转

因此,第三方包一般只转语法,不转 api 。 同时,大家都约定所用语法基于标准,仅当库所用 api 非标准时才自行加 runtime Polyfill。

# 总结

  • 库一般不需要提供补丁,始终由应用方统一提供即可。
  • 应用补丁,使用 @babel/preset-env + useBuiltIns ,根据兼容目标自动获取补丁

# Babel 与 webpack

现在的项目中,好像用 Babel 的时候就得带上 webpack ,实际上不是的。

如果项目已经采用 webpack 等构建工具, 那么可以选择使用 babel-loader ,内部会获取文件内容并调用 @babel/core 进行转换

推荐 babel 配置用 babel.config.js 维护,打包配置用 webpack 维护

如果不是标准项目,仅仅只是为了转换,那么可以选择使用 babel 命令

详见:https://babeljs.io/docs/usage

# 示例 Babel 项目

  1. 安装依赖
yarn add --dev @babel/core @babel/cli @babel/preset-env
1
  1. 配置 babel
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          edge: "17",
          firefox: "60",
          chrome: "67",
          safari: "11.1",
          ie: "11",
        },
        useBuiltIns: "usage",
        corejs: "3.6.5",
      },
    ],
  ],
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  1. 编写代码,并执行命令转换
# 将 src 目录下的 js 文件进行转换,并输出到 lib 目录
./node_modules/.bin/babel src --out-dir lib
1
2

# Babel 与第三方依赖

babel 只处理单文件,因此如果要转译第三方依赖,以及其他文件,就需要用到打包器(比如 webpack)

注意 core-js 的代码逻辑,除了引入 core-js/modules 的代码,还会引入众多 core-js/internals 工具代码(这部分代码量还是挺多的)。

# 将第三方依赖进行 babel 处理

默认情况下不对第三方依赖进行处理 (包括语法转译) ,即 exclude /node_modules/ 。

如果想要 babel 处理第三方模块,需要修改 exclude 逻辑:

{
  exclude: (path) => {
    return /node_modules/.test(path) && !/@gahing\/test-pkg/.test(path)
  },
}
1
2
3
4
5

# Babel 与 TypeScript

对于 typescript 项目,编译采用 tsc 还是 babel?

直接说结论:

  • 大部分项目用 tsc 足以,支持语法转译,polyfill 可以走 core-js 全量引入,担心体积太大可以用 polyfill.io 这类在线方案
  • 有草案阶段语法诉求,可以升级 tsc 版本实现支持;当然也可以走 babel 方案编译
  • 如果走 babel 方案编译,需要注意 babel 主要针对单文件处理,部分 ts 语法无法正常转换

更多细节可以看以下文章:

  • Babel vs. TypeScript: Choosing the right compiler for your project (opens new window)
  • 编译 ts 代码用 tsc 还是 babel? (opens new window)
  • 为什么说用 babel 编译 typescript 是更好的选择 (opens new window)

# 常见问题

# Q:为什么通常不对第三方依赖做 babel 处理?

有以下原因:

  1. 避免增大包体积(重点)以及较长的构建时间。
  2. 社区约定俗成,打包产物需要转成 es5。
  3. 大多数业务对浏览器兼容性诉求没那么高

如果担心的话,可以对第三方依赖做 babel 处理。此外也可以通过一些白屏检测方案来保证不会出问题。

# Q: 如果第三方依赖没有转译 es5,webpack 打包时默认会处理么?会出现什么问题?怎么解决?

基本不会。

webpack 打包时配置 target 为 ['web','es5'],只会影响包裹代码和代码压缩逻辑。压缩过程中有可能会转换语法,但大多数情况下不会。

  • 导致的问题:低版本浏览器解析报错甚至白屏。
  • 解决方案:可以将该包加入 babel 编译

详见这两个知乎问题:

  • https://www.zhihu.com/question/319494477
  • https://www.zhihu.com/question/266814164

# Q:如果项目 useBuiltIns 配置按需加载,且项目代码中未使用 Map,而第三方库使用了 Map ,目标兼容 ie11 ,会出现什么问题?怎么解决?

将导致运行时报错。可以手动添加 Polyfill ,或者改用 entry 的 useBuiltIns 配置

# Q:如何避免不必要的 Polyfill 加载?

有两种解决思路:

  1. 使用 polyfill.io 这类线上方案
    • 原理:根据 UA 动态加载差异 Polyfill
    • 优点:项目中仅需处理语法,无需处理 api polyyfill
    • 缺点:项目代码中的语法还是得转译,转译语法执行性能相对较差
  2. 使用 @vitejs/plugin-legacy 的解决方案
  • 利用 script 的 module/nomodule 特性差异化加载,现代浏览器走 module 逻辑+剩余差异 Polyfill,传统浏览器走语法转译+Polyfill
  • 优点:优先保证大多数现代浏览器的加载体验,少部分浏览器再走语法编译逻辑
  • 缺点:产物文件较多;少部分版本的浏览器存在 ESM 文件重复加载的问题;发展时间短,可能有隐藏问题
  • 更多细节可以看这 2 篇文章:
    • 【原理揭秘】Vite 是怎么兼容老旧浏览器的?你以为仅仅依靠 Babel? (opens new window)
    • 深入浅出 Vite (opens new window)

# Q: @vitejs/plugin-legacy 方案还会对第三方库进行处理,具体怎么做的?

对现代编译的输出代码再次进行 babel 编译而已。

具体代码在这 (opens new window)

// raw 是现代编译的输出代码
const result = babel.transform(raw, {
        babelrc: false,
        configFile: false,
        compact: !!config.build.minify,
        sourceMaps,
        inputSourceMap: undefined, // sourceMaps ? chunk.map : undefined, `.map` TODO: moved to OutputChunk?
        presets: [
          // forcing our plugin to run before preset-env by wrapping it in a
          // preset so we can catch the injected import statements...
          [
            () => ({
              plugins: [
                recordAndRemovePolyfillBabelPlugin(legacyPolyfills),
                replaceLegacyEnvBabelPlugin(),
                wrapIIFEBabelPlugin(),
              ],
            }),
          ],
          [
            (await import('@babel/preset-env')).default,
            createBabelPresetEnvOptions(targets, {
              needPolyfills,
              ignoreBrowserslistConfig: options.ignoreBrowserslistConfig,
            }),
          ],
        ],
      })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# Q:部署 polyfill.io 这类方案,需要注意什么?

polyfill.io 根据 ua 对比自动下发差异,提供开源部署方案 (opens new window),使用时需要注意:

  • 时刻关注 browserlist 变化
  • 需要部署 CDN 边缘服务
  • 异常 UA (比如国产浏览器)无法识别,只能走降级兜底策略
  • 不能处理语法层面

# Q: 有哪些常用的 browserslist 配置

// .browserslistrc
// 现代浏览器
last 2 versions and since 2018 and > 0.5%
// 兼容低版本 PC 浏览器
IE >= 11, > 0.5%, not dead
// 兼容低版本移动端浏览器
iOS >= 9, Android >= 4.4, last 2 versions, > 0.2%, not dead
1
2
3
4
5
6
7

# 最佳方案是什么

目标:尽可能快的打包,尽可能少的加载产物,尽可能快的执行代码,绝对安全的加载代码(避免第三方依赖语法和 api 缺失的报错问题)

一个终极方案是:

  1. 打包不处理 Polyfill ,走 polyfill.io :少掉分析步骤,尽可能快的打包,尽可能少的加载产物
  2. 业务产物走 script 的 module/nomodule 特性差异化加载方案(类 @vitejs/plugin-legacy 方案):尽可能快的执行代码,绝对安全的执行代码
  3. 线上真机白屏检测卡点:绝对安全的执行代码

当然,要完成这套方案成本较高,可以根据各自公司基建能力和业务诉求来做,最合适的才是最佳的。

成本最低效果又不错的方案就是直接使用 @vitejs/plugin-legacy:尽可能快的执行代码,绝对安全的加载代码,相对快的打包、相对少的加载产物

# 拓展阅读

  1. Babel学习系列 (opens new window)
  2. 不要肆无忌惮地在你的项目中使用 ES78910 了 (opens new window)
编辑 (opens new window)
上次更新: 2024/09/01, 23:56:56
前端热更新原理剖析
Webpack之TreeShaking

← 前端热更新原理剖析 Webpack之TreeShaking→

最近更新
01
浅谈代码质量与量化指标
08-27
02
快速理解 JS 装饰器
08-26
03
Vue 项目中的 data-v-xxx 是怎么生成的
09-19
更多文章>
Theme by Vdoing | Copyright © 2016-2024 Gahing | 闽ICP备19024221号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式