轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。原文出處:https://wanago.io/2018/08/13/webpack-4-course-part-seven-decreasing-the-bundle-size-with-tree-shakin ...
轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。
原文出處:https://wanago.io/2018/08/13/webpack-4-course-part-seven-decreasing-the-bundle-size-with-tree-shaking/
在本系列的第一篇文章中,我們討論了導入(import)和導出(export)。這次我們深入介紹動態導入(dynamic import),它值得專門用一篇文章來介紹。我們會介紹動態導入是什麼以及如何使用它們。開始吧!
在過去,ECMAScript模塊是完全靜態的。你必須在運行代碼之前指明想要導入和導出的東西。隨著動態導入提案的出現,我們有了額外的選擇,即動態地導入模塊。現在它進行到了TC39流程的第三個階段。有了它,你就可以添加動態導入模塊了。使用它時,你可能會根據用戶及其操作行為的做相應處理。比如,你有一個單頁應用,只有當用戶決定打開它的子頁面時才載入特定代碼。這樣可以大幅節省應用的初始載入時間。
使用動態導入
動態導入操作符是作為函數使用的。它接受一個字元串參數,返回一個Promise。當模塊載入好後,這個Promise被resolve。
如果你想瞭解更多關於Promise的內容,可查看以實現一個排序演算法為例解釋Promise和回調函數。
document.addEventListener("DOMContentLoaded", () => { const button = document.querySelector('#divideButton'); button.addEventListener('click', () => { import('./utilities/divide') .then(divideModule => { console.log(divideModule.divide(6, 3)); // 2 }) }); });
在瀏覽器的開發者工具,如果打開Network標簽,你可以看到,模塊開始下載的發生在點擊按鈕之後,而不是在此之前。值得註意的是,如果再次點擊按鈕,包含了拆分後的模塊文件不會再次被下載。
在Webpack中使用動態導入,會新增一個chunk,我們視作非同步chunk。
非同步chunk在教程的第四部分-使用SplitChunksPlugin分離代碼中有介紹。
像這樣的chunk會被打包進單獨的文件。當使用表達式創建指向其文件的路徑時,你需要小心。考慮如下例子:
let fileName = ''; document.addEventListener("DOMContentLoaded", () => { const button = document.querySelector('#divideButton'); fileName = 'divide'; button.addEventListener('click', () => { import(`./utilities/${fileName}`) .then(divideModule => { console.log(divideModule.divide(6, 3)); // 2 }) }); });
以上代碼在你的項目中被打包過後,你會發現Webpack在utilities文件夾下為每個模塊單獨創建了非同步chunk。這是因為Webpack不能在編譯時知道哪些模塊需要被導入。
你還需要知道像import(pathToFile)這樣的完全的動態聲明是不起作用的,因為Webpack至少需要一部分文件路徑信息。這是因為pathToFile可以是你工程中任何文件的路徑,而Webpack會為每個模塊在給定的文件夾中創建非同步chunk。你可以自定義此行為,我們下麵就會這麼做。
使用在Webpack中使用魔法註釋
導入模塊的規範不允許你在導入時使用除了文件名以外的參數。幸運的是,有了Webpack,你可以利用所謂的**魔法註釋(magic comments)**來使用附加參數。
webpackInclude 和 webpackExclude
在之前的小節,我們提到Webpack會為每個模塊在我們給定的文件夾中創建非同步chunk。雖然這是預設行為,但它可以修改。
其中一種方法是使用webpackExclude,它是一個正則表達式,用以匹配潛在的可被導入的文件。任何匹配到的文件都不會被打包進來。
import( `./utilities/${fileName}` /* webpackExclude: /subtract.js$/ */ )
以上代碼表示,文件 subtract.js 文件不會被打包進來,即使它在 utilities 目錄下。
與之相反的一個參數叫做webpackInclude。使用它時,只有匹配了正則表達式的模塊會被打包。
webpackMode
webpackMode屬性定義了resolve動態模塊時的模式。支持以下模式:
lazy
這是預設模式。它為每個動態導入的模塊創建非同步chunk。
lazy-once
使用它,會為滿足導入條件的所有模塊創建單一的非同步chunk。
import( `./utilities/${fileName}` /* webpackMode: "lazy-once" */ ) .then(divideModule => { console.log(divideModule.divide(6, 3)); // 2 })
以上代碼表示,Webpack會為所有 utilities 目錄下的所有模塊共同創建一個非同步chunk。它會導致用戶以一個文件下載所有的模塊。
eager
此模式會阻止Webpack生成額外的chunk。所有導入的模塊被包含在當前chunk,所以不需要再發額外的網路請求。它仍然返回一個Promise,但它被自動resolve。使用eager模式的動態導入與靜態導入的區別在於,整個模塊只有當**import()**掉用之後才執行。
weak
徹底阻止額外的網路請求。只有當該模塊已在其他地方被載入過了之後,Promise才被resolve,否則直接被reject。
webpackChunkName
它是新chunk的名字,可以和[index]、[request]變數一起使用。
[index]在當前動態導入聲明中表示文件的索引。另一方面,[request]表示導入文件的動態部分。
import( `./utilities/${fileName}` /* webpackChunkName: "utilities-[index]-[request]" */ )
以上代碼可能生成例如 utilities-0-divide.js 這樣的文件名。
請註意,如果在某些情況下,確定只有一個非同步chunk(比如本來就沒有動態生成路徑,或者使用了lazy-once模式),[index]和[request]就不會被使用了。
使用預先拉取和預先載入提升性能
Webpack 4.6.0為我們提供了預先拉取(prefetching)和預先載入(preloading)的功能。使用這些聲明可以修改瀏覽器處理非同步chunk的方式。
預先拉取
使用預先拉取,你表示該模塊可能以後會用到。瀏覽器會在空閑時間下載該模塊,且下載是發生在父級chunk載入完成之後。
import( `./utilities/divide` /* webpackPrefetch: true */ /* webpackChunkName: "utilities" */ )
以上的導入會讓<link rel="prefetch" as="script" href="utilities.js">被添加至頁面的頭部。因此瀏覽器會在空閑時間預先拉取該文件。
預先載入
在資源上添加預先載入的註釋,你指明該模塊需要立即被使用。非同步chunk會和父級chunk並行載入。如果父級chunk先下載好,頁面就已可顯示了,同時等待非同步chunk的下載。這能大幅提升性能。
import( `./utilities/divide` /* webpackPreload: true */ /* webpackChunkName: "utilities" */ )
以上代碼的效果是讓<link rel="preload" as="script" href="utilities.js">起作用。不當地使用wepbackPreload會損害性能,所以使用的時候要小心。
總結
這次我們學習瞭如何使用動態導入提升應用的性能。它們能顯著減少頁面的初次載入時間。使用可傳入Webpack的額外參數,你可以更進一步地定製它,並且添加上對預先拉取和預先載入的支持。所有這些,都會優化你的用戶體驗,讓你的網站更加靈動。