个人文档
  • AI编程Cursor
  • GPT使用笔记
  • npm常用库合集
  • 同步用
  • 小Demo们
  • 工具网站教程集合
  • HTML、CSS 工具方法集合
    • HTML 全局属性
    • css常用功能
    • font-face 字体|子集相关
    • iframe父子页面传值
    • input输入优化
    • loading状态
    • nodejs使用谷歌邮箱发邮件
    • 为 Dom 自定义事件监听
    • 初始html的head标签配置
    • 拼音输入中文汉字的事件监听
    • 文字颜色效果
    • 文档片段范围 Range
    • 移动端开发-rem
    • 等宽字体推荐
    • 网站SEO优化注意点
    • 邮件html模板
  • JS 工具方法集合
    • Axios 简单使用
    • Axios 简单封装
    • Gitbook的安装和使用
    • Github 登录开发
    • HTML转为纯文本
    • JS 中强大的操作符
    • cookie 操作
    • js 动态加载js资源
    • js 常用功能语句
    • js取代trycatch的方法封装
    • js接口下载二进制
    • script 标签的异步属性
    • 判断当前是移动端还是pc端
    • 刷新token队列管理
    • 前端多线程 Web Worker
    • 加密-AES对称加密
    • 加密-node进行rsa加密解密
    • 地区省市区三级联动的地址数据 + 功能
    • 复制插件
    • 开发时环境变量
    • 得到随机图片
    • 数字格式整理集合
    • 数学计算插件
    • 时间格式整理
    • 获取ip地址
    • 获取url传参
    • 进制转换和位运算符
    • 页面隐藏|激活|关闭的监听
  • JS 知识点研究
    • Babel 历史和原理
    • Babel 配置和使用
    • Function 的 apply、call、bind
    • HTTP浏览器缓存粗解
    • Source map 文件还原为源码
    • TS常用技巧
    • js 的加载和模块化
    • js 的新数据类型 Symbol
    • js的代理对象 proxy 和 defineProperty
    • js的原型链 prototype
    • vite 打包体积优化
    • webpack 可视化打包文件大小插件
    • webpack 基础使用配置
    • webpack 版本5的报错
    • yeoman 开发脚手架的工具
    • 同步异步和微任务宏任务
    • 移动端调试---谷歌工具+eruda+vconsole
    • 转换-Blob URL
    • 转换-FileReader
    • 转换-Js文件类型和转换
    • 转换-前端开发的URL的编码和解码
    • 转换-字符串和Base 64的转换
  • Node 和 Npm 相关
    • Node 开发环境配置
    • express + jwt 校验
    • node 常用方法
    • node后台服务器-PM2
    • node基本使用
    • npm 中依赖的版本问题
    • npm 功能使用
    • npm指令说明和其他对比
    • nvm版本管理+自动切换node版本
  • React 学习
    • React Hook
    • React 项目基础开发
    • React.memo 和 React.PureComponent
    • React懒加载进阶
    • useContext Hook
    • useEffect Hook
    • useMemo 和 useCallback - Hook
    • useRef Hook
    • useState Hook
    • 同步修改变量功能封装 useVal for react
    • 轻便的传值组件
  • Rust 语言相关
    • Rust 基本
    • Rust 基础学习
    • Rust 调用 Object-C 的API
    • Tauri 基本使用
    • Tauri 是什么
  • VUE 学习
    • Vue3 使用
    • Vue3使用hook
    • Vue开发小技术点
    • vue路由切换时的动画效果
    • 花式引入组件和资源-打包时拆包减少js体积
  • Web3相关
    • Web3.0开发上-准备和概念理解
    • Web3.0开发下-功能代码示例
    • 以太坊区块链和Web3.0
    • 开发智能合约
  • python
    • pyenv版本管理工具
    • python初始化
    • python基本概念
    • venv虚拟环境
  • 个人其他
    • Steam Deck的基本设置和插件
  • 其他编程相关
    • Git教程和常用命令
    • Java开发-JDK和Maven的安装和卸载
    • Jenkins安装和基本使用
    • Linux系统指令
    • Mac 使用2K屏幕开启缩放
    • Mac 使用VS code打开项目
    • Mac 安装 Homebrew
    • Mac 的终端 shell 与 zsh
    • Mac 软件和插件
    • MacBook使用建议
    • Mac升降级到指定版本的系统
    • Mac安装Zsh
    • Mac安装软件各种提示
    • Mac系统脚本语言 AppleScript 的使用
    • Mac终端代理工具
    • Markdown(md)文档开发-Typora
    • Mysql 的安装和使用
    • Nginx 安装和基础使用
    • Nginx 稍微高深的配置
    • Slate - Api 的文档开发工具
    • Sublime配置
    • Ubuntu的 apt-get 使用
    • VScode配置
    • Windows 软件和插件
    • curl 工具使用
    • github 网站访问优化
    • host 文件
    • inquirer 终端中和用户交互
    • uTools的插件开发教程
    • vim 文本编辑功能
    • 使用 Github Pages 免费部署网站
    • 压缩指令 zip 和 unzip
    • 油猴的安装和开发(Tampermonkey)
    • 阿里云简略使用
  • 微信开发
    • 微信小程序开发
    • 微信开发必读
    • 微信开发提前购买域名
    • 微信手机打开的页面中授权登录
    • 微信扫码登录
    • 微信服务号登录+推送服务提醒
    • 自定义分享卡片-node.js实现
  • 数据结构与算法
    • KMP算法
    • Wildcard字符串分析算法
    • 二叉树
    • 字典树
    • 时间复杂度浅析
    • 算法神器——动态规划
