[译]在 Babel 中支持 TC39 标准的装饰器

Babel7.jpg

欢迎拍砖,协助咱们更好的建设中文社区。javascript

Babel 7.1.0 最终支持了新的装饰器提案:你可使用 @babel/plugin-proposal-decorators 插件来提早尝试此功能 🎉。html

相关历史

三年多之前,Yehuda Katz 首先提出了装饰器的概念。TypeScript 在 1.5 版本(2015)中发布了对装饰器的支持以及许多 ES6 的相关特性。 一些主流框架,如 Angular 和 MobX 等开始使用它们来增长开发者体验:这使得装饰器很是受欢迎,并给社区带来了一种已经稳定的错觉。java

Babel 第一次实现装饰器是在 v5 版本中,但因为该提案仍在不断变化,则在 Babel v6 中移除了它们。Logan Smyth 建立了一个非官方的插件(babel-plugin-transform-decorators-legacy),它延用了 Babel 5 中装饰器的行为;在 Babel 7 的 alpha 版本发布期间该库被移至 Babel 官方的仓库中。当时该插件仍使用旧的装饰器语法,由于新提案还没有明确。webpack

自那时起,Daniel EhrenbergBrian Terlson 以及 Yehuda Katz 就一块儿成为了该提案的共同做者,该提案几乎已被彻底重写。固然并不是一切事情都已肯定,由于至今还没有出现符合规范的实现方式。git

Babel 7.0.0 为 @babel/plugin-proposal-decorators 插件引入了新的标识:legacy 选项,其惟一有效值为 true。这种突破性变动是必要的,它为提案从第一阶段到当前阶段平稳过渡做铺垫。github

在 Babel 7.1.0 中,咱们引入了对这个新提案的支持,而且当 @babel/plugin-proposal-decorators 插件被使用时,默认启用。而在 Babel 7.0.0 中若是咱们不设置 legacy: true 选项,默认状况下就不能使用该语义(至关于 legacy: false)。web

新提案同时支持使用装饰器实现私有字段(private fields)和私有方法(private methods)。咱们还没有在 Babel 中实现此功能(在每一个 class 中使用装饰器或私有元素),但咱们会很快去出现它。typescript

新提案有何变化?

尽管新提案看起来与旧提案很是类似,但仍是有几个重要的差别使得它们互不兼容。shell

语法

旧提案容许任何有效的左表达式(字面量,函数,类表达式,new 表达式以及函数调用等)用做装饰器主体。有效代码以下所示:npm

class MyClass {
  @getDecorators().methods[name]
  foo() {}

  @decorator
  [bar]() {}
}
复制代码

该语法存在问题:[...] 符号在装饰器内被用做属性访问及定义计算名称。为了防止这种歧义出现,新提案只容许经过点属性访问(foo.bar)能够选择在参数末尾使用(foo.bar())。若是须要使用很复杂的表达式,能够将它们包裹在括号内:

class MyClass {
  @decorator
  @dec(arg1, arg2)
  @namespace.decorator
  @(complex ? dec1 : dec2)
  method() {}
}
复制代码

对象装饰器

旧提案容许除类和类元素装饰器之外的对象成员使用装饰器:

const myObj = {
  @dec1 foo: 3,
  @dec2 bar() {},
};
复制代码

