[譯] 在 npm 上啓用現代 JavaScript

在 npm 上啓用現代 JavaScript

現代 JavaScript 語法讓咱們使用較少的代碼作更多的事,然而咱們傳輸給用戶的 JavaScript,有多少是現代的呢?html

過去的幾年中咱們一直在寫現代 JavaScript(或者 TypeScript,它們在轉譯的過程當中編譯爲 ES5。這樣的作法讓 JavaScript 的「最新技術」以比支持舊版瀏覽器時更快的速度向前發展。前端

最近,開發者已經採用差分的打包技術,其中兩個或者更多個不一樣的 JavaScript 文件集被生成到不一樣目標環境中。這個技術最通用的例子是模塊/非模塊模式,它利用原生 JS 模塊(也被認爲『ES 模塊』)支持它的『切割 mustard』測試:支持模塊的瀏覽器請求現代版 JavaScript(~ES2017),同時舊版瀏覽器請求更加厚重的可兼容和編譯的傳統代碼 bundle。爲這套瀏覽器們作編譯,取決於它們支持的 JS 模塊類型,經過 @babel/preset-envtargets.esmodules 選項能夠相對簡單直接地完成編譯。同時 Webpack 插件,就像 babel-esm-plugin 能夠輕鬆生成兩個 JavaScript bundle。vue

鑑於上述狀況,全部博客文章和案例研究哪裏展現了使用這種技術實現的卓越性能和 bundle 尺寸優點?事實證實,發送現代 JavaScript 代碼須要的不只僅是變動咱們的轉譯目標。node

這不是咱們的代碼

當前生成現代 bundle 與傳統相對應 bundle 的解決方案僅僅關注於『業務代碼』——— 咱們寫的應用程序代碼。這些方法目前沒法幫咱們處理從 npm 安裝的源碼 ——— 這是一個問題,由於一些代碼將安裝類型的代碼與編寫類型代碼的比例控制在 10:1 範圍以內。儘管這個比例在每一個工程內都明顯不一樣,咱們總能發現發給用戶的 JavaScript 包含大量的安裝類型的代碼。即便回過頭來看,也有明顯的跡象代表生態系統傾向於安裝現有模塊,而非編寫一次性使用模塊。react

在許多方面,這表明了開源的勝利:開發人員可以在共享代碼的共同價值上作轉譯,並在公共論壇裏對須要解決的問題,合力合做出通用的解決方案。android

『咱們從 npm 安裝的依賴項在 2014 年停滯不前』webpack

事實證實,這個神奇的生態系統也是咱們現代 JavaScript 拼圖所缺失的最重要的部分:咱們從 npm 安裝的依賴項在 2014 年停滯不前ios

「咱們從 npm 安裝的依賴項在 2014 年停滯不前」git

僅僅 JavaScript」

咱們發佈到 npm 的模塊都是『JavaScript』,但那是任何對均勻性抱有期待終將落空的地方。幾乎全球的前端開發者使用來自 npm 的 JavaScript 時都指望 JavaScript 運行『在一個瀏覽器中』。鑑於咱們須要支持各類瀏覽器,咱們最終會遇到這種狀況:模塊須要支持其消費者所用瀏覽器的目標支持版本的最小公分母。這種可能性的產生意味着咱們明確地依賴於 node_modules 中全部代碼需是 ECMAScript 5。在一些不常見的狀況下,開發人員使用 bolted-on 的方法來檢測非 ES5 模塊,而且把這些模塊預處理成他們須要的輸出目標(這裏有個你不應使用的 hacky 方法)。做爲一個社區,每一個新版本 ECMAScript 的向後兼容性使咱們在很大程度上忽略了它對咱們應用程序的影響,儘管咱們編碼的語法與咱們最喜歡的 npm 依賴包中的語法之間的差別愈來愈大。github

這就使得你們廣泛認同:npm 模塊在向倉庫發佈以前須要作模塊轉換。做者的發佈過程通常包括把資源模塊打包成多種格式:JS 模塊、CommonJS 和 UMD。模塊做者有時使用模塊的 package.json 中的一組非官方字段來表示這些不一樣的 bundle,這個文件中 "module" 指向 .mjs 文件,"unpkg" 指向 UMD bundle,同時 "main" 仍被保留爲引用一個 CommonJS 文件。

{
  "main": "dist/es5-commonjs.js",
  "module": "dist/es5-modules.mjs",
  "unpkg": "dist/es5-umd.js"
}
複製代碼

全部這些格式僅影響到模塊的接口 ——— 它的 import 和 export ——— 而且這造成了開發人員和工具之間的一個遺憾的共識:即便現代 JS 模塊也應該被轉譯成庫的最低支持版本。有人建議包做者能夠在入口模塊經過它們的 package.json 中 module 字段標識來開始啓用現代 JavaScript 語法。遺憾的是,這種方法與現在的工具不兼容 ——— 尤爲是,它與咱們配置本身工具的方式不兼容。這些配置對每一個工程都是不一樣的,因爲工具自己並不須要改變,使得配置工程這件事自己就是一項繁複的任務。相反,修改須要放在每一個應用程序的轉譯配置時。

這些約束一直堅挺的緣由很大程度上是因爲像 webpack 和 Rollup 這種主流打包器對是否處理從 node_modules 引入的 JavaScript 這件事並無默認操做。這些工具能夠輕鬆地配置成與原創代碼相同方式處理 node_modules 的代碼,可是它們的文檔一向建議 開發者爲 node_modules 關閉 Babel 轉換。儘管較慢的轉譯過程爲最終用戶產出更好的效果,但上述建議一般在提高轉譯性能時會被說起。這使得從 node_modules 引入的代碼作任何語義上的修改都很是難以在生態系統中傳播,由於這些工具實際上並不控制轉換的內容和方式。這種變化控制位於應用程序的開發者手中,意味着問題是分散的。

模塊做者的觀點

咱們最喜歡的 npm 模塊的做者們也參與了討論。目前,模塊做者們最終被迫在發佈到 npm 以前將包進行 JavaScript 轉換的五個主要緣由是:

  1. 咱們知道應用開發者並無轉換 node_modules 中代碼來匹配他們的支持目標。
  2. 咱們不能依賴應用開發者來設置足夠的代碼壓縮和優化。
  3. 庫的大小必須以 bundled+minified+gzipped 操做以後的字節做爲真實大小。
  4. 以 ECMAScript 5 發佈的 npm 模塊仍被普遍接受。。
  5. 對一個模塊增長 JS 版本的要求意味着某些用戶沒法使用它。

合在一塊兒,這些緣由使得一個流行模塊的做者幾乎不可能轉爲默認使用現代 JavaScript。把你本身放在一個模塊做者的位置來看:在知道更新結果會破壞你大多數用戶的轉譯或者生產部署的狀況下,你會願意發佈僅有現代語法的模塊嗎?

npm 生態系統的當前狀態以及沒法將經典 JavaScript 與現代 JavaScript 分離的問題,都致使咱們沒法徹底擁抱 JS 模塊和 ES20xx。

Module authoring tools hurt, too

就像應用打包器被設置爲對 node_modules 沒有默認操做,改變模塊的創做形式也是一個遺憾的分佈式問題。由於大多數模塊做者傾向於根據不一樣的項目需求推出本身的轉譯工具,所以實際上沒有一套規範工具能夠進行更改。Microbundle 做爲一種共享方案一直在得到關注,還有最近發佈的具備類似優化格式功能的 @pika/pack,模塊能夠經過它發佈到 npm。遺憾的是,這些工具在得以考慮普遍傳播前仍須要走很長的一段路。

假設能夠影響到 Microbundle、Pika 和 Angular 的庫打包器 這樣一組解決方案,或許可使用流行模塊做爲示範來改變生態系統。如此規模的努力可能會遇到模塊使用者的一些阻力,由於許多人尚未意識到他們的打包策略所產生的限制。然而,這些顛覆式的指望正是咱們社區所須要的轉變。

期待

這並非全部的厄運和沮喪。儘管 Webpack 和 Rollup 只是經過它們的文檔來鼓勵未經處理的 npm 模塊,Browserify 實際上在 node_modules 中默認禁用了全部的轉換。這意味着 Browserify 能夠被修改用於自動生成現代/經典 bundle,而無需每個應用開發者更改他們的轉譯配置。類似地,在 Webpack 和 Rollup 上轉譯的腳手架工具也提供一些集中地方,咱們能夠在這裏進行更改,將現代 JS 引入 node_modules。咱們在 Next.jsCreate React App, Angular CLIVue CLI 以及 Preact CLI 中作這個變化,最終的轉譯配置將會使得至關一部分應用程序使用上述這些工具。

絕大多數 JavaScript 應用的轉譯系統是一次性的或者爲每一個項目單獨定製的,沒有統一的中心位置能夠修改它們。一個可被咱們考慮的緩慢地將社區推向現代 JS-friendly 配置方法的選擇是:使得當從 node_modules 導入的 JavaScript 資源未被處理時,修改後的 Webpack 對此顯示警告。去年 Bable 宣佈了一些新功能,容許在 node_modules 中作一些選擇性地轉換,同時 Create React App 工具最近開始使用保守配置來作轉換 node_modules。一樣,能夠建立工具來檢查咱們打包的 JavaScript,看看它有多少是過分填充或低效的傳統語法。

The last piece最後一塊

假設咱們能夠將自動化和指導服務轉譯到咱們的工具中,這樣作最終會將使用這些工具的成千上萬(甚至是百萬)個應用遷移到容許在 node_modules 中使用現代語法的配置上。爲了使這個方法產生效果,咱們須要提出一致的規範來指定他們現代 JS 資源的位置,而且在該上下文中對什麼是『現代』達成共識。對於 3 年前發佈的軟件包,『現代』可能意味着 ES2015。對於一個現今發佈的包,『現代』大概會包括 class fieldsBigInt 或者 Dynamic Import 吧?這很難說清楚,畢竟瀏覽器支持程度、各個規範所處階段都各不相同。

當咱們考慮到對差分打包的影響時,這就變成了一個問題。對於那些不熟悉的人,差分打包指的是一種設置,它容許咱們編寫現代 JavaScript,而後針對不一樣環境轉譯單獨的輸出 bundle 套裝。在最流行的用法中,針對較新瀏覽器咱們有一套包含 ~ES2015 語法的 bundle,而後是針對全部其餘瀏覽器的一套『傳統』bundle,它們被轉換成 ES5 並被填充。

圖表顯示了多個 JavaScript 源文件被打包進入單獨的 JavaScript 文件集:一個用於現代瀏覽器,另外一個用於其餘全部瀏覽器。

問題是:若是咱們假設『現代』意味着『比 ES5 更新的東西』,則沒法肯定一個包中哪些語法應該作轉換以知足給定的瀏覽器支持目標。咱們能夠經過爲包建立一種表達它們所依賴的特定語法功能集的方法來定位上述問題,然而這仍須要維護大量不一樣的配置來控制每組輸入到輸出的語法對:

Package Syntax Output Target Example 「Downleveling」 Transformations
ES5 ES5/nomodule none
ES5 <script type=module> none
ES2015(classes) ES5 / nomodule classes & tagged templates
ES2015(classes) <script type=module> none
ES2017(async/await) ES5 / nomodule async/await, classes & tagged templates
ES2017(async/await) <script type=module> none
ES2019 ES5 / nomodule rest/spread, for-await, async/await, classes & tagged templates
ES2019 <script type=module> rest/spread & for-await

你會怎麼作?

過分轉換的 JavaScript 在咱們發送給最終用戶的代碼中佔比逐漸增長,影響了 Web 應用的初始加載時間和總體運行性能。咱們相信這是一個須要解決的問題 ——— 一個須要模塊做者使用者達成一致的解決方案。問題空間相對較小,可是有許多具備獨特約束條件的有趣部分。

咱們期待社區的幫助。您對在整個 JavaScript 開源生態系統中解決這個問題有何建議?咱們期待收到您的回覆,與您合做,並以可擴展的形式來幫助解決此問題,以便進行新的語法修訂。在 Twitter 上與咱們聯繫:_developitkristoferbaxternomadtechie 都期待參與討論。

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


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

相關文章
相關標籤/搜索