當前位置:
首頁 > 科技 > ES6 modules 即將到來,現在該考慮新的打包方案了嘛?

ES6 modules 即將到來,現在該考慮新的打包方案了嘛?

前言

震驚,也就這幾年的時候,這迭代變更的時候驚嘆了。今日早讀文章由@趙飛翻譯授權分享。

正文從這開始~

近年來,構建高性能JavaScript應用是一個複雜的工程。幾年前,從為了節省HTTP開銷做代碼合并開始到壓縮混淆變數名來擠出最後一bit的代碼放在我們的工程里。

現在我們需要tree shaking我們的代碼以及打包我們的模塊,然後回過頭來,為了不阻塞主進程加快首屏載入速度做代碼拆分。我們同時也更換了所有的東西:使用上未來的一些特性?答案是肯定的,這得歸功於Babel!

ES6 modules(以下稱ESM)已經定義到了ECMAScript規範裡面有段時間了。社區的人們也寫了很多文章佈道怎麼配合Babel來使用以及說明了import和node里的require的區別。但是在瀏覽器端完整的實現還需要一點時間。

很欣喜的看到Safari第一個在其工程預覽版搭載ESM),現在Edge 和 Firefox Nightly也搭載了這個功能。在經歷過使用像RequireJs Browserify(還記得AMD和common js之爭嗎)似乎modules終將到達瀏覽器領地,那麼就讓我們來看看未來將會帶給我們什麼吧

常規的方案

通常構建web應用的方式是載入Browserify, Rollup 和 Webpack (or 或者市面上的其他工具)構建出來的bundle。一個經典網站(非SPA)就是由一個服務端渲染的HTML裡面載入了一個單文件的JS bundle。

ES6 modules tryout

上面個包含了三個js文件的bundle是有Webpack打包,這些文件都使用了ESM(ES6 modules)

// app/index.js

importdep1from ./dep-1 ;

functiongetComponent(){

varelement=document.createElement( div );

element.innerHTML=dep1();

returnelement;

}

document.body.appendChild(getComponent());

// app/dep-1.js

importdep2from ./dep-2 ;

exportdefaultfunction(){

returndep2();

}

// app/dep-2.js

exportdefaultfunction(){

return Hello World, dependencies loaded! ;

}

app運行的結果是 『Hello world』,說明所有的文件已經載入了。

裝載bundle

// webpack.config.js

constpath=require( path );

constUglifyJSPlugin=require( uglifyjs-webpack-plugin );

module.exports={

entry: ./app/index.js ,

output:{

filename: bundle.js ,

path:path.resolve(__dirname, dist )

},

plugins:[

newUglifyJSPlugin()

]

};

這三個文件實際上很小總共才347 個位元組。

$ ll app

total24

-rw-r--r--1stefanjudis staff 75B Mar1619:33dep-1.js

-rw-r--r--1stefanjudis staff 75B Mar721:56dep-2.js

-rw-r--r--1stefanjudis staff 197B Mar1619:33index.js

當我幫用Webpack跑一遍後我得到的bundle大小是865位元組,裡面大概有500位元組的母版。這些額外的位元組是可以接受的,因為和我們平時在大多數生產環境放的代碼比不算什麼。感謝Webpack,我們能提前用上 ESM。

$ webpack

Hash:4a237b1d69f142c78884

Version:webpack2.2.1

Time:114ms

Asset Size Chunks Chunk Names

bundle.js856bytes[emitted]main

[]./app/dep-1.js78bytes{}[built]

[1]./app/dep-2.js75bytes{}[built]

[2]./app/index.js202bytes{}[built]

新方案,使用原生支持的ESM

對於所有的不支持ESM的瀏覽器我們有「傳統的bundle」,但是現在我們要玩一些更酷的東西了。首先在index.html裡面添加一個新的script標籤指定其類型為ESM:type="module"

ES6 modules tryout

但我們打開Chrome的時候,我們看到好像沒有什麼事情發生。

