Babel @babel/plugin-transform-shorthand-properties:把 ES2015 对象简写完整编译到 ES5 的实现全解
【免费下载链接】babel🐠 Babel is a compiler for writing next generation JavaScript.项目地址: https://gitcode.com/gh_mirrors/ba/babel
@babel/plugin-transform-shorthand-properties是 Babel 官方插件包,职责单一而明确:将 ES2015 引入的对象属性简写(shorthand properties)与方法简写(method definition)语法编译为 ES5 兼容的完整key: value形式。本篇以该包的 README 为主体骨架,结合 源码实现 与 全部测试夹具,完整讲清这个插件能编译什么、在 AST 层怎么改、__proto__为什么需要特殊处理,以及它在@babel/preset-env中如何被按目标环境自动启用。
插件定位与包信息
README 对插件的一句话定义是:
Compile ES2015 shorthand properties to ES5
从当前仓库的 package.json 可以确认该包的关键元信息(适用前提:本仓库处于 Babel 8 开发线,以下版本均以仓库实际内容为准):
| 字段 | 值 | 说明 |
|---|---|---|
name | @babel/plugin-transform-shorthand-properties | npm 包名 |
version | 8.0.1 | 当前仓库版本 |
peerDependencies | @babel/core: ^8.0.0 | 必须与 Babel 8 的 core 配套 |
dependencies | @babel/helper-plugin-utils | 仅提供declare包装工具 |
type/main | module/./lib/index.js | ESM 包,构建产物在lib/ |
engines.node | ^22.18.0 \|\| >=24.11.0 | 仓库内运行该包所需的 Node 版本 |
值得注意的是,它唯一的运行时依赖就是@babel/helper-plugin-utils(用于declare声明),真正的所有转换逻辑都写在 src/index.ts 这 45 行代码里,属于典型的"零配置、纯 visitor"型插件。
安装与配置方式
安装
README 给出了两种安装方式:
# npm npm install --save-dev @babel/plugin-transform-shorthand-properties# yarn yarn add @babel/plugin-transform-shorthand-properties --dev在 Babel 配置中启用
从源码中插件的注册名可以直接确定配置写法:src/index.ts 中name字段为"transform-shorthand-properties",因此最简配置为:
// babel.config.js module.exports = { presets: [["@babel/preset-env", { targets: "ie 11" }]], // 通常由 preset-env 自动启用 // 或单独显式启用: plugins: ["transform-shorthand-properties"], };这里的插件名transform-shorthand-properties同时也被@babel/preset-env注册为内置可用插件(见下文"在 preset-env 中的集成"一节)。
它能编译什么:四类典型输入输出
插件的全部行为都可以用仓库自带的 7 组 测试夹具 精确复现。测试入口 test/index.js 通过@babel/helper-plugin-test-runner驱动这些夹具,其 options.json 指定了plugins: ["transform-shorthand-properties"]。下面按夹具逐组给出真实的输入/输出对照。
1. 单个与多个简写属性
shorthand-single/input.js:
var coords = { x };编译为 output.js:
var coords = { x: x };多个简写同理(shorthand-multiple夹具):
// 输入 var coords = { x, y }; // 输出 var coords = { x: x, y: y };2. 简写与非简写混合
shorthand-mixed夹具覆盖了最常见的真实写法——同一对象里既有简写又有显式键值:
// 输入 var coords = { x, y, foo: "bar" }; // 输出 var coords = { x: x, y: y, foo: "bar" };这说明插件只改写"简写"项,显式属性原样保留。
3. 方法简写转 FunctionExpression
method-plain夹具展示了第二类目标语法——对象方法定义:
// 输入 var obj = { method() { return 5 + 5; } }; // 输出 var obj = { method: function () { return 5 + 5; } };方法简写与属性简写是 ES2015 中同一批"对象字面量语法糖"的两半,因此被同一个插件一并处理。
4. 注释保留
shorthand-comments夹具验证了改写不会破坏行尾注释:
// 输入 var A = "a"; var o = { A // comment }; // 输出 var A = "a"; var o = { A: A // comment };由于实现上只是把node.shorthand置为false而不重建节点(见下文源码分析),Babel 生成器会基于原节点重新打印key: value,节点上挂的注释自然得以保留。
源码级实现解析
整个插件只有一个 visitor 集,入口结构如下(src/index.ts):
import { declare } from "@babel/helper-plugin-utils"; import { types as t } from "@babel/core"; export default declare(api => { api.assertVersion(REQUIRED_VERSION("^7.0.0-0 || ^8.0.0")); return { name: "transform-shorthand-properties", visitor: { ObjectMethod(path) { /* … */ }, ObjectProperty(path) { /* … */ }, }, }; });两个细节值得注意:
- 第 5 行 的
api.assertVersion("^7.0.0-0 || ^8.0.0")声明了它对 Babel core 大版本的兼容范围(7 或 8),而包的peerDependencies进一步收紧到^8.0.0,即发布物面向 Babel 8。 - 插件不接受任何选项:源码中没有读取
api.opts的任何代码,它是一个行为固定的转换开关,这也是它配置写法只有"transform-shorthand-properties"一项的原因。
ObjectProperty:把简写"展开"而不是"重建"
第 32–42 行 处理属性简写:
ObjectProperty(path) { const { node } = path; if (node.shorthand) { const computedKey = t.toComputedKey(node); if (t.isStringLiteral(computedKey, { value: "__proto__" })) { path.replaceWith(t.objectProperty(computedKey, node.value, true)); } else { node.shorthand = false; } } }普通情况(如{ x })的处理仅仅是node.shorthand = false——不删除、不新建 AST 节点,只是把"简写标记"翻转。生成器打印时看到shorthand: false就会输出x: x。这正是注释能原样保留的原因,也意味着转换零额外 AST 开销。
if分支则是__proto__特判,见下节专门说明。
ObjectMethod:方法简写降为 FunctionExpression
第 11–30 行 处理kind === "method"的对象方法:
ObjectMethod(path) { const { node } = path; if (node.kind === "method") { const func = t.functionExpression( null, node.params, node.body, node.generator, node.async, ); func.returnType = node.returnType; const computedKey = t.toComputedKey(node); if (t.isStringLiteral(computedKey, { value: "__proto__" })) { path.replaceWith(t.objectProperty(computedKey, func, true)); } else { path.replaceWith(t.objectProperty(node.key, func, node.computed)); } } }可以逐点读出它保留了什么语义:
node.params/node.body原样迁移,方法体不做二次改写(函数体内若有 async/箭头等其余 ES2015 语法,交由各自插件处理);node.generator与node.async标志透传给新的FunctionExpression,即async m() {}/*m() {}改写后仍保持 async/生成器函数(后续由transform-async-to-generator、transform-regenerator等插件接力降 ES5);func.returnType = node.returnType保留了 Flow 类型标注——这被method-type-annotations夹具直接验证:在 options.json 中同时启用syntax-flow插件后,输入method(a: string): number {}会输出为method: function (a: string): number {},类型注解完整保留到后续 strip-types 阶段;t.objectProperty(node.key, func, node.computed)保留了键的 computed 属性,计算键方法不会丢失其"计算"语义。
__proto__特判:为什么简写的__proto__必须变成计算键
两个 visitor 中都有一个相同的分支:用t.toComputedKey(node)把键归一化,若结果是字符串字面量"__proto__",就替换为计算键属性[computed: true]。对应夹具是 proto/input.js:
// 输入 var shorthand = { __proto__, } var method = { __proto__() {} } // 输出 var shorthand = { ["__proto__"]: __proto__ }; var method = { ["__proto__"]: function () {} };这处理的是一个真实的语义陷阱:在 ES5 环境下,普通属性形式{ "__proto__": value }里的"__proto__"会命中Object.prototype.__proto__的存取器,行为等价于Object.setPrototypeOf(obj, value)(且value不是对象时会抛错),而不是创建一个名为__proto__的自有属性。Babel 必须保证 ES5 产物与 ES2015 语义一致——ES2015 规范明确规定对象字面量中的__proto__键要当作普通键处理。唯一能做到"只创建自有数据属性"的 ES5 写法就是计算键["__proto__"]: value,所以源码用t.objectProperty(computedKey, value, true)显式置 computed 为true。这是本插件中最容易写错、也最能体现"编译器正确性"的一个分支。
测试体系:如何用 fixture 复现全部行为
该包的测试入口只有两行(test/index.js):
import runner from "@babel/helper-plugin-test-runner"; runner(import.meta.url);它委托给 babel-helper-plugin-test-runner,后者进一步调用@babel/helper-transform-fixture-test-runner,按test/fixtures/下的目录约定执行:读取各夹具目录的input.js,以options.json中声明的插件集跑一次 Babel 转换,再断言产物与output.js逐字节一致。7 组夹具的职责划分如下:
| 夹具 | 验证点 |
|---|---|
shorthand-single/shorthand-multiple | 单个 / 多个属性简写展开 |
shorthand-mixed | 简写与显式属性混排 |
shorthand-comments | 行尾注释在改写后保留 |
method-plain | 方法简写 →key: function |
method-type-annotations | 方法改写保留 Flow 注解(需syntax-flow) |
proto | __proto__简写与方法特判为计算键 |
如果你要核对某一行为,直接查看对应夹具目录下input.js/output.js即可,无需运行测试。
在 @babel/preset-env 中的集成
这个插件在@babel/preset-env的 available-plugins.ts 中被注册为内置可用插件:第 45 行import transformShorthandProperties from "@babel/plugin-transform-shorthand-properties",第 137 行以键名"transform-shorthand-properties"挂入插件表;preset-env 的 package.json 也将其列为依赖。
从 preset-env 的调试输出夹具可以推断它的自动启用策略:
- browserslist-env/stdout.txt 中显示
transform-shorthand-properties { ie },即针对支持 object-shorthand 特性数据的浏览器列表(此处为 IE)会自动开启该插件; - edge-default-params-chrome-40/stdout.txt 中显示
transform-shorthand-properties { chrome < 43 },即当目标包含低于 Chrome 43 的环境时同样命中。
也就是说,绝大多数使用者并不需要手写plugins配置:只要targets指向不支持对象简写的环境,preset-env会依据特性数据自动启用本插件;只有针对特定文件单独控制、或构建自定义 preset 时,才需要按前文的plugins写法显式列出插件名。
小结:适用边界与使用建议
结合 README 与仓库源码,可以对本插件的使用边界做如下总结:
- 能力范围:只处理对象字面量中的属性简写(
{ x }→{ x: x })与方法简写(m() {}→m: function () {}),不触碰函数体内语法,也不做任何运行时替换——它是纯 AST 改写,产物与 ES5 目标环境(包括 IE)兼容。 - 无选项:插件不接受任何配置参数,启用即全量改写,配置成本为零。
- 正确性要点:
__proto__键被强制改为计算键以保证 ES5 下创建的是自有属性而非触发原型链存取器,这是理解该插件价值的关键细节。 - 版本前提:以当前仓库为准,包版本
8.0.1,peerDependencies要求@babel/core ^8.0.0;在 Babel 7 生态中对应的是同名的@babel/plugin-transform-shorthand-properties7.x 系列。 - 常规用法:面向旧浏览器编译时交给
@babel/preset-env按targets自动调度即可;需要细粒度控制时,在plugins中以插件名"transform-shorthand-properties"显式启用。
【免费下载链接】babel🐠 Babel is a compiler for writing next generation JavaScript.项目地址: https://gitcode.com/gh_mirrors/ba/babel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考