Powered by GitBook
On this page
  • Babel 历史和原理
  • core-js
  • @babel/polyfill
  • @babel/preset-env
  • runtime 和 @babel/plugin-transform-runtime
  • 总结

Was this helpful?

  1. JS 知识点研究

Babel 历史和原理

title: Babel 历史和原理 id: 2439d67e9f2c71d13d0eb7f012fe2ba6 tags: [] date: 2000/01/01 00:00:00 updated: 2023/03/04 19:29:12 isPublic: true --#|[分隔]|#--

Babel 历史和原理

Babel文档:https://babeljs.io/docs/en/。

只能说,Babel 的历史太厚重了。

之前的配置中,进行编译使用的是 babel-preset-es2015、babel-preset-stage-2、transform-object-rest-spread 、 @babel/polyfill 等包,现在已经不是这些了,有了新的配置方案,见后面。

以下历史参考自 https://juejin.cn/post/6976501655302832159。

core-js

core-js 是 JavaScript 的模块化标准库,包括了 ECMAScript 新 api 的兼容实现。它和 babel 高度集成,是 babel 解决新特性在浏览器中兼容问题的核心依赖。

目前 core-js 的版本是 3.x,与 core-js@2 相比不仅在本身的架构上有重大调整,还对 babel 中的一些插件有重大影响。

这里为了后面方便理解,先说两个名字,一个叫做污染包,一个叫做清洁包,后面马上就会用到。

总之,阅读了下面 的 core-js@2 和 core-js@3 的说明后,会知道 core-js@2 和 core-js@3 各有一个污染包和一个清洁包。

具体污染包和清洁包的实现具体实例,见后面的举例

现阶段能够编译 api 的插件一共有三个,见下:

@babel/polyfill 只能使用 core-js@2 的污染包(@babel/polyfill 包已被 Babel 废弃,不推荐使用)。

@babel/preset-env,根据配置,能使用 core-js@2 或 core-js@3 的污染包。

@babel/plugin-transform-runtime 使用 @babel/runtime-corejs2 或 @babel/runtime-corejs3:

  • @babel/runtime-corejs2 使用 core-js@2 的清洁包。

  • @babel/runtime-corejs3 使用 core-js@3 的清洁包。

