成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

深入理解 Webpack 打包分塊(上)

Rocko / 3792人閱讀

摘要:而一個哈希字符串就是根據(jù)文件內(nèi)容產(chǎn)生的簽名,每當(dāng)文件內(nèi)容發(fā)生更改時,哈希串也就發(fā)生了更改,文件名也就隨之更改。很顯然這不是我們需要的,如果文件內(nèi)容發(fā)生了更改,的打包文件的哈希應(yīng)該發(fā)生變化,但是不應(yīng)該。

前言

隨著前端代碼需要處理的業(yè)務(wù)越來越繁重,我們不得不面臨的一個問題是前端的代碼體積也變得越來越龐大。這造成無論是在調(diào)式還是在上線時都需要花長時間等待編譯完成,并且用戶也不得不花額外的時間和帶寬下載更大體積的腳本文件。

然而仔細(xì)想想這完全是可以避免的:在開發(fā)時難道一行代碼的修改也要重新打包整個腳本?用戶只是粗略瀏覽頁面也需要將整個站點的腳本全部下載下來?所以趨勢必然是按需的、有策略性的將代碼拆分和提供給用戶。最近流行的微前端某種意義上來說也是遵循了這樣的原則(但也并不是完全基于這樣的原因)

幸運的是,我們目前已有的工具已經(jīng)完全賦予我們實現(xiàn)以上需求的能力。例如 Webpack 允許我們在打包時將腳本分塊;利用瀏覽器緩存我們能夠有的放矢的加載資源。

在探尋最佳實踐的過程中,最讓我疑惑的不是我們能不能做,而是我們應(yīng)該如何做:我們因該采取什么樣的特征拆分腳本?我們應(yīng)該使用什么樣的緩存策略?使用懶加載和分塊是否有異曲同工之妙?拆分之后究竟能帶來多大的性能提升?最重要的是,在面多諸多的方案和工具以及不確定的因素時,我們應(yīng)該如何開始?這篇文章就是對以上問題的梳理和回答。文章的內(nèi)容大體分為兩個方面,一方面在思路制定模塊分離的策略,另一方面從技術(shù)上對方案進行落地。

本文的主要內(nèi)容翻譯自 The 100% correct way to split your chunks with Webpack。 這篇文章循序漸進的引導(dǎo)開發(fā)者步步為營的對代碼進行拆分優(yōu)化,所以它是作為本文的線索存在。同時在它的基礎(chǔ)上,我會對 Webpack 及其他的知識點做縱向擴展,對方案進行落地。

以下開始正文


根據(jù) Webpack 術(shù)語表,存在兩類文件的分離。這些名詞聽起來是可以互換的,但實際上不行:

打包分離 (Bundle splitting):為了更好的緩存創(chuàng)建更多、更小的文件(但仍然以每一個文件一個請求的方式進行加載)

代碼分離 (Code splitting):動態(tài)加載代碼,所以用戶只需要下載當(dāng)前他正在瀏覽站點的這部分代碼

第二種策略聽起來更吸引人是不是?事實上許多的文章也假定認(rèn)為這才是唯一值得將 JavaScript 文件進行小文件拆分的場景。

但是我在這里告訴你第一種策略對許多的站點來說才更有價值,并且應(yīng)該是你首先為頁面做的事

讓我們來深入理解

Bundle VS Chunk VS Module

在正式開始編碼之前,我們還是要明確一些概念。例如我們貫穿全文的“塊”(chunk) ,以及它和我們常常提到的“包”(bundle)以及“模塊”(module) 到底有什么區(qū)別。

遺憾的事情是即使在查閱了很多資料之后,我仍然沒法得到一個確切的標(biāo)準(zhǔn)答案,所以這里我選擇我個人比較認(rèn)可的定義在這里做一個分享,重要的還是希望能起到統(tǒng)一口徑的作用

首先對于“模塊”(module)的概念相信大家都沒有異議,它指的就是我們在編碼過程中有意識的封裝和組織起來的代碼片段。狹義上我們首先聯(lián)想到的是碎片化的 React 組件,或者是 CommonJS 模塊又或者是 ES6 模塊,但是對 Webpack 和 Loader 而言,廣義上的模塊還包括樣式和圖片,甚至說是不同類型的文件

而“包”(bundle) 就是把相關(guān)代碼都打包進入的單個文件。如果你不想把所有的代碼都放入一個包中,你可以把它們劃分為多個包,也就是“塊”(chunk) 中。從這個角度上看,“塊”等于“包”,它們都是對代碼再一層的組織和封裝。如果必須要給一個區(qū)分的話,通常我們在討論時,bundle 指的是所有模塊都打包進入的單個文件,而 chunk 指的是按照某種規(guī)則的模塊集合,chunk 的體積大于單個模塊,同時小于整個 bundle