bundle首先載入了,展示出了「Hello World」,僅僅這樣而已。但是這就是瀏覽器的高明之處,他會忽略掉他不理解的標記類型而不是拋出錯誤。Chrome也一樣他忽略了他不知道類型的script標籤。

現在,我們來看看Safari預覽版的效果:

很遺憾,沒有展示出「Hello World」。原因在於webpack打包的文件和ESM的不同:webpack能在構建時候就為我們找到文件依賴,但是ESM需要我們手動的去定義準確的文件目錄。

// app/index.js

// This needs to be changed

// import dep1 from ./dep-1 ;

// This works

importdep1from ./dep-1.js ;

調整之後工作正常了,期望的是Safari 預覽版,載入bundle和三個獨立的modules,也就是說程序會執行兩次。

解決這個問題可以使用 nomodule屬性,它可以在script標籤上控制bundle的載入。

這個屬性的細則很快也會出來,,一月底Safari預覽版已經支持了。它會告訴不支持ESM的Safari在ESM不能執行時才執行回退的bundle。

ES6 modules tryout

很好,有了這個組合我們既可以在不支持的瀏覽器里載入經典的bundle也可以在支持的瀏覽器載入ESM。

modules和script的不同

這裡有一些陷阱。首先跑在ESM下的JS和一般的script標籤裡面的些許不同。Axel Rauschmayer總結了幾點不同在他的新書 《探索ES6》裡面。我推薦大家去看看,在這裡我列舉一下主要的不同。

ESM 默認在嚴格模式下運行(無須指明『use strict』)

頂級This是undefined

頂級變數是相對於module的局部變數

ESM是在瀏覽器解析萬HTML載入並且非同步執行的。

我覺得這些特點有很大的優勢,Modules是局部的,就不需要在外麵包一層IIFE,也不用擔心全局變數污染,還能少些很多『use strict』 聲明。。

從頁面性能來看(這個可能是最重要的點)模塊默認是懶載入和懶執行。這樣當我們使用type="module"時候,就避免了意外加入一段阻塞頁面的script的可能並且也將不會有SPOF問題。當然我們也可以設置非同步屬性:async,他將替代默認的延遲屬性。但是還是得強調,延遲是一個好的選擇。

console.log( js module );

console.log( standard module );

壓縮 ES6代碼

還沒完,我們剛剛給出了一個壓縮後的bundle給Chrome,也給Safari提供了獨立的沒有壓縮的文件。但是我們怎麼讓他們體積變小呢?UglifyJS可以勝任這份工作嗎?

事實證明不行,它還不能完全處理ES6的代碼。UglifyJS有一個harmony版本,但是在發稿前我試了還不能很好的壓縮我的那仨個文件。

$ uglifyjs dep-1.js-o dep-1.min.js

Parse error at dep-1.js:3,23

