說明:本文發佈以後,此問題的推動峯迴路轉,不停有新內容。文末新增一節 Updates,跟進本文發佈以後的 ES Module 標準化進展狀況。javascript
瀏覽器大戰多年了熱度依舊高漲,你們終於在 JS 新特性的部署上達成一致紛紛追趕最新標準,然而 ES2015 中的 ES Module 這個萬衆期待的重要特性卻始終遲遲未能實現。php
等 2020 年回望歷史,假若咱們錯過了 ES Module 這艘船而 Node.js 死在汪洋大海之中,沒有任何其餘技術問題的重要性能夠與此相比。
-- issac
Module 的規範是完工了的,只是對於模塊如何加載和解析留給了「實現環境決定」——按歷史經驗,問題每每就出如今這一環。固然了不是燙手山芋 W3C 也不會就這麼輕鬆甩開對吧,事實上這也不是 W3C 一家的事情,牽涉到 TC3九、Node 技術委員會、Node 和前端兩個開發社羣,以及 npm 公司。html
故事很長,咱們從頭提及。import
和 export
的語法規範很明確,模塊的解析器 V8 早已實現,萬事俱備只欠加載。區區加載能有多麻煩?前端
在新規範下,JavaScript 程序劃分紅兩種類型:腳本(咱們之前寫的傳統JS)和模塊(ES規範中新定義的 Module),模塊有四項於腳本不一樣的特性:java
import
導入其餘 Module 的 bindingexport
導出本 Module 的 binding看上去規則簡單明白,可是要讓一個解析器(parser)區分兼容這兩種模式還挺複雜的。node
看看代碼中是否包含import
和export
關鍵字不就能夠判斷它的類型了麼?
不行。首先猜想用戶意圖是個危險行爲,若是你猜對了,就更加掩蓋了猜錯可能會形成的風險。git
而嚴格模式,除了運行時的一些要求以外還定義了幾個語法錯誤:es6
with
關鍵字;010
);implements
、interface
、let
、package
、private
、protected
、public
、static
或 yield
做爲標識符。這些語法錯誤須要在解析時就拋出來。因此若是以腳本模式解析到了文件末尾才發現有 export
,就得從頭從新解析整個文件來捕捉上述語法錯誤。github
那咱們換一條路,開始先假定爲模塊進行解析代碼。既然 Module 語法至關於嚴格模式 + 導入導出 (import
和 export
),咱們能夠用腳本模式 + 導入導出的語法來解析整個文件。然而這種解析規則已經超越了規範定義,這麼扭曲的路線能夠預見它成爲 Bug 源泉的樣子。npm
危險但不是不可能。OK 真正的麻煩來了:按照規範 import
和 export
都是可選的——你能夠寫一個 Module,既不導入也不導出任何東西,它只是對全局做用域作些小動做,好比這樣:
// 一個合法的 Module window.addEventListener("load", function() { console.log("Window is loaded"); }); // WAT!
總的來講,包含 import
或 export
代表它必定是個 Module,但沒有這兩個關鍵字卻不能證實它不是 Module。 ╮(╯_╰)╭
區分 JavaScript 文件類型的任務無法放在解析器裏自動完成,咱們須要在解析文件以前就知道它的類型。
這就是爲何瀏覽器的模塊引用是這個寫法:
<script type="module" src="foo.js"></script>
當瀏覽器開始加載這個 foo.js
,它會邊加載邊解析,碰到 import { bar } from './bar.js'
的第一時間開始加載依賴的 bar.js
,加載完以後對其解析,檢查其中是否導出了 bar
。如此往復完成整個 Module 的解析。
到了 Node.js,新的問題來了。
做爲世界上最大的軟件包倉庫,npm 中現有的軟件包都是 CommonJS 規範。ES Module 須要可以與 CommonJS 模塊共存,容許開發者們逐步轉向新的語法。
所謂的共存,主要是指 import { foobar } from 'foobar'
語法要支持 CJS Module 和 ES Module 兩種包格式——若是 import
只能用來導入 ES Module 而 require
能夠導入任意模塊,那麼全部人都會用 require
;若是 import
和 require
各自負責導入各自的格式,那麼開發者就須要知道全部依賴的庫的格式,使用相應語法來導入它,而且在依賴的庫們更換到新格式的時候修改本身的代碼去兼容……在可預見的 CommonJS -> ES Module 漫長過渡期裏這樣的負擔對社區而言不可接受。
爲此社區提出了很多方案,(好消息)通過大量的討論以後如今已經集中到兩個選擇還在討論:
"use module"
標註。一想到 JS 的將來永遠都要在文件開頭貼這麼個膏藥你們就不能忍了。否認。.jsm
。主要問題是現有社區工具鏈所有須要更新才能支持,另外和瀏覽器實現的統一也要考慮。package.json
上發揮。這個門類下的提議就更多了,好比添加一個 module
字段逐步替代掉 main
:{ // ... "module": "lib/index.js", "main": "old/index.js", // ... }
這個方案只適用單入口的狀況,對多文件(好比 require('foo/bar.js')
的場景)就不行了。那就改爲 modules
字段(複雜度陡升):
{ // ... // files: "modules": ["lib/hello.js", "bin/hello.js"], // directories: "modules": ["lib", "bin"], // files and directories: "modules": ["lib", "bin", "special.js"], // if package never uses CJS Modules "modules": ["."], }
這還沒完,更多方案就不詳述了,你們能夠到 Node.js Wiki 上查看。
就我的偏好而言,儘管全部的方案都有利有弊,而 package.json
這條路爲了兼容各類需求,修改版的提案已經愈來愈複雜,比較起來 .jsm
後綴卻是愈發顯得簡單清晰了。我更喜歡這個乾淨的解決方案。
<script type="module" />
已經加入 HTML 規範,WhatWG 剛剛發了一篇文章講述他們如何通過堅苦卓絕的努力達成這一目標,接下來就看瀏覽器廠商實現了。
除此以外 WhatWG 手上還有一個 ES Module loader 規範,用於指定 Module 的動態加載方式。它曾經是 ES6 草案的一部分,但由於 ES2015 「要趕着發佈來不及了」不幸被砍,目前歸屬 WhatWG 推動。
Node.js 這邊,在至關一段時間裏咱們還要藉助 transpiler 來體驗 ES Module。這件事須要 V八、Node.js、WhatWG 共同協調完成。
按計劃本月 Node.js 發佈 6.0,順利的話能夠 肯定集成 V8 5.0(BTW,一天後 V8 發佈了 5.1),對 ES2015 的特性支持達到 93%——看來 ES Module 極可能會成爲 「The last ES2015 feature」 了。
關注 ES Module 的進展,還能夠看看幾個地方:
願 ES Module 早日到來。
關於 ES Module 在 Node.js 環境下的識別方案,從一月份 bmeck 提出提案開始社區就持續溝通和爭論,如下是相關進展更新。
.mjs
),社區討論開始。.mjs
派和 package.json
派,然而這兩派的爭論很是激烈。.mjs
已經在正式提案中,假若討論持續僵持不下,不出意外 .mjs
將會隨着時間推移而正式成爲規範。懷着這樣的危機感,package.json
派發起了 In defense of dot js 來抗衡 .mjs
的提案,要求保持 .js
後綴不變而使用 package.json
來識別 ES Module。exports
語句再也不是可選的,至少有一句 exports {}
來代表該文件是個 ES Module),兩派的爭論就這麼迎刃而解了!import()
做爲動態加載的方案,有望取代 System.import()
或 System.loader.import()
。import()
。2016.09.30
TC39 9月碰頭會的與會者紛紛表示此次會議進展使人愉快,會議內容彙總在此,以及一些補充。
對模塊類型的檢測目前是三個方案選項:
.mjs
的方案最簡單,看來是最可行的,並且也跟 Node.js 現有方式一致(用後綴 .node
、.json
、.js
來區分加載類型)。除非 Unambiguous JavaScript Grammar 的實現問題都解決掉,不然最終方案就是它了。import()
你們都以爲沒問題,穩步推動中。2017.02.12
Node.js CTC 和 TC39 的討論:
import { foo } from 'node-cjs-module'
也不符合規範,想 import
一個 NCJS 模塊的話只能 import m from 'node-cjs-module'
而後 m.foo()
調用。.mjs
是問題最少的選擇。.mjs
加載器的原型,在 Node.js v9 中能夠用 flag 的方式啓用,(但願)在 v10 中正式推出。也就是還有一年的時間,一切順利的話 2018 年 4 月就能看到 ES Module 正式加入 Node.js LTS。2017.05.11
工具鏈對 .mjs
後綴的支持都在推動中:
--experimental-modules
開啓)--experimental-modules
開啓。目前的計劃是遇上 4 月份 Node.js 12 發佈,最終在 2019 年 10 月進入 LTS。