国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

Rocko / 3749人閱讀

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

前言

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

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

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

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

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

以下開始正文


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

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

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

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

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

讓我們來深入理解

Bundle VS Chunk VS Module

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

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

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

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

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

打包分離 (Bundle splitting)

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

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

(我認為太多的性能討論都是關于站點的首次訪問。或許部分原因是因為第一映像很重要,另一部分因為這部分性能測量起來簡單和討巧)

當談論到頻繁訪問者時,量化性能提升會稍有棘手,但是我們必須量化!

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

我們假設一個場景:

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

我們每周更新站點一次

我們每周更新“產品列表”頁面

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

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

在第 8 周時我們更新了現有的一個 npm 包

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

性能基線

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

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

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

當只有單個入口時,Webpack 會自動把結果命名為main.js

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

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

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

我們能做的更好

哈希(hash)與性能

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

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

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

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

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

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

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

為了便于說明,我們先準備以下這段非常簡單的 Webpack 配置,它擁有兩個打包入口,同時額外提取出 css 文件,最終生成三個文件。filename配置中我們使用的是hash標識符、在 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針對的是每一次構建(build)而言,每一次構建之后生成的文件所帶的哈希都是一致的。它關心的是整體項目的變化,只要有任意文件內容發(fā)生了更改,那么構建之后其他文件的哈希也會發(fā)生更改。

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

chunkhash

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

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

contenthash

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

不過特殊之處是,或者說我讀到的關于它的使用說明中,都指示如果你想在ExtractTextWebpackPlugin或者MiniCssExtractPlugin中用到哈希標識,你應該使用contenthash。但就我個人的測試而言,使用hash或者chunkhash也都沒有問題(也許是因為 extract 插件是嚴格基于 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 在盡它最大的努力把這件事做的最好

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

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

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

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

也就是 2.64MB

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

分離每一個 npm 包

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

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

讓我們把我們的reactlodashreduxmoment等分離為不同的文件

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%,這是一段你能夠從文章里粘貼復制的非常酷的代碼。

我好奇我們能超越 50%?

那不是很棒嗎

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

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

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

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

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

splitChunks: { chunks: "all", },

chunks有三個選項:initialasyncall。它指示應該優(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 最大請求個數為 3 個。通過覆蓋 minSizemaxInitialRequests 配置就能夠重寫這兩個參數。注意這里的maxInitialRequestsminSize是在splitChunks根目錄中的,我們暫且稱它為全局配置

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

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

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

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

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

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

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

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

小結

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

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

再決定使用什么樣的方案(通過將修改頻率低、重復的代碼分離出來,并配上恰當的緩存策略);

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

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

參考資料

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?

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

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/6650.html

相關文章

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

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

    pingan8787 評論0 收藏0
  • 關于vue的懶加載實踐

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

    wangzy2019 評論0 收藏0
  • 談談前端工程化 js加載

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

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

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

    whinc 評論0 收藏0

發(fā)表評論

0條評論

Rocko

|高級講師

TA的文章

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