core-js@2

core-js@2 被 @babel/polyfill、@babel/preset-env 和 @babel/runtime-corejs2 (最终会由 @babel/plugin-transform-runtime 使用) 引入,来进行新 api 的兼容处理。

其中有两个核心的模块,其实就是一个是污染包,一个清洁包:

  • modules:污染全局的 polyfill 模块,供 @babel/polyfill 和 @babel/preset-env 引入。

    这个包我叫它 污染包,它主要是采用在全局和实例上添加 api 的方式解决兼容性问题,比如要兼容 Array 的 includes 方法,就直接给 Array 的原型链添加这个方法,会修改原型链。

  • library:不污染全局的 runtime 模块,供 @babel/runtime-corejs2 引入。

    这个包我叫它 清洁包,它主要是采用模拟替换api的方式解决兼容性问题,比如发现代码中使用了 [1, 2].includes(2) 方法,就引入一个方法叫做 _includes,然后把代码部分替换为 _includes.call([1, 2], 2) 的方式,不污染原型链。

core-js@3

core-js@3 放弃了对 @babel/polyfill 的支持,被 @babel/preset-env 和 @babel/runtime-corejs3(最终会由 @babel/plugin-transform-runtime 使用) 引入来进行新api的兼容处理。

由于 core-js@2 包的体积太大(约2M),并且有很多重复的文件被引用,所以 core-js@3 对包进行拆分,其中两个核心,仍然一个是污染包,一个清洁包:

  • core-js:污染全局的 polyfill 包,供 @babel/preset-env 引入,等价于 core-js@2/modules,这个包我叫它污染包;

  • core-js-pure:不污染全局的 runtime 包,供 @babel/runtime-corejs3 引入,等价于core-js@2/library,这个包我叫它清洁包。

core-js@2分支已经冻结,不会再添加新特性,新特性都会添加到 core-js@3。

为了可以使用更多的新特性,建议大家使用 core-js@3,但我们其实不会直接使用 core-js@3,我们需要使用的是那些引入了 core-js@3 的插件和配置。

关于 core-js 的内容大家先了解这么多,先有个印象,大家只需要记住一点:corejs 才是 api 兼容实现的提供者!

污染包和清洁包的实际效果举例

使用污染包的编译前后,编译后的使用 require 引入的 js,就是给 Array 添加了 from 这个静态方法:

// 编译前
const array = Array.from([1, 2, 3, 4, 5])
console.log(array?.[6] || '无')

// 编译后
"use strict";

require("core-js/modules/es6.symbol.js");
require("core-js/modules/es6.array.from.js");
require("core-js/modules/es6.string.iterator.js");
require("core-js/modules/es6.object.to-string.js");
require("core-js/modules/es6.array.iterator.js");
require("core-js/modules/web.dom.iterable.js");
var array = Array.from([1, 2, 3, 4, 5]);
console.log((array === null || array === void 0 ? void 0 : array[6]) || '无');

使用清洁包的编译前后,可见对应编译前的 Array.form 方法,编译后是引入了一个 _from 来替换使用。

// 编译前
const array = Array.from([1, 2, 3, 4, 5])
console.log(array?.[6] || '无')

// 编译后
"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _from = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/array/from"));
var array = (0, _from.default)([1, 2, 3, 4, 5]);
console.log((array === null || array === void 0 ? void 0 : array[6]) || '无');

@babel/polyfill

此包已被官方弃用,虽然仍然可以使用,但不建议使用,后面有完全取代的方案。

@babel/polyfill 是一个运行时包,主要是通过核心依赖 core-js@2 来完成对应浏览器不支持的新的全局和实例 api 的添加。

而在升级到 core-js@3 后,如果还要保留 @babel/polyfill 的使用,就要在 @babel/polyfill 中添加 core-js@2 和 core-js@3 切换的选项,这样 @babel/polyfill 中将包含 core-js@2 和 core-js@3 两个包。