(但如果要仔細(xì)的深究,Chunk是 Webpack 用于管理打包流程中的技術(shù)術(shù)語,甚至能劃分為不同類型的 chunk。我想我們不用從這個角度理解。只需要記住上一段的定義即可)

打包分離 (Bundle splitting)

打包分離背后的思想非常簡單。如果你有一個體積巨大的文件,并且只改了一行代碼,用戶仍然需要重新下載整個文件。但是如果你把它分為了兩個文件,那么用戶只需要下載那個被修改的文件,而瀏覽器則可以從緩存中加載另一個文件。

值得注意的是因為打包分離與緩存相關(guān),所以對站點的首次訪問者來說沒有區(qū)別

(我認(rèn)為太多的性能討論都是關(guān)于站點的首次訪問?;蛟S部分原因是因為第一映像很重要,另一部分因為這部分性能測量起來簡單和討巧)

當(dāng)談?wù)摰筋l繁訪問者時,量化性能提升會稍有棘手,但是我們必須量化!

這將需要一張表格,我們將每一種場景與每一種策略的組合結(jié)果都記錄下來

我們假設(shè)一個場景:

Alice 連續(xù) 10 周每周訪問站點一次

我們每周更新站點一次

我們每周更新“產(chǎn)品列表”頁面

我們也有一個“產(chǎn)品詳情”頁面,但是目前不需要對它進行更新

在第 5 周的時我們給站點新增了一個 npm 包

在第 8 周時我們更新了現(xiàn)有的一個 npm 包

當(dāng)然包括我在內(nèi)的部分人希望場景盡可能的逼真。但其實無關(guān)緊要,我們隨后會解釋為什么。

性能基線

假設(shè)我們的 JavaScript 打包后的總體積為 400KB, 將它命名為 main.js,然后以單文件的形式加載它

我們有一個類似如下的 Webpack 配置(我已經(jīng)移除了無關(guān)的配置項):

const path = require("path"); module.exports = { entry: path.resolve(__dirname, "src/index.js"), output: { path: path.resolve(__dirname, "dist"), filename: "[name].[contenthash].js", }, };

當(dāng)只有單個入口時,Webpack 會自動把結(jié)果命名為main.js

(對那些剛接觸緩知識的人我解釋一下:每當(dāng)我我提及main.js的時候,我實際上是在說類似于main.xMePWxHo.js這種包含一堆帶有文件內(nèi)容哈希字符串的東西。這意味著當(dāng)你應(yīng)用代碼發(fā)生更改時新的文件名會生成,這樣就能迫使瀏覽器下載新的文件)

所以當(dāng)每周我向站點發(fā)布新的變更時,包的contenthash就會發(fā)生更改。以至于每周 Alice 訪問我們站點時不得不下載一個全新的 400KB 大小的文件

連續(xù)十周也就是 4.12MB

我們能做的更好

哈希(hash)與性能

不知道你是否真的理解上面的表述。有幾點需要在這里澄清:

    為什么帶哈希串的文件名會對瀏覽器緩存產(chǎn)生影響?

    為什么文件名里的哈希后綴是contenthash?如果把contenthash替換成hash或者chunkhash有什么影響?

為了每次訪問時不讓瀏覽器都重新下載同一個文件,我們通常會把這個文件返回的 HTTP 頭中的Cache-Control設(shè)置為max-age=31536000,也就是一年(秒數(shù)的)時間。這樣以來,在一年之內(nèi)用戶訪問這個文件時,都不會再次向服務(wù)器發(fā)送請求而是直接從緩存中讀取,直到或者手動清除了緩存。

如果我中途修改了文件內(nèi)容必須讓用戶重新下載怎么辦?修改文件名就好了,不同的文件(名)對應(yīng)不同的緩存策略。而一個哈希字符串就是根據(jù)文件內(nèi)容產(chǎn)生的“簽名”,每當(dāng)文件內(nèi)容發(fā)生更改時,哈希串也就發(fā)生了更改,文件名也就隨之更改。這樣一來,舊版本文件的緩存策略就會失效,瀏覽器就會重新加載新版本的該文件。當(dāng)然這只是其中一種最基礎(chǔ)的緩存策略,更復(fù)雜的場景請參考我之前的一篇文章:設(shè)計一個無懈可擊的瀏覽器緩存方案:關(guān)于思路,細(xì)節(jié),ServiceWorker,以及HTTP/2

