[譯] 誤解 ES6 模塊,升級 Babel 的一個解決方案(淚奔)

誤解 ES6 模塊,升級 Babel 的一個解決方案(淚奔)

說多了都是淚...javascript

2015 年 10 月 29 號Sebastian McKenzieJames Kyle 以及 Babel 團隊的其餘成員,發佈了一個面向各地前端開發者的大型版本:Babel 6.0.0。太棒了,由於它再也不是一個轉譯器,而是一個可插拔的 JavaScript 工具平臺。做爲一個社區,咱們只觸及了它能力的表面,我對 JavaScript 工具的將來感到興奮(謹慎樂觀態度)。html

全部這些都說明了,Babel 6.0.0 是一個很是重大的變革版本。一開始可能有點不穩定。所以升級也並不容易,須要學習。這篇文章不必定會討論如何升級 Babel。我只想討論我從本身代碼中學會的內容 —— 當 Babel 修復了個人嚴重依賴問題時... 在嘗試將 Babel 5 升級到 Babel 6 以前,但願你能夠去閱讀如下內容:前端

ES6 模塊

若是我能夠正確理解 ES6 模塊規範,對我來講,升級就不會那麼困難了。Babel 5 容許濫用 exportimport 語句,Babel 6 解決了這個問題。一開始我覺得這多是 Bug。我在 Stack OverflowLogan Smyth 上提問這個問題,反饋的信息告訴我,我從根本上誤解了 ES6 模塊,並且 Babel 5 滋長了這種誤解(編寫一個轉換器很困難)。android

當前危機

起初,我不太明白 Logan 的意思,但當我有時間全身心投入個人應用升級時,發生了這些事情:webpack

我瘋了麼?這是無效的 ES6 麼?export default { foo: 'foo', bar: 'bar', }ios

— @kentcdoddsgit

Tyler McGinnisJosh Manders 和我在這個線程上測試了一下。這可能很難理解,但我意識到問題不是將對象默認導出,而是如何像預期那樣能夠導入該對象。angularjs

我老是能夠導出一個對象做爲默認值,而後從該對象中經過解構的方式得到我所須要的部分(字段),以下所示:

// foo.js
const foo = {baz: 42, bar: false}
export default foo

// bar.js
import {baz} from './foo'
複製代碼

由於 Babel 5 的轉換是導出默認語句,因此它容許咱們這樣作。然而,根據規範,這在技術上是不正確的,這也是爲何 Babel 6(正確地)刪除了該功能,由於它的能力其實是在破壞我在工做中應用程序的 200 多個模塊。

當我回顧 Nicolás Bevacqua 的博客時,我終於明白了它的工做原理。

固然,也要感謝 @nzgb 在 ES6 上的 350 個使人驚訝的要點,由於它很是清晰ponyfoo.com/articles/es…@rauschma

— @kentcdodds

當我讀到 Axel Rauschmayer博客時,我發現爲何我一直在作內容無效。

我想感謝 @rauschma 用 ES6 模塊將我從早期中年危機中拯救出來。我可能對這事太專一了。。。

— @kentcdodds

基本思想是:ES6 模塊應該是靜態可分析的(運行時不能更改該導出/導入),所以不能是動態的。在上述示例中,我能夠在運行時更改 foo 的對象屬性,而後個人 import 語句就能夠導入該動態屬性,就像這樣:

// foo.js
const foo = {}
export default foo
somethingAsync().then(result => foo[result.key] = result.value)

// bar.js
import {foobar} from './foo'
複製代碼

咱們將假設 result.key 是 ‘foobar’。在 CommonJS 中這很好,由於 require 語句發生在運行時(在模塊被須要的時候):

// foo.js
const foo = {}
module.exports = foo
somethingAsync().then(result => foo[result.key] = result.value)

// bar.js
const {foobar} = require('./foo')
複製代碼

但是,由於 ES6 規範規定導入和導出必須是靜態可分析的,因此你不可能在 ES6 中完成這種動態行爲。

這也是 Babel 作出改變的緣由。這樣作是不太可能的,但這也是件好事。