出于这个原因,官方决定弃用 @babel/polyfill。

@babel/preset-env

编译语法时,有的环境下可能需要转换几十种不同语法的代码,则需要配置几十个 plugin ,这显然会非常繁琐。

所以,为了解决这种问题,Babel 提供了预设插件机制 preset,preset 中可以 预设置一组插件来便捷的使用这些插件所提供的功能。目前,Babel 官方推荐使用 @babel/preset-env 预设插件。

从 babel@7 开始,已经移除了 @babel/preset-stage-x,所以当前的新版本中直接使用 @babel/preset-env 就行。

@babel/preset-env 主要的作用是用来转换那些已经被正式纳入TC39 中的语法。所以它无法对那些还在提案中的语法进行处理,对于处在 stage 中的语法,需要安装对应的 plugin 进行处理。

意思就是刚刚提出的新语法还无法编译,需要安装语法对应的 plugin 来处理,不过那种太新的语法,不太关注这些的程序员几乎也都不知道。

除了语法转换,@babel/preset-env 另一个重要的功能是对 api 的处理,也就是在代码中引入 polyfill。

但是,@babel/preset-env 默认是不开启处理 api 的功能,只有设置了 useBuiltIns 选项(不为 false )才会开启。

@babel/preset-env 主要还是依赖 core-js 来处理api的兼容性,在升级到 7.4.0 以上的版本以后,既支持 core-js@2,也支持 core-js@3,所以增加了 corejs 的配置来控制所需的版本。

**但注意,根据上面 core-js 的描述,如果使用 @babel/preset-env 来编译API,那只能使用 core-js@2 或 core-js@3 的污染包! **

如果设置了useBuiltIns选项(不为false)就得设置 corejs 版本,否则 babel 将会发出警告。

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage", // 也可配置成 entry,如果配置为 false 则不需要配置 corejs
        "corejs": 3 // 3: 使用 core-js@3,2: 使用 core-js@2
      }
    ]
  ]
}

下面是 useBuiltIns 的三个值的区别说明。

useBuiltIns 设置为 usage

注意,设置了此项,需要同时设置 corejs 选项。

代码中不需要开发人员主动引入 polyfill 垫片,babel 会自动将代码里已使用到、且 browserslist 环境不支持的 polyfill 导入。

// 编译前
const result = [1, 2, 3, 4, 5].copyWithin(0, 3)
const instance = new Promise((resolve, reject) => {
  resolve(123)
})

// 编译后
"use strict";

require("core-js/modules/es.array.copy-within.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");

var result = [1, 2, 3, 4, 5].copyWithin(0, 3);
var instance = new Promise(function (resolve, reject) {
  resolve(123);
});

useBuiltIns 设置为 entry

注意,设置了此项,需要同时设置 corejs 选项。

需要在代码运行之前导入,会将 browserslist 环境不支持的所有 polyfill 都导入。

// 编译前
import "core-js/stable";
import "regenerator-runtime/runtime";
// 上面两行是需要开发人员手动引入的 polyfill
const result = [1, 2, 3, 4, 5].copyWithin(0, 3)

const instance = new Promise((resolve, reject) => {
  resolve(123)
})

// 编译后
"use strict";

require("core-js/modules/es.symbol.js");
// ... 此处省略400+行代码,全是类似上下的导入语句
require("regenerator-runtime/runtime");

var result = [1, 2, 3, 4, 5].copyWithin(0, 3);
var instance = new Promise(function (resolve, reject) {
  resolve(123);
});

useBuiltIns 设置为 false

只做了语法转换,不会导入任何 polyfill 进来,并且 corejs 配置将无效。

// 编译前
const result = [1, 2, 3, 4, 5].copyWithin(0, 3)

const instance = new Promise((resolve, reject) => {
    resolve(123)
})

