- 原文地址:Misunderstanding ES6 Modules, Upgrading Babel, Tears, and a Solution
- 原文作者:Kent C. Dodds
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:Starrier
- 校對者:SinanJS,caoyi0905
誤解 ES6 模組,升級 Babel 的一個解決方案(淚奔)
說多了都是淚...
在 2015 年 10 月 29 號,Sebastian McKenzie、James Kyle 以及 Babel 團隊的其他成員,釋出了一個面向各地前端開發者的大型版本:Babel 6.0.0。太棒了,因為它不再是一個轉譯器,而是一個可插拔的 JavaScript 工具平臺。作為一個社群,我們只觸及了它能力的表面,我對 JavaScript 工具的未來感到興奮(謹慎樂觀態度)。
所有這些都說明了,Babel 6.0.0 是一個非常重大的變革版本。一開始可能有點不穩定。因此升級也並不容易,需要學習。這篇文章不一定會討論如何升級 Babel。我只想討論我從自己程式碼中學會的內容 —— 當 Babel 修復了我的嚴重依賴問題時... 在嘗試將 Babel 5 升級到 Babel 6 之前,希望你可以去閱讀以下內容:
ES6 模組
如果我可以正確理解 ES6 模組規範,對我來說,升級就不會那麼困難了。Babel 5 允許濫用 export 和 import 語句,Babel 6 解決了這個問題。一開始我以為這可能是 Bug。我在 Stack Overflow 和 Logan Smyth 上提問這個問題,反饋的資訊告訴我,我從根本上誤解了 ES6 模組,而且 Babel 5 助長了這種誤解(編寫一個轉換器很困難)。
當前危機
起初,我不太明白 Logan 的意思,但當我有時間全身心投入我的應用升級時,發生了這些事情:
我瘋了麼?這是無效的 ES6 麼?export default { foo: 'foo', bar: 'bar', }
Tyler McGinnis、Josh Manders 和我在這個執行緒上測試了一下。這可能很難理解,但我意識到問題不是將物件預設匯出,而是如何像預期那樣可以匯入該物件。
我總是可以匯出一個物件作為預設值,然後從該物件中通過解構的方式獲得我所需要的部分(欄位),如下所示:
// 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。
當我讀到 Axel Rauschmayer 的部落格時,我發現為什麼我一直在做內容無效。
我想感謝 @rauschma 用 ES6 模組將我從早期中年危機中拯救出來。我可能對這事太專注了。。。
基本思想是: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)
複製程式碼
我如何修復它?
幾小時後我開始執行構建並通過了測試。不同的場景,我有兩種不同的方法:
-
我將匯出更改為 CommonJS(module.exports),而不是 ES6(export default),這樣我就可以像一直做的那樣繼續 require。
-
我寫了一個複雜的正規表示式來查詢並替換(應該使用一個 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:
另外,記住,沒有任何人是完美的,我們都在這裡學習 :-) 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 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。