因为与当前对象字面量语义的某些不兼容性,它们已从提案中被移除。若是你的代码中使用了它们,请继续关注,由于它们可能会在后续提案中被从新引入。(tc39/proposal-decorators#119

装饰器函数相关参数

新提案提出的第三个重要变化与传递给装饰器函数参数相关。

在提案的第一个版本中,类元素装饰器接收的参数分别为目标类(或对象),key 以及属性描述符 - 与传递给 Object.defineProperty 的形式相似。类装饰器将目标构造函数(constructor)做为惟一参数。

新的装饰器提案更增强大:元素装饰器会接收一个对象,该对象除更改属性描述符外,还容许更改 key 值,能够赋值(staticprototype 或者 own),以及元素的类型(fieldmethod)。它们还能够建立其余属性并在装饰类上定义运行函数(完成器)。

类装饰器接收一个包含类描述符的对象,使得类在建立以前修改它们成为可能。

升级

鉴于这些不兼容性问题,新提案中不可能使用现有的装饰器:这将使得迁移变得缓慢,由于现有库(MobX,Angular等)没法在不引入这些突破性变化的状况下进行升级。 为解决此问题,咱们发布了实用工具包,它将装饰器包装在你的代码当中。运行后, 你能够安心的更改你的 Babel 配置以便使用新提案 🎉。

使用以下命令来升级你的文件:

npx wrap-legacy-decorators src/file-with-decorators.js --decorators-before-export --write
复制代码

若是你的代码仅在 Node 中运行,或者你会使用 webpack 或 rollup 构建你的代码,则须要使用外部依赖项(external dependency),避免在每一个文件中注入包装函数:

npm install --save decorators-compat
npx wrap-legacy-decorators src/file-with-decorators.js --decorators-before-export --external-helpers --write
复制代码

欲了解更多信息,请参阅工具包(package)相关文档.

开放问题

并不是全部内容都已肯定:装饰器是一个很是强大的功能,想要将装饰器定义为最好的表现形式,是至关复杂的。

导出类(exported class)装饰器如何放置?

tc39/proposal-decorators#69

该问题在装饰器提案中反复被问到:装饰器应该放置在关键字 export 前仍是关键字 export 后?

export @decorator class MyClass {}

// 或者

@decorator
export class MyClass {}
复制代码

根本问题是 export 关键字是不是类声明的一部分,或者它是不是一个"包装器"。第一种状况下,它应该在装饰器以后,由于装饰器出如今声明的起始位置;第二种状况下,它应该在装饰器以前,由于装饰器是类声明的一部分。

如何让装饰器与私有元素安全地交互?

tc39/proposal-decorators#129, tc39/proposal-decorators#133

装饰器引起了一个重要的安全隐患:若是装饰私有元素,那么私有名称(能够视为私有元素的 "key")可能会被泄露。有不一样的安全级别须要考虑:

  1. 装饰器有意外泄露私有名称的风险。恶意代码不该该以任何方式从其余装饰器中"窃取"私有名称。
  2. 只有直接应用于私有元素的装饰器才被视为可信任:类装饰器是否是不该该读写私有元素?
  3. 高度隐私 (class fields 提案的目标之一) 意味着私有元素只能从类内部访问:是否须要让任何装饰器均可以访问私有名称?是否应该只装饰公共元素?

这些问题须要在解决以前进一步讨论,这正是 Babel 所存在的意义。

Babel 的做用

遵循 What's Happening With the Pipeline (|>) Proposal? 文章中的走向,随着 Babel 7 的发布,咱们开始利用咱们在 JS 生态系统中的地位,经过让开发人员可以测试提案的不一样变体,根据他们给出的反馈来帮助提案的做者完善提案。

出于这样的角度,随着 @babel/plugin-proposal-decorators 的更新,咱们引入了新的选项:decoratorsBeforeExport,它容许用户尝试使用 export @decorator class C {}@decorator export default class

咱们还将采用一个选项来定制装饰器私有元素的隐私约束。使用该选项是必要的,直到 TC39 人员对它们作出选择,由此就可让默认行为指定为最终提案中的内容。

若是你直接使用 (@babel/parser,以前的 babylon),你能够在 7.0.0 版本中使用 decoratorsBeforeExport 选项:

const ast = babylon.parse(code, {
  plugins: [
    ["decorators", { decoratorsBeforeExport: true }]
  ]
})
复制代码

用法

用于 Babel 自己:

npm install @babel/plugin-proposal-decorators --save-dev
复制代码
{
  "plugins": ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }]
}
复制代码

查阅 @babel/plugin-proposal-decorators 文档以获取更多相关选项。

你的做用

做为 JavaScript 开发者,你能够帮助规划改语言的将来。你能够为装饰器考虑各类语义环境同时进行测试,并向提案的做者提出反馈。咱们须要知道你在真实项目环境中是如何使用它们的!你还能够经过阅读提案仓库中的 issues 讨论及会议记录来找出为何最终作出这样的设计决策。

若是想当即尝试装饰器,可使用咱们的 repl 配置不一样的 preset 选项进行试用!

关注咱们

扫码关注咱们的公众号,咱们会按期推送一下社区相关的文章和动态。

印记中文
相关文章
相关标签/搜索