const shen = result?.a

// 编译后
"use strict";

var result = [1, 2, 3, 4, 5].copyWithin(0, 3);
var instance = new Promise(function (resolve, reject) {
  resolve(123);
});
var shen = result === null || result === void 0 ? void 0 : result.a;

runtime 和 @babel/plugin-transform-runtime

在使用 @babel/preset-env 提供的全局 api 添加的功能时,由于使用的是污染包,难免会造成文件的体积增加以及api的全局污染。

为了解决这类问题,Babel 提供了另一种 api 转译的方式,引入了 runtime 的概念。

runtime

runtime 的核心思想是以引入方法替换的方式来解决兼容性问题。

runtime 包其实有三个:

  • @babel/runtime:只能处理语法替换,且只有在 @babel/preset-env 的帮助下,runtime 包的语法模拟替换功能才会发挥作用,这个包相当于 @babel/preset-env 的语法替换功能的延伸。

  • @babel/runtime-corejs2:相比较 @babel/runtime,增加了 core-js@2 的清洁包,来支持全局构造函数(例如Promise)和静态方法(例如Array.from)兼容。

  • @babel/runtime-corejs3:相比较 @babel/runtime-corejs2,增加了 core-js@3 的清洁包,支持了实例方法的兼容,同时还支持对ECMAScript 提案的 api 进行模拟,例如 [].flat()。

三个包都依赖 helpers、regenerator-runtime 模块来实现语法的替换,helpers中 提供了一些语法模拟的函数,regenerator-runtime 中实现了 async/await 语法的转换。

所以实际使用时,直接使用 @babel/runtime-corejs3 就行,比如要使用数组的 includes 方法,可以像下面这么写:

import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes";

_includesInstanceProperty(foo).call(foo, "a");

但这样一个个手动导入很麻烦,这个时候我们就需要借助自动导入插件来帮助我们完成这项工作。

@babel/plugin-transform-runtime

@babel/plugin-transform-runtime 就是为了方便 @babel/runtime 的使用。通过ast的分析,自动识别并替换代码中的新api,解决手动 require 的烦恼。

@babel/plugin-transform-runtime 是开发依赖,编译时负责处理 @babel/runtime,两者是搭配使用的。

下面是使用 @babel/plugin-transform-runtime 来编译 api 的配置,其中 corejs 选项来配置使用的是 @babel/runtime-corejs2 还是 @babel/runtime-corejs3。

{
  "presets": [
    [
      "@babel/preset-env"
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 3
      }
    ]
  ]
}

总结

目前,Babel 处理兼容性问题有两种方案:

  • @babel/preset-env + corejs@3:实现语法转换 + 在全局和实例上添加api,支持全量加载和按需加载,我们简称 polyfill 方案;

    • 缺点:就是会造成全局污染。

    • 优点:可以根据浏览器对新特性的支持度,来选择性的进行兼容性处理。

  • @babel/preset-env + @babel/runtime-corejs3 + @babel/plugin-transform-runtime:实现语法转换 + 模拟替换 api,只支持按需加载,我们简称 runtime 方案。

    • 优点:不会存在全局污染。

    • 不能根据浏览器对新特性的支持度来选择性的进行兼容性处理,也就是说只要在代码中识别到的 api,并且该api也在 corejs3 的 core-js-pure 包中存在,就会自动替换,这样一来就会造成一些不必要的转换,从而增加代码体积。

两种方案都依赖核心包 corejs@3,只不过依赖的模块(一个污染包一个清洁包)不同,导致实现方式不同。

所以,polyfill 方案比较适合单独运行的业务项目,如果你是想开发一些供别人使用的第三方工具库,则建议你使用 runtime 方案来处理兼容性方案,以免影响使用者的运行环境。

两种配置的模板在 Babel 配置和使用 的文档中。

PreviousJS 知识点研究NextBabel 配置和使用

Last updated 3 months ago

Was this helpful?