exportdefaultfunction(){

^

SyntaxError:Unexpected token:punc(()

// ..

FAIL:1

但是,如今UglifyJS幾乎在每一種工具鏈裡面都能看到,怎麼就不能處理用ES6寫的代碼呢?

通常的工作流是先用Babel這樣的工具將其轉化為ES5代碼,然後再由UglifyJS來壓縮ES5的代碼。但是在這裡,我想跳過這個步驟,我們在處理未來的問題。Chrome的ES6覆蓋率達到97% Safari 預覽版已經 100%了。

後來,我在Twitter圈裡面問有沒有好的辦法來壓縮ES6代碼。Lars Graubner 給我介紹了Babili,使用它我們很容易壓縮我們的ES6 模塊。

// app/dep-2.js

exportdefaultfunction(){

return Hello World. dependencies loaded. ;

}

// dist/modules/dep-2.js

exportdefaultfunction(){return Hello World. dependencies loaded. }

使用Babili CLI ,就更加方便了:

$ babili app-d dist/modules

app/dep-1.js->dist/modules/dep-1.js

app/dep-2.js->dist/modules/dep-2.js

app/index.js->dist/modules/index.js

結果為:

$ ll dist

-rw-r--r--1stefanjudis staff 856B Mar1622:32bundle.js

$ ll dist/modules

-rw-r--r--1stefanjudis staff 69B Mar1622:32dep-1.js

-rw-r--r--1stefanjudis staff 68B Mar1622:32dep-2.js

-rw-r--r--1stefanjudis staff 161B Mar1622:32index.js

可以看到,壓縮後的bundle依然有850B,但是單獨三個文件加起來才300B。這裡我忽略了GZIP壓縮,因為這對於小文件來說影響甚微。

用rel=preload? 加速ES6 modules

壓縮單文件取得成功,這是一個 298B vs. 856B的優化。但是,我們還可以做得更好,讓其速度更快。使用ESM我們可以載入更少的代碼,但是當我們打開調試面板的瀑布圖,我們可以看到文件請求是根據模塊定義的依賴鏈循序的載入。

我們可不可以添加一個標籤 用來提前告訴瀏覽器未來會發出一些額外的請求? Addy Osmani 的 Webpack preload plugin就是干這個的。那麼有沒有類似的東西應用到ESM來呢?以防你不知道rel="preload"怎麼回事,可以看看Yoav Weiss 在 Smashing Magazine的文章

很不幸的是, 預載入的功能造ESM不容易實現,因為他們不像一般的script。問題在於一個 帶有preload屬性link元素怎麼處理一個ESM?是不是要載入所有的以來文件?答案很明顯的,但是如果要加入預載入指令瀏覽器端實現也會有很多問題需要解決。

如果你對這個話題感興趣可以Domenic Denicola在github inssue關於這個問題的討論。但至少我們知道rel="preload"指令在處理一般script和ESM之間有很多不同。就在我寫這篇問章的時候一個社區提了一個規範來解決這些問題,就是用一個新的rel="modulepreload"指令。未來我們怎麼預載入,讓我們拭目以待。

引入真實的依賴

三個文件不能構成真正的App,我們加入真正的依賴。剛好,Lodash提供了他函數所有的ESM實現,我用Babili壓縮了他們。接下來我們修改index.js,讓其引入Lodash 方法。

importdep1from ./dep-1.js ;

importisEmptyfrom ./lodash/isEmpty.js ;

functiongetComponent(){

constelement=document.createElement( div );

element.innerHTML=dep1()+ +isEmpty([]);

returnelement;

}

document.body.appendChild(getComponent());

關於isEmpty使用在這裡不重要了,我們來看一下加入真實依賴發生了生什麼。

The use of isEmpty is trivial in this case, but let』s see what happens now after adding this dependency.

請求一下子增長到了超過40個,在一般的WiFi環境下頁面載入速度從100ms漲到400ms到800ms,並且所有裝載文件加起來達到了大約12KB,沒有被壓縮。很不幸Safari不支持WebPagetest來跑分。

Chrome接收到的bundle文件差不多為 8KB。

4KB的差距,足以讓我們去查一下原因在哪。

只有在大文件的壓縮效果好

如果你細心的話可以發現在Safari開發者工具的那張截圖裡面,transferred的文件大小實際上比source文件還要大。特別是在引入很多小的文件塊的大型JS App裡面這個變化更加明顯,究其原因就是因為,GZIP只有在大文件的壓縮效果好。

不久前Khan Academy 也發現了這個問題 當他在使用 HTTP/2做實驗時. 載入更小的文件的方案是為了提高緩存的命中率,但是到頭來,總是需要一個權衡並且取決於很多因素。

對於大型代碼庫,將其拆分成很多文件是有必要的,但是裝載上千個不能更好的壓縮的小文件的確不是一個好的辦法。

Tree shaking 是個好孩子

還有一個事情值得一提,那就是得益於相對新穎的Tree shakin解決方案,在構建過程中可以去除屌那些沒有使用或者被引用的代碼。第一個支持這個方案的是Rollup,現在Webpack2.0也已經支持了——只要我們在bable裡面禁止掉module選項

舉個例子,我們修改dep-2.js 加入一個在dep-1.js 沒有被調用的函數

exportdefaultfunction(){

return Hello World. dependencies loaded. ;

}

exportconstunneededStuff=[

unneeded stuff

];

對於Babili來說,它將會簡單的壓縮文件然後在Safari 裡面將接收帶很多行沒有被用到的代碼。但是一個webpack或者Rollup的bundle文件將不會包含unneededStuff.Tree shaking提供了很強大的好處,絕對應該被用在真實的生產環境中來。

未來看似光明, 但是構建過程停滯在這裡

所以,ESM將要到來,但是似乎不是所有的事情都會改變。我們為了保證壓縮效果不希望裝載數千個小文件,我們也不會拋棄這些帶有可以剔除殭屍代碼Tree shaking方案的構建方案。前端開發始終會是一個複雜的工程。

最終要記住的是權衡是成功的關鍵。不要拆分所有的事情並期望它會帶來很多改進。不要因為我們將要支持ESM就以為我們可以擺脫構建步驟和「打包策略」。在這裡我們將會堅持使用我們的構建方案,並且繼續使用「打包策略」來載入我們的文件以及我們的Javascript SDK。

到目前為止,我不得不承認前端開發依然偉大。JS在進化,我們最終將有一個方案來解決Module融入到這個語言的問題。我等不及想看到它帶給JS生態的影響,未來一兩年內會出現最佳實踐。

關於本文

譯者:@趙飛

作者:@Stefan Judis

原文:https://www.contentful.com/blog/2017/04/04/es6-modules-support-lands-in-browsers-is-it-time-to-rethink-bundling/

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 前端早讀課 的精彩文章:

第三方Javascript開發系列之前後端介面協議
理解Node事件驅動架構
【第957期】JavaScript 模塊現狀
「零廣告,全乾貨」iWeb峰會上海站,最後500位參會名額限免來襲!
代碼審查應該關注什麼:數據結構

TAG:前端早讀課 |

您可能感興趣

Loda:為了Alliance的未來,我會考慮退出
買不到Virgil Abloh x Air Jordan 1的你,應該考慮這雙灰藍無鞋帶版了!
蘋果新機即將發布,正在使用iPhone6s/iPhone6sp的你是否考慮更換?
想換iPhone XS Max,但考慮到 5G,該換嗎?
實在買不到Virgil Abloh x Nike?這雙Air Jordan 1出了6款配色考慮一下?
現在入手iPhone 7 Plus過時了嗎?不妨試著從這三個方面考慮
新ipad發布,這次我終於考慮換 iPad 了!
還有壓歲錢的你,考慮下 Vans Vault Slip-On?
還有壓歲錢的你,考慮下 Vans Vault Slip-On ?
Air Jordan 1看的有點疲?那這款Nike SB 「Dog Walker」可以考慮下!
iPhone8 plus又又又降價,這次你會考慮嗎?
官方聯名看膩了?Virgil Abloh x Air Jordan 1 雙勾版考慮一下?
iOS 12即將推送,還在考慮您的iPhone是否升級嗎?看完你就懂了!
發售在即!顏值堪比Supreme聯乘的Nike Air Zoom Streak新配色不考慮下?
今天Game Royal戰績如何?不如考慮一下Virgil Abloh x Air Jordan 1黑白版?
質感黑灰的Air Jordan 10 「Dark Shadow」即將登場,你考慮入手嗎?
重磅聯名即將登場! Supreme x Nike Air Max Tailwind 4 你考慮入手嗎?
彩色版iPhone 8s來了 嫌iPhone X太貴的不妨考慮一下
Theshy與Khan几几開?李哥考慮了30秒,回答令人慌了
Winter is Coming 不來雙禦寒靴怎麼行!Timberland x The North Face 你不先留著考慮?