所以在 Webpack 中配置的 filename: [name]:[contenthash].js 就是為了每次發(fā)布時自動生成新的文件名。

然而如果你對 Webpack 稍有了解的話,你應(yīng)該知道 Webpack 還提供了另外兩種哈希算法供開發(fā)者使用:hashchunkhash。那么為什么不使用它們而是使用contenthash?這要從它們的區(qū)別說起。原則上來說,它們是為不同目的服務(wù)的,但在實際操作中,也可以交替使用。

為了便于說明,我們先準(zhǔn)備以下這段非常簡單的 Webpack 配置,它擁有兩個打包入口,同時額外提取出 css 文件,最終生成三個文件。filename配置中我們使用的是hash標(biāo)識符、在 MinCssExtractPlugin中我們使用的是contenthash,為什么會這樣稍后會解釋。

const CleanWebpackPlugin = require("clean-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: { module_a: "./src/module_a.js", module_b: "./src/module_b.js" }, output: { filename: "[name].[hash].js" }, plugins: [ new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" }) ] };

hash

hash針對的是每一次構(gòu)建(build)而言,每一次構(gòu)建之后生成的文件所帶的哈希都是一致的。它關(guān)心的是整體項目的變化,只要有任意文件內(nèi)容發(fā)生了更改,那么構(gòu)建之后其他文件的哈希也會發(fā)生更改。

很顯然這不是我們需要的,如果module_a文件內(nèi)容發(fā)生了更改,module_a的打包文件的哈希應(yīng)該發(fā)生變化,但是module_b不應(yīng)該。這會導(dǎo)致用戶不得不重新下載沒有發(fā)生變化的module_b打包文件

chunkhash

chunkhash基于的是每一個 chunk 內(nèi)容的改變,如果是該 chunk 所屬的內(nèi)容發(fā)生了變化,那么只有該 chunk 的輸出文件的哈希會發(fā)生變化,其它的不會。這聽上去符合我們的需求。

在之前我們對 chunk 進行過定義,即是小單位的代碼聚合形式。在上面的例子中以entry入口體現(xiàn),也就是說每一個入口對應(yīng)的文件就是一個 chunk。在后面的例子中我們會看到更復(fù)雜的例子

contenthash

顧名思義,該哈希根據(jù)的是文件的內(nèi)容。從這個角度上說,它和chunkhash是能夠相互代替的。所以在“性能基線”代碼中作者使用了contenthash

不過特殊之處是,或者說我讀到的關(guān)于它的使用說明中,都指示如果你想在ExtractTextWebpackPlugin或者MiniCssExtractPlugin中用到哈希標(biāo)識,你應(yīng)該使用contenthash。但就我個人的測試而言,使用hash或者chunkhash也都沒有問題(也許是因為 extract 插件是嚴(yán)格基于 content 的?但難道 chunk 不是嗎?)

分離第三方類庫(vendor)類庫

讓我們把打包文件劃分為main.jsvendor.js

很簡單,類似于:

const path = require("path"); module.exports = { entry: path.resolve(__dirname, "src/index.js"), output: { path: path.resolve(__dirname, "dist"), filename: "[name].[contenthash].js", }, optimization: { splitChunks: { chunks: "all", }, }, };

在你沒有告訴它你想如何拆分打包文件的情況下, Webpack 4 在盡它最大的努力把這件事做的最好

這就導(dǎo)致一些聲音在說:“太驚人了,Webpack 做的真不錯!”

而另一些聲音在說:“你對我的打包文件做了什么!”

無論如何,添加optimization.splitChunks.chunks = "all"配置也就是在說:“把所有node_modules里的東西都放到vendors~main.js的文件中去”

在實現(xiàn)基本的打包分離條件后,Alice 在每次訪問時仍然需要下載 200KB 大小的 main.js 文件, 但是只需要在第一周、第五周、第八周下載 200KB 的 vendors.js腳本

也就是 2.64MB

