- 原文地址:Misunderstanding ES6 Modules, Upgrading Babel, Tears, and a Solution
- 原文做者:Kent C. Dodds
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:Starrier
- 校對者:SinanJS,caoyi0905
說多了都是淚...javascript
在 2015 年 10 月 29 號,Sebastian McKenzie、James Kyle 以及 Babel 團隊的其餘成員,發佈了一個面向各地前端開發者的大型版本:Babel 6.0.0。太棒了,由於它再也不是一個轉譯器,而是一個可插拔的 JavaScript 工具平臺。做爲一個社區,咱們只觸及了它能力的表面,我對 JavaScript 工具的將來感到興奮(謹慎樂觀態度)。html
全部這些都說明了,Babel 6.0.0 是一個很是重大的變革版本。一開始可能有點不穩定。所以升級也並不容易,須要學習。這篇文章不必定會討論如何升級 Babel。我只想討論我從本身代碼中學會的內容 —— 當 Babel 修復了個人嚴重依賴問題時... 在嘗試將 Babel 5 升級到 Babel 6 以前,但願你能夠去閱讀如下內容:前端
若是我能夠正確理解 ES6 模塊規範,對我來講,升級就不會那麼困難了。Babel 5 容許濫用 export 和 import 語句,Babel 6 解決了這個問題。一開始我覺得這多是 Bug。我在 Stack Overflow 和 Logan Smyth 上提問這個問題,反饋的信息告訴我,我從根本上誤解了 ES6 模塊,並且 Babel 5 滋長了這種誤解(編寫一個轉換器很困難)。android
起初,我不太明白 Logan 的意思,但當我有時間全身心投入個人應用升級時,發生了這些事情:webpack
我瘋了麼?這是無效的 ES6 麼?export default { foo: 'foo', bar: 'bar', }ios
— @kentcdoddsgit
Tyler McGinnis、Josh 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。
當我讀到 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、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。