這意味着什麼?

用文字來描述這個問題確實比較困難,因此我但願一些代碼的示例與對比會有指導意義

我遇到的問題是,我將 ES6 exports 與 CommonsJS require 組合在一塊兒。我會這樣作:

// add.js
export default (x, y) => x + y

// bar.js
const three = require('./add')(1, 2)
複製代碼

Babel 改變後,我有三個選擇:

選擇 1:默認 require

// add.js
export default (x, y) => x + y

// bar.js
const three = require('./add').default(1, 2)
複製代碼

選擇 2:100% 的 ES6 模塊

// add.js
export default (x, y) => x + y

// bar.js
import add from './add'
const three = add(1, 2)
複製代碼

選擇 3:100% 的 CommonJS

// add.js
module.exports = (x, y) => x + y

// bar.js
const three = require('./add')(1, 2)
複製代碼

我如何修復它?

幾小時後我開始運行構建並經過了測試。不一樣的場景,我有兩種不一樣的方法:

  1. 我將導出更改成 CommonJS(module.exports),而不是 ES6(export default),這樣我就能夠像一直作的那樣繼續 require。

  2. 我寫了一個複雜的正則表達式來查找並替換(應該使用一個 codemod)那些將其餘 require 語句從 require(‘./thing’) 轉向 require(‘./thing’).default** 的改變。

它工做的很完美,最大的挑戰就是理解 ES6 模塊規範是如何工做的,Babel 如何將其轉換到 CommonJS,從而實現交互操做。一旦我把問題弄清楚了,遵循這一規則來升級個人代碼就變成了超簡單的工做。

建議

儘可能避免混合 ES6 模塊和 CommonsJS。我我的而言,會盡可能使用 ES6。首先,我將它們混合在一塊兒的緣由之一是我能夠執行單行的 require,並當即使用所需的模塊(好比 require(‘./add’)(1, 2))。但這真的不是一個足夠大的好處(就我我的看來)。

若是你以爲必須將它們組合起來,能夠考慮使用如下 Babel 插件/預置之一:


結論

全部這些真正的教訓是,咱們應該明白事情是如何運做的。若是我理解 ES6 模塊規範其實是如何運做的,我就能夠節省大量時間。

你可能會受益於這個 Egghead.io 課程,我演示瞭如何從 Babel 5 升級到 Babel 6:

egghead.io/lessons/ang…

另外,記住,沒有任何人是完美的,咱們都在這裏學習 :-) Twitter 上見


附錄

更多示例

在對 Babel 進行更改以前,有一個像這樣的 require 語句:

import add from './add'
const three = add(1, 2)
複製代碼

但在 Babel 發生變化以後,Require 語句如今變得就像這樣:

import * as add from './add'
const three = add.default(1, 2)
複製代碼

我想,致使這個問題的緣由是,add 變量再也不是默認導出,而是一個擁有全部命名導出以及 default export 的對象(在默認鍵下)。

命名導出:

值得注意的是,你可使用命名導出,個人建議是在工具模塊中那麼作。這容許你在 import 語句(警告,儘管因爲前面的靜態分析緣由,他看起來並非真正的析構)中執行相似於析構的語法。所以,你能夠那麼作:

// math.js
const add = (x, y) => x + y
const subtract = (x, y) => x - y
const multiply = (x, y) => x * y
export {add, subtract, multiply}

// foo.js
import {subtract, multiply} from './math'
複製代碼

tree shaking 的狀況下,這使人興奮,還很棒。

我的而言,我一般建議對於組件(像 React 組件或 Angular 服務)使用 default export(你知道本身要導入的待定內容,單文件,單組件 😀)。但對於工具模塊,一般有各類能夠獨立使用的純函數。這是命名導出的一個很好的用例。

還有一件事

若是你以爲這頗有趣,那麼你應該會喜歡查看我博客的其餘內容而且訂閱個人最新內容 💌(信息在發送到電子郵件 2 周後,會發布到個人博客)。

TestingJavaScript.com 能夠學習更好、更高效的方法來測試任何 JavaScript 程序。

感謝 Tyler McG

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索