[譯]在 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 選項進行試用!

關注咱們

掃碼關注咱們的公衆號,咱們會按期推送一下社區相關的文章和動態。

印記中文
相關文章
相關標籤/搜索