體積減少了 36%。對于配置里新增的五行代碼來說結(jié)果還不錯。在繼續(xù)閱讀之前你可以立刻就去試試。如果你需要將 Webpack 3 升級到 4,也不要著急,升級不會帶來痛苦(而且是免費的?。?/p>

分離每一個 npm 包

我們的 vendors.js 承受著和開始 main.js 文件同樣的問題——部分的修改會意味著重新下載所有的文件

所以為什么不把每一個 npm 包都分割為多帶帶的文件?做起來非常簡單

讓我們把我們的reactlodash,reduxmoment等分離為不同的文件

const path = require("path"); const webpack = require("webpack"); module.exports = { entry: path.resolve(__dirname, "src/index.js"), plugins: [ new webpack.HashedModuleIdsPlugin(), // so that file hashes don"t change unexpectedly ], output: { path: path.resolve(__dirname, "dist"), filename: "[name].[contenthash].js", }, optimization: { runtimeChunk: "single", splitChunks: { chunks: "all", maxInitialRequests: Infinity, minSize: 0, cacheGroups: { vendor: { test: /[/]node_modules[/]/, name(module) { // get the name. E.g. node_modules/packageName/not/this/part.js // or node_modules/packageName const packageName = module.context.match(/[/]node_modules[/](.*");

也就是2.24MB

相對于基線減少了 44%,這是一段你能夠從文章里粘貼復(fù)制的非常酷的代碼。

我好奇我們能超越 50%?

那不是很棒嗎

稍等,那段 Webpack 配置代碼究竟是怎么回事

此時你的疑惑可能是,optimization 選項里的配置怎么就把 vendor 代碼分離出來了?

接下來的這一小節(jié)會針對 Webpack 的 Optimization 選項做講解。我個人并非 Webpack 的專家,配置和對應(yīng)的描述功能也并非一一經(jīng)過驗證,也并非全部都覆蓋到,如果有紕漏的地方還請大家諒解。

optimization配置如其名所示,是為優(yōu)化代碼而生。如果你再仔細(xì)觀察,大部分配置又在splitChunk字段下,因為它間接使用 SplitChunkPlugin 實現(xiàn)對塊的拆分功能(這些都是在 Webpack 4 中引入的新的機制。在 Webpack 3 中使用的是 CommonsChunkPlugin,在 4 中已經(jīng)不再使用了。所以這里我們也主要關(guān)注的是 SplitChunkPlugin 的配置)從整體上看,SplitChunksPlugin 的功能只有一個,就是split——把代碼分離出來。分離是相對于把所有模塊都打包成一個文件而言,把單個大文件分離為多個小文件。

在最初分離 vendor 代碼時,我們只使用了一個配置

splitChunks: { chunks: "all", },

chunks有三個選項:initial、asyncall。它指示應(yīng)該優(yōu)先分離同步(initial)、異步(async)還是所有的代碼模塊。這里的異步指的是通過動態(tài)加載方式(import())加載的模塊。

這里的重點是優(yōu)先二字。以async為例,假如你有兩個模塊 a 和 b,兩者都引用了 jQuery,但是 a 模塊還通過動態(tài)加載的方式引入了 lodash。那么在 async 模式下,插件在打包時會分離出lodash~for~a.js的 chunk 模塊,而 a 和 b 的公共模塊 jQuery 并不會被(優(yōu)化)分離出來,所以它可能還同時存在于打包后的a.bundle.jsb.bundle.js文件中。因為async告訴插件優(yōu)先考慮的是動態(tài)加載的模塊

接下來聚焦第二段分離每個 npm 包的 Webpack 配置中

maxInitialRequestsminSize確實就是插件自作多情的杰作了。插件自帶一些分離 chunk 的規(guī)則:如果即將分離的 chunk 文件體積小于 30KB 的話,那么就不會將該 chunk 分離出來;并且限制并行下載的 chunk 最大請求個數(shù)為 3 個。通過覆蓋 minSizemaxInitialRequests 配置就能夠重寫這兩個參數(shù)。注意這里的maxInitialRequestsminSize是在splitChunks根目錄中的,我們暫且稱它為全局配置

cacheGroups配置才是最重要,它允許自定義規(guī)則分離 chunk。并且每條cacheGroups規(guī)則下都允許定義上面提到的chunksminSize字段用于覆蓋全局配置(又或者將cacheGroups規(guī)則中enforce參數(shù)設(shè)為true來忽略全局配置)

cacheGroups里默認(rèn)自帶vendors配置來分離node_modules里的類庫模塊,它的默認(rèn)配置如下:

cacheGroups: { vendors: { test: /[/]node_modules[/]/, priority: -10 },

如果你不想使用它的配置,你可以把它設(shè)為false又或者重寫它。這里我選擇重寫,并且加入了額外的配置nameenforce:

vendors: { test: /[/]node_modules[/]/, name: "vendors", enforce: true, },

最后介紹以上并沒有出現(xiàn)但是仍然常用的兩個配置:priorityreuseExistingChunk

reuseExistingChunk: 該選項只會出現(xiàn)在cacheGroups的分離規(guī)則中,意味重復(fù)利用現(xiàn)有的 chunk。例如 chunk 1 擁有模塊 A、B、C;chunk 2 擁有模塊 B、C。如果 reuseExistingChunkfalse 的情況下,在打包時插件會為我們多帶帶創(chuàng)建一個 chunk 名為 common~for~1~2,它包含公共模塊 B 和 C。而如果該值為true的話,因為 chunk 2 中已經(jīng)擁有公共模塊 B 和 C,所以插件就不會再為我們創(chuàng)建新的模塊

priority: 很容易想象到我們會在cacheGroups中配置多個 chunk 分離規(guī)則。如果同一個模塊同時匹配多個規(guī)則怎么辦,priority解決的這個問題。注意所有默認(rèn)配置的priority都為負(fù)數(shù),所以自定義的priority必須大于等于0才行

小結(jié)

截至目前為止,我們已經(jīng)看出了一套分離代碼的模式:

首先決定我們想要解決什么樣的問題(避免用戶在每次訪問時下載額外的代碼);

再決定使用什么樣的方案(通過將修改頻率低、重復(fù)的代碼分離出來,并配上恰當(dāng)?shù)木彺娌呗裕?/p>

最后決定實施的方案是什么(通過配置 Webpack 來實現(xiàn)代碼的分離)

本文也同時發(fā)布在我的知乎專欄,歡迎大家關(guān)注

參考資料

Bundle VS Chunk

What are module, chunk and bundle in webpack?

Concepts - Bundle vs Chunk

SurviveJS: Glossary

Hash

What is the purpose of webpack hash and chunkhash?

Hash vs chunkhash vs ContentHash

Adding Hashes to Filenames

SplitChunksPlugin

Webpack 4?—?Mysterious SplitChunks Plugin

Webpack (v4) Code Splitting using SplitChunksPlugin

Reduce JavaScript Payloads with Code Splitting

Webpack v4 chunk splitting deep dive

what reuseExistingChunk: true means, can give a sample?

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/6650.html

相關(guān)文章

  • 深入理解 Webpack 打包分塊(下)

    摘要:例如允許我們在打包時將腳本分塊利用瀏覽器緩存我們能夠有的放矢的加載資源。文章的內(nèi)容大體分為兩個方面,一方面在思路制定模塊分離的策略,另一方面從技術(shù)上對方案進行落地。我之前提到測試之下是什么樣具體的場景并不重要。前言 隨著前端代碼需要處理的業(yè)務(wù)越來越繁重,我們不得不面臨的一個問題是前端的代碼體積也變得越來越龐大。這造成無論是在調(diào)式還是在上線時都需要花長時間等待編譯完成,并且用戶也不得不花額外的...

    pingan8787 評論0 收藏0
  • 關(guān)于vue的懶加載實踐

    摘要:最近在研究的按需加載,好奇怪,之前好像并沒有看到的官文里面有這一部分,是我看差了嗎尬笑其實只需要看官文就可以了,里面有懶加載的講解,并且附帶了詳細(xì)內(nèi)容的連接。所以很大程度上優(yōu)化了頁面的初始加載速度。只是為了測試按需加載隨便寫的而已。 最近在研究vue的按需加載,好奇怪,之前好像并沒有看到vue的官文里面有這一部分,是我看差了嗎hahaha~尬笑~ 其實只需要看vue-router官文就...

    wangzy2019 評論0 收藏0
  • 談?wù)勄岸斯こ袒?js加載

    摘要:當(dāng)年的加載在沒有前端工程化之前,基本上是我們是代碼一把梭,把所需要的庫和自己的代碼堆砌在一起,然后自上往下的引用就可以了。而且對于前后端的技術(shù)要求較高,所以對于項目未必是最有效的方案。 當(dāng)年的 js 加載 在沒有 前端工程化之前,基本上是我們是代碼一把梭,把所需要的庫和自己的代碼堆砌在一起,然后自上往下的引用就可以了。 那個時代我們沒有公用的cdn,也沒有什么特別好的方法來優(yōu)化加載j...

    paulli3 評論0 收藏0
  • webpack再看一遍

    摘要:在終端中使用可以自動創(chuàng)建這個文件輸入這個命令后,終端會問你一系列問題。百度后發(fā)現(xiàn)引入了模式,有三個狀態(tài),開發(fā)模式生產(chǎn)模式無。 什么是webpack,為什么要使用webapck * 導(dǎo)語 之前一直忙著項目,沒時間整理自己的東西,最近剛好發(fā)現(xiàn)自己對webpack又如此陌生了,于是整理了一篇關(guān)于webpack初探的干貨,這里是一點簡單的webpack配置 為什么使用webpck 現(xiàn)今很多網(wǎng)頁...

    whinc 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<