摘要:鑒于目前通行的做法就是在所有瀏覽器中一致同仁地加載,相比而言條件可以讓大部分現(xiàn)代瀏覽器用戶避免加載代碼。
原文地址: Modern Script Loading, 文章作者是Preact作者Jason Miller
背景知識(shí)先簡(jiǎn)單介紹一下模塊script(Module script), 它指的是現(xiàn)代瀏覽器支持通過(guò)來(lái)加載現(xiàn)代的ES6模塊. 現(xiàn)代瀏覽器對(duì)ES6現(xiàn)代語(yǔ)法有良好的支持,這意味著我們可以給這些現(xiàn)代瀏覽器提供更緊湊的‘現(xiàn)代代碼’,一方面可以減小打包的體積,減少網(wǎng)絡(luò)傳輸?shù)膸挘硗膺€可以提高腳本解析的效率和運(yùn)行效率.
下圖來(lái)源于module/nomodule pattern, 對(duì)比了模塊script和傳統(tǒng)(legacy) script的性能:
體積對(duì)比:
Version | Size (minified) | Size (minified + gzipped) |
---|---|---|
ES2015+ (main.mjs) | 80K | 21K |
ES5 (main.es5.js) | 175K | 43K |
解析效率:
Version | Parse/eval time (individual runs) | Parse/eval time (avg) |
---|---|---|
ES2015+ (main.mjs) | 184ms, 164ms, 166ms | 172ms |
ES5 (main.es5.js) | 389ms, 351ms, 360ms | 367ms |
Ok,為了兼容舊瀏覽器, module/nomodule pattern這篇文章介紹了一種module/nomodule 模式, 簡(jiǎn)單說(shuō)就是同時(shí)提供兩個(gè)script, 由瀏覽器來(lái)決定加載哪個(gè)文件:
看起來(lái)很美好是吧? 現(xiàn)實(shí)是:中間存在一些瀏覽器,它們可以識(shí)別模塊script但是不認(rèn)識(shí)nomodule屬性, 這就導(dǎo)致了這些瀏覽器會(huì)同時(shí)加載這兩個(gè)文件(下文統(tǒng)一稱為‘雙重加載’(over-fetching)).
OK,正式進(jìn)入正文. 給正確的瀏覽器交付正確代碼是一件棘手的事情。本文會(huì)介紹幾種方式, 來(lái)解決上述的問(wèn)題:
給現(xiàn)代瀏覽器伺服"現(xiàn)代的代碼"對(duì)性能有很大的幫助。所以你應(yīng)該針對(duì)現(xiàn)代瀏覽器提供包含更緊湊和優(yōu)化的現(xiàn)代語(yǔ)法的Javascript包,同時(shí)又可以保持對(duì)舊瀏覽器的支持
現(xiàn)有的工具鏈的生態(tài)系統(tǒng)基本都是在module/nomodule模式上整合的,它聲明式加載現(xiàn)代和傳統(tǒng)代碼(legacy code),即給瀏覽器提供兩個(gè)源代碼,讓它來(lái)自己來(lái)決定用哪個(gè):
然而現(xiàn)實(shí)總是給你當(dāng)頭一棒,它沒(méi)我們期望的那么簡(jiǎn)單直接。上述基于HTML的加載方式在Edge和Safari中會(huì)被同時(shí)加載!
怎么辦?怎么辦?我們想依賴瀏覽器來(lái)交付不同的編譯目標(biāo),但是一些舊瀏覽器并不能優(yōu)雅地支持這種簡(jiǎn)潔的寫法。
首先,Safari 在10.1開始支持JS模塊, 但不支持nomodule屬性。值得慶幸的是,Sam找到了一種方法,可以通過(guò)Safari 10和11中非標(biāo)準(zhǔn)的beforeload事件來(lái)模擬 nomodule, 也就是可以認(rèn)為Safari 10.1開始是可以支持module/nomodule模式
選項(xiàng)1: 動(dòng)態(tài)加載我們可以實(shí)現(xiàn)一個(gè)小型script加載器來(lái)規(guī)避這個(gè)問(wèn)題,工作原理類似于LoadCSS。只不過(guò)這里需要依靠瀏覽器的來(lái)實(shí)現(xiàn)ES模塊和nomodule屬性.
我們首先嘗試執(zhí)行一個(gè)模塊script進(jìn)行"石蕊試驗(yàn)"(litmus test), 然后由這個(gè)試驗(yàn)的結(jié)果來(lái)決定加載現(xiàn)代代碼還是傳統(tǒng)代碼:
然而,這個(gè)解決方案必須等待進(jìn)行‘石蕊試驗(yàn)’模塊script執(zhí)行完成, 才能開始注入script。這是因?yàn)?b>
看起來(lái)已經(jīng)很完美了,還有什么問(wèn)題呢?我們還沒(méi)考慮預(yù)加載(preloading)
這個(gè)有點(diǎn)蛋疼, 因?yàn)橐话銥g覽器只會(huì)靜態(tài)地掃描HTML,然后查找它可以預(yù)加載的資源。 我們上面介紹的模塊加載器是完全動(dòng)態(tài)的,所以瀏覽器在沒(méi)有運(yùn)行我們的代碼之前,是沒(méi)辦法發(fā)現(xiàn)我們要預(yù)加載現(xiàn)代還是傳統(tǒng)的Javascript資源的。
不過(guò)有一個(gè)解決辦法,就是不完美:就是使用來(lái)預(yù)加載現(xiàn)代版本的包, 舊瀏覽器會(huì)忽略這條規(guī)則,然而目前只有Chrome支持這么做:
其實(shí)預(yù)加載這種技術(shù)是否有效,取決于嵌入你的腳本的HTML文檔的大小。
如果你的HTML載荷很小, 比如只是一個(gè)啟動(dòng)屏或者只是簡(jiǎn)單啟動(dòng)客戶端應(yīng)用,那么放棄預(yù)加載掃描對(duì)你的應(yīng)用性能影響很小。
如果你的應(yīng)用使用服務(wù)器渲染大量有意義的HTML, 并以流(stream)的方式傳輸給瀏覽器,那么預(yù)加載掃描就是你的朋友,但這也未必是最佳方法。
譯注: 現(xiàn)代瀏覽器都支持分塊編碼傳輸,等服務(wù)端完全輸出html可能有一段空閑時(shí)間,這時(shí)候可以通過(guò)預(yù)加載技術(shù),讓瀏覽器預(yù)先去請(qǐng)求資源
大概代碼如下:
還要指出的是,支持JS模塊的瀏覽器一般也支持。對(duì)于某些網(wǎng)站,相比依靠modulepreload, 使用可能更有意義。不過(guò)性能上面可能欠點(diǎn),因?yàn)閭鹘y(tǒng)的腳本預(yù)加載不會(huì)像modulepreload一樣隨著時(shí)間的推移而去展開解析工作(rel=preload只是下載,不會(huì)嘗試去解析腳本)。
選項(xiàng)2: 用戶代理嗅探我辦法拿出一個(gè)簡(jiǎn)潔的代碼示例,因?yàn)橛脩舸頇z測(cè)不在本文的范圍之內(nèi),推薦閱讀這篇Smashing Magazine文章
本質(zhì)上,這種技術(shù)在每個(gè)瀏覽器上都使用來(lái)加載代碼,當(dāng)bundle.js被請(qǐng)求時(shí),服務(wù)器會(huì)解析瀏覽器的用戶代理,并選擇返回現(xiàn)代代碼還是傳統(tǒng)代碼,取決于瀏覽器是否能被識(shí)別為現(xiàn)代瀏覽器.
盡管這種方法比較通用,但它也有一些嚴(yán)重的缺點(diǎn):
因?yàn)橐蕾囉诜?wù)端實(shí)現(xiàn),所以前端資源不能被靜態(tài)部署(例如靜態(tài)網(wǎng)站生成器(如github page),Netlify等等)
很難進(jìn)行有效的緩存. 現(xiàn)在這些JavaScript URL的緩存會(huì)因用戶代理而異,這是非常不穩(wěn)定的, 而很多緩存機(jī)制只是將URL作為緩存鍵,現(xiàn)在這些緩存中間件可能就沒(méi)辦法工作了。
UA檢測(cè)很難,容易出現(xiàn)誤報(bào)
用戶代理字符串容易被篡改,而且每天都有新的UA出現(xiàn)
解決這些限制的一種方法就是將module/nomodule模式與"用戶代理區(qū)分"結(jié)合起來(lái),首先這可以避免單純的module/nomodule模式需要發(fā)送多個(gè)軟件包問(wèn)題,盡管這種方法仍然會(huì)降低頁(yè)面(這時(shí)候指HTML,而不是Javascript包)的可緩存性,但是它可以有效地觸發(fā)預(yù)加載,因?yàn)樯蒆TML的服務(wù)器根據(jù)用戶代理知道應(yīng)該使用modulepreload還是preload:
function renderPage(request, response) { let html = `...`; const agent = request.headers.userAgent; const isModern = userAgent.isModern(agent); if (isModern) { html += ` `; } else { html += ` `; } response.end(html); }
對(duì)于那些已經(jīng)在使用服務(wù)端渲染的網(wǎng)站來(lái)說(shuō),用戶代理嗅探是一個(gè)比較有效的解決方案
選項(xiàng) 3:不考慮舊版本瀏覽器注意這里的‘舊版本瀏覽器’特指那些出現(xiàn)雙重加載的瀏覽器. 對(duì)于module/nomodule模式支持比較差(即雙重加載)的主要是一些舊版本的Chrome、Firefox和Safari. 幸運(yùn)的是這部分瀏覽器的市場(chǎng)范圍通常是比較窄,因?yàn)橛脩魰?huì)自動(dòng)升級(jí)到最新的版本。Edge 16-18是例外, 但還有希望: 新版本的Edge會(huì)使用基于Chromium的渲染器,可以不受該問(wèn)題的影響.
對(duì)于某些應(yīng)用程序來(lái)說(shuō),接受這一點(diǎn)妥協(xié)是完全合理的:你可以給90%的瀏覽器中提供現(xiàn)代代碼,讓他們獲得更好的體驗(yàn),而極少數(shù)舊瀏覽器不得不拋棄它們,它們只是付出的額外帶寬(即雙重加載),并不影響功能。值得注意的是,占據(jù)移動(dòng)端主要市場(chǎng)份額的用戶代理不會(huì)有雙重加載問(wèn)題,所以這些流量不太可能來(lái)自于低速或者高昂流量費(fèi)的手機(jī)。
如果你的網(wǎng)站用戶主要使用移動(dòng)設(shè)備或較新版本的瀏覽器,那么最簡(jiǎn)單的module/nomodule模式將適用于你的絕大多數(shù)用戶, 其他用戶就不考慮了,反正也是可以跑起來(lái)的, 優(yōu)先考慮大多數(shù)用戶的體驗(yàn)。
選項(xiàng) 4: 使用條件包
nomodule可以巧妙地用來(lái)條件加載那些現(xiàn)代瀏覽器不需要的代碼, 例如polyfills。通過(guò)這種方法,最壞的情況就是polyfill和bundle都會(huì)被加載(例如Safari 10.1),但這畢竟是少數(shù)。鑒于目前通行的做法就是在所有瀏覽器中一致同仁地加載polyfills,相比而言, 條件polyfills可以讓大部分現(xiàn)代瀏覽器用戶避免加載polyfill代碼。
Angular CLI支持配置這種方式來(lái)加載polyfill, 查看Minko Gechev的代碼示例.
了解了這種方式之后,我決定在preact-cli中支持自動(dòng)polyfill注入,你可以查看這個(gè)PR
如果你使用Webpack,這里有一個(gè)html-webpack-plugin插件可以方便地為polyfill包添加nomodule屬性.
你應(yīng)該怎么做?答案取決于你的使用場(chǎng)景, 選擇和你們的架構(gòu)匹配的選項(xiàng):
如果你的應(yīng)用只是客戶端渲染, 而且你的HTML不超過(guò)一個(gè),選項(xiàng)1比較合適;
如果你的應(yīng)用使用服務(wù)端渲染,而且可以接受緩存問(wèn)題,那么可以選擇選項(xiàng)2;
如果你開發(fā)的是同構(gòu)應(yīng)用,預(yù)加載的功能可能對(duì)你很重要,這時(shí)你可以考慮選項(xiàng)3和4.
就我個(gè)人而言,相比考慮桌面端瀏覽器資源下載成本,我更傾向于優(yōu)化移動(dòng)設(shè)備解析時(shí)間. 移動(dòng)用戶體驗(yàn)會(huì)受到數(shù)據(jù)解析、流量費(fèi)用,電池消耗等因素的影響,而桌面用戶往往不需要考慮這些因素。
另外這些優(yōu)化適用于90%的用戶,比如我工作面對(duì)的大部分用戶都是使用現(xiàn)代或移動(dòng)瀏覽器的。
有興趣繼續(xù)深入?可以從下面的文章開始挖掘:
Phil的webpack-esnext-boilerplate的一些附加的背景.
Ralph在Next.js中實(shí)現(xiàn)了module/nomodule, 并努力解決了上面的問(wèn)題.
感謝Phil, Shubhie, Alex, Houssein, Ralph 以及 Addy 的反饋.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/105816.html
摘要:異步請(qǐng)求線程在在連接后是通過(guò)瀏覽器新開一個(gè)線程請(qǐng)求,將檢測(cè)到狀態(tài)變更時(shí),如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件放到引擎的處理隊(duì)列中等待處理。 瀏覽器的主要功能是將用戶選擇的 web 資源呈現(xiàn)出來(lái),它需要從服務(wù)器請(qǐng)求資源,并將其顯示在瀏覽器窗口中,資源的格式通常是 HTML,也包括 PDF、image 及其他格式。 瀏覽器的線程 瀏覽器是多線程的,它們?cè)趦?nèi)核制控下相互配合以保持同...
摘要:異步請(qǐng)求線程在在連接后是通過(guò)瀏覽器新開一個(gè)線程請(qǐng)求,將檢測(cè)到狀態(tài)變更時(shí),如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件放到引擎的處理隊(duì)列中等待處理。 瀏覽器的主要功能是將用戶選擇的 web 資源呈現(xiàn)出來(lái),它需要從服務(wù)器請(qǐng)求資源,并將其顯示在瀏覽器窗口中,資源的格式通常是 HTML,也包括 PDF、image 及其他格式。 瀏覽器的線程 瀏覽器是多線程的,它們?cè)趦?nèi)核制控下相互配合以保持同...
摘要:?jiǎn)?dòng)性能瓶頸分析與解決方案翻譯自的,從屬于筆者的前端入門與工程實(shí)踐。我們必須要清醒地認(rèn)識(shí)到全面評(píng)測(cè)以挖掘出真正性能瓶頸的重要性。這可能是最佳的方式了,類似于這樣的模式鼓勵(lì)基于路由的分組,目前被與廣泛使用。 JavaScript 啟動(dòng)性能瓶頸分析與解決方案 翻譯自 Addy Osmani 的 JavaScript Start-up Performance,從屬于筆者的Web 前端入門與工...
摘要:而且默認(rèn)帶有執(zhí)行的順序是,,即便是內(nèi)聯(lián)的,依然具有屬性。模塊腳本只會(huì)執(zhí)行一次必須符合同源策略模塊腳本在跨域的時(shí)候默認(rèn)是不帶的。通常被用作腳本被禁用的回退方案。最后標(biāo)簽真的令人感到興奮。 窺探 Script 標(biāo)簽 0x01 什么是 script 標(biāo)簽? script 標(biāo)簽允許你包含一些動(dòng)態(tài)腳本或數(shù)據(jù)塊到文檔中,script 標(biāo)簽是非閉合的,你也可以將動(dòng)態(tài)腳本或數(shù)據(jù)塊當(dāng)做 script 的...
摘要:而且默認(rèn)帶有執(zhí)行的順序是,,即便是內(nèi)聯(lián)的,依然具有屬性。模塊腳本只會(huì)執(zhí)行一次必須符合同源策略模塊腳本在跨域的時(shí)候默認(rèn)是不帶的。通常被用作腳本被禁用的回退方案。最后標(biāo)簽真的令人感到興奮。 窺探 Script 標(biāo)簽 0x01 什么是 script 標(biāo)簽? script 標(biāo)簽允許你包含一些動(dòng)態(tài)腳本或數(shù)據(jù)塊到文檔中,script 標(biāo)簽是非閉合的,你也可以將動(dòng)態(tài)腳本或數(shù)據(jù)塊當(dāng)做 script 的...
閱讀 3170·2021-09-10 10:51
閱讀 3351·2021-08-31 09:38
閱讀 1639·2019-08-30 15:54
閱讀 3129·2019-08-29 17:22
閱讀 3214·2019-08-26 13:53
閱讀 1960·2019-08-26 11:59
閱讀 3283·2019-08-26 11:37
閱讀 3308·2019-08-26 10:47