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

資訊專欄INFORMATION COLUMN

webpack優(yōu)化

ChanceWong / 1166人閱讀

摘要:使用要給項(xiàng)目構(gòu)建接入動(dòng)態(tài)鏈接庫(kù)的思想,需要完成以下事情把網(wǎng)頁(yè)依賴的基礎(chǔ)模塊抽離出來(lái),打包到一個(gè)個(gè)多帶帶的動(dòng)態(tài)鏈接庫(kù)中去。接入已經(jīng)內(nèi)置了對(duì)動(dòng)態(tài)鏈接庫(kù)的支持,需要通過(guò)個(gè)內(nèi)置的插件接入,它們分別是插件用于打包出一個(gè)個(gè)多帶帶的動(dòng)態(tài)鏈接庫(kù)文件。

webpack優(yōu)化
查看所有文檔頁(yè)面:全棧開(kāi)發(fā),獲取更多信息。

原文鏈接:webpack優(yōu)化,原文廣告模態(tài)框遮擋,閱讀體驗(yàn)不好,所以整理成本文,方便查找。

優(yōu)化開(kāi)發(fā)體驗(yàn)

優(yōu)化構(gòu)建速度。在項(xiàng)目龐大時(shí)構(gòu)建耗時(shí)可能會(huì)變的很長(zhǎng),每次等待構(gòu)建的耗時(shí)加起來(lái)也會(huì)是個(gè)大數(shù)目。

縮小文件搜索范圍

使用 DllPlugin

使用 HappyPack

使用 ParallelUglifyPlugin

優(yōu)化使用體驗(yàn)。通過(guò)自動(dòng)化手段完成一些重復(fù)的工作,讓我們專注于解決問(wèn)題本身。

使用自動(dòng)刷新

開(kāi)啟模塊熱替換

優(yōu)化輸出質(zhì)量

優(yōu)化輸出質(zhì)量的目的是為了給用戶呈現(xiàn)體驗(yàn)更好的網(wǎng)頁(yè),例如減少首屏加載時(shí)間、提升性能流暢度等。 這至關(guān)重要,因?yàn)樵诨ヂ?lián)網(wǎng)行業(yè)競(jìng)爭(zhēng)日益激烈的今天,這可能關(guān)系到你的產(chǎn)品的生死。

優(yōu)化輸出質(zhì)量本質(zhì)是優(yōu)化構(gòu)建輸出的要發(fā)布到線上的代碼,分為以下幾點(diǎn):

減少用戶能感知到的加載時(shí)間,也就是首屏加載時(shí)間。

區(qū)分環(huán)境

壓縮代碼

CDN 加速

使用 Tree Shaking

提取公共代碼

按需加載

提升流暢度,也就是提升代碼性能。

使用 Prepack

開(kāi)啟 Scope Hoisting

縮小文件搜索范圍

Webpack 啟動(dòng)后會(huì)從配置的 Entry 出發(fā),解析出文件中的導(dǎo)入語(yǔ)句,再遞歸的解析。 在遇到導(dǎo)入語(yǔ)句時(shí) Webpack 會(huì)做兩件事情:

根據(jù)導(dǎo)入語(yǔ)句去尋找對(duì)應(yīng)的要導(dǎo)入的文件。例如 require("react") 導(dǎo)入語(yǔ)句對(duì)應(yīng)的文件是 ./node_modules/react/react.js,require("./util") 對(duì)應(yīng)的文件是 ./util.js

根據(jù)找到的要導(dǎo)入文件的后綴,使用配置中的 Loader 去處理文件。例如使用 ES6 開(kāi)發(fā)的 JavaScript 文件需要使用 babel-loader 去處理。

優(yōu)化 loader 配置

由于 Loader 對(duì)文件的轉(zhuǎn)換操作很耗時(shí),需要讓盡可能少的文件被 Loader 處理。

在 Module 中介紹過(guò)在使用 Loader 時(shí)可以通過(guò) testincludeexclude 三個(gè)配置項(xiàng)來(lái)命中 Loader 要應(yīng)用規(guī)則的文件。 為了盡可能少的讓文件被 Loader 處理,可以通過(guò) include 去命中只有哪些文件需要被處理。

以采用 ES6 的項(xiàng)目為例,在配置 babel-loader 時(shí),可以這樣:

module.exports = {
  module: {
    rules: [
      {
        // 如果項(xiàng)目源碼中只有 js 文件就不要寫(xiě)成 /.jsx?$/,提升正則表達(dá)式性能
        test: /.js$/,
        // babel-loader 支持緩存轉(zhuǎn)換出的結(jié)果,通過(guò) cacheDirectory 選項(xiàng)開(kāi)啟
        use: ["babel-loader?cacheDirectory"],
        // 只對(duì)項(xiàng)目根目錄下的 src 目錄中的文件采用 babel-loader
        include: path.resolve(__dirname, "src"),
      },
    ]
  },
};


你可以適當(dāng)?shù)恼{(diào)整項(xiàng)目的目錄結(jié)構(gòu),以方便在配置 Loader 時(shí)通過(guò) include 去縮小命中范圍。
優(yōu)化 resolve.modules 配置

在 Resolve 中介紹過(guò) resolve.modules 用于配置 Webpack 去哪些目錄下尋找第三方模塊。

resolve.modules 的默認(rèn)值是 ["node_modules"],含義是先去當(dāng)前目錄下的 ./node_modules 目錄下去找想找的模塊,如果沒(méi)找到就去上一級(jí)目錄 ../node_modules 中找,再?zèng)]有就去 ../../node_modules 中找,以此類推,這和 Node.js 的模塊尋找機(jī)制很相似。

當(dāng)安裝的第三方模塊都放在項(xiàng)目根目錄下的 ./node_modules 目錄下時(shí),沒(méi)有必要按照默認(rèn)的方式去一層層的尋找,可以指明存放第三方模塊的絕對(duì)路徑,以減少尋找,配置如下:

module.exports = {
  resolve: {
    // 使用絕對(duì)路徑指明第三方模塊存放的位置,以減少搜索步驟
    // 其中 __dirname 表示當(dāng)前工作目錄,也就是項(xiàng)目根目錄
    modules: [path.resolve(__dirname, "node_modules")]
  },
};
優(yōu)化 resolve.mainFields 配置

在 Resolve 中介紹過(guò) resolve.mainFields 用于配置第三方模塊使用哪個(gè)入口文件。

安裝的第三方模塊中都會(huì)有一個(gè) package.json 文件用于描述這個(gè)模塊的屬性,其中有些字段用于描述入口文件在哪里,resolve.mainFields 用于配置采用哪個(gè)字段作為入口文件的描述。

可以存在多個(gè)字段描述入口文件的原因是因?yàn)橛行┠K可以同時(shí)用在多個(gè)環(huán)境中,準(zhǔn)對(duì)不同的運(yùn)行環(huán)境需要使用不同的代碼。 以 isomorphic-fetch 為例,它是 fetch API 的一個(gè)實(shí)現(xiàn),但可同時(shí)用于瀏覽器和 Node.js 環(huán)境。 它的 package.json 中就有2個(gè)入口文件描述字段:

{
  "browser": "fetch-npm-browserify.js",
  "main": "fetch-npm-node.js"
}   
isomorphic-fetch 在不同的運(yùn)行環(huán)境下使用不同的代碼是因?yàn)?fetch API 的實(shí)現(xiàn)機(jī)制不一樣,在瀏覽器中通過(guò)原生的 fetch 或者 XMLHttpRequest 實(shí)現(xiàn),在 Node.js 中通過(guò) http 模塊實(shí)現(xiàn)。

resolve.mainFields 的默認(rèn)值和當(dāng)前的 target 配置有關(guān)系,對(duì)應(yīng)關(guān)系如下:

當(dāng) targetweb 或者 webworker 時(shí),值是 ["browser", "module", "main"]

當(dāng) target 為其它情況時(shí),值是 ["module", "main"]

target 等于 web 為例,Webpack 會(huì)先采用第三方模塊中的 browser 字段去尋找模塊的入口文件,如果不存在就采用 module 字段,以此類推。

為了減少搜索步驟,在你明確第三方模塊的入口文件描述字段時(shí),你可以把它設(shè)置的盡量少。 由于大多數(shù)第三方模塊都采用 main 字段去描述入口文件的位置,可以這樣配置 Webpack:

module.exports = {
  resolve: {
    // 只采用 main 字段作為入口文件描述字段,以減少搜索步驟
    mainFields: ["main"],
  },
};   
使用本方法優(yōu)化時(shí),你需要考慮到所有運(yùn)行時(shí)依賴的第三方模塊的入口文件描述字段,就算有一個(gè)模塊搞錯(cuò)了都可能會(huì)造成構(gòu)建出的代碼無(wú)法正常運(yùn)行。
優(yōu)化 resolve.alias 配置

resolve.alias 配置項(xiàng)通過(guò)別名來(lái)把原導(dǎo)入路徑映射成一個(gè)新的導(dǎo)入路徑。

在實(shí)戰(zhàn)項(xiàng)目中經(jīng)常會(huì)依賴一些龐大的第三方模塊,以 React 庫(kù)為例,安裝到 node_modules 目錄下的 React 庫(kù)的目錄結(jié)構(gòu)如下:

├── dist
│   ├── react.js
│   └── react.min.js
├── lib
│   ... 還有幾十個(gè)文件被忽略
│   ├── LinkedStateMixin.js
│   ├── createClass.js
│   └── React.js
├── package.json
└── react.js

可以看到發(fā)布出去的 React 庫(kù)中包含兩套代碼:

一套是采用 CommonJS 規(guī)范的模塊化代碼,這些文件都放在 lib 目錄下,以 package.json 中指定的入口文件 react.js 為模塊的入口。

一套是把 React 所有相關(guān)的代碼打包好的完整代碼放到一個(gè)多帶帶的文件中,這些代碼沒(méi)有采用模塊化可以直接執(zhí)行。其中 dist/react.js 是用于開(kāi)發(fā)環(huán)境,里面包含檢查和警告的代碼。dist/react.min.js 是用于線上環(huán)境,被最小化了。

默認(rèn)情況下 Webpack 會(huì)從入口文件 ./node_modules/react/react.js 開(kāi)始遞歸的解析和處理依賴的幾十個(gè)文件,這會(huì)時(shí)一個(gè)耗時(shí)的操作。 通過(guò)配置 resolve.alias 可以讓 Webpack 在處理 React 庫(kù)時(shí),直接使用多帶帶完整的 react.min.js 文件,從而跳過(guò)耗時(shí)的遞歸解析操作。

相關(guān) Webpack 配置如下:

module.exports = {
  resolve: {
    // 使用 alias 把導(dǎo)入 react 的語(yǔ)句換成直接使用多帶帶完整的 react.min.js 文件,
    // 減少耗時(shí)的遞歸解析操作
    alias: {
      "react": path.resolve(__dirname, "./node_modules/react/dist/react.min.js"),
    }
  },
};

除了 React 庫(kù)外,大多數(shù)庫(kù)發(fā)布到 Npm 倉(cāng)庫(kù)中時(shí)都會(huì)包含打包好的完整文件,對(duì)于這些庫(kù)你也可以對(duì)它們配置 alias。

但是對(duì)于有些庫(kù)使用本優(yōu)化方法后會(huì)影響到后面要講的使用 Tree-Shaking 去除無(wú)效代碼的優(yōu)化,因?yàn)榇虬玫耐暾募杏胁糠执a你的項(xiàng)目可能永遠(yuǎn)用不上。 一般對(duì)整體性比較強(qiáng)的庫(kù)采用本方法優(yōu)化,因?yàn)橥暾募械拇a是一個(gè)整體,每一行都是不可或缺的。 但是對(duì)于一些工具類的庫(kù),例如 lodash,你的項(xiàng)目可能只用到了其中幾個(gè)工具函數(shù),你就不能使用本方法去優(yōu)化,因?yàn)檫@會(huì)導(dǎo)致你的輸出代碼中包含很多永遠(yuǎn)不會(huì)執(zhí)行的代碼。

優(yōu)化 resolve.extensions 配置

在導(dǎo)入語(yǔ)句沒(méi)帶文件后綴時(shí),Webpack 會(huì)自動(dòng)帶上后綴后去嘗試詢問(wèn)文件是否存在。resolve.extensions 用于配置在嘗試過(guò)程中用到的后綴列表,默認(rèn)是:

extensions: [".js", ".json"]

也就是說(shuō)當(dāng)遇到 require("./data") 這樣的導(dǎo)入語(yǔ)句時(shí),Webpack 會(huì)先去尋找 ./data.js 文件,如果該文件不存在就去尋找 ./data.json 文件,如果還是找不到就報(bào)錯(cuò)。

如果這個(gè)列表越長(zhǎng),或者正確的后綴在越后面,就會(huì)造成嘗試的次數(shù)越多,所以 resolve.extensions 的配置也會(huì)影響到構(gòu)建的性能。 在配置 resolve.extensions 時(shí)你需要遵守以下幾點(diǎn),以做到盡可能的優(yōu)化構(gòu)建性能:

后綴嘗試列表要盡可能的小,不要把項(xiàng)目中不可能存在的情況寫(xiě)到后綴嘗試列表中。

頻率出現(xiàn)最高的文件后綴要優(yōu)先放在最前面,以做到盡快的退出尋找過(guò)程。

在源碼中寫(xiě)導(dǎo)入語(yǔ)句時(shí),要盡可能的帶上后綴,從而可以避免尋找過(guò)程。例如在你確定的情況下把 require("./data") 寫(xiě)成 require("./data.json")。

相關(guān) Webpack 配置如下:

module.exports = {
  resolve: {
    // 盡可能的減少后綴嘗試的可能性
    extensions: ["js"],
  },
};
優(yōu)化 module.noParse 配置

module.noParse 配置項(xiàng)可以讓 Webpack 忽略對(duì)部分沒(méi)采用模塊化的文件的遞歸解析處理,這樣做的好處是能提高構(gòu)建性能。 原因是一些庫(kù),例如 jQuery 、ChartJS, 它們龐大又沒(méi)有采用模塊化標(biāo)準(zhǔn),讓 Webpack 去解析這些文件耗時(shí)又沒(méi)有意義。

在上面的 優(yōu)化 resolve.alias 配置 中講到多帶帶完整的 react.min.js 文件就沒(méi)有采用模塊化,讓我們來(lái)通過(guò)配置 module.noParse 忽略對(duì) react.min.js 文件的遞歸解析處理, 相關(guān) Webpack 配置如下:

const path = require("path");

module.exports = {
  module: {
    // 獨(dú)完整的 `react.min.js` 文件就沒(méi)有采用模塊化,忽略對(duì) `react.min.js` 文件的遞歸解析處理
    noParse: [/react.min.js$/],
  },
};
注意被忽略掉的文件里不應(yīng)該包含 importrequire 、 define 等模塊化語(yǔ)句,不然會(huì)導(dǎo)致構(gòu)建出的代碼中包含無(wú)法在瀏覽器環(huán)境下執(zhí)行的模塊化語(yǔ)句。

以上就是所有和縮小文件搜索范圍相關(guān)的構(gòu)建性能優(yōu)化了,在根據(jù)自己項(xiàng)目的需要去按照以上方法改造后,你的構(gòu)建速度一定會(huì)有所提升。

使用 DllPlugin

要給 Web 項(xiàng)目構(gòu)建接入動(dòng)態(tài)鏈接庫(kù)的思想,需要完成以下事情:

把網(wǎng)頁(yè)依賴的基礎(chǔ)模塊抽離出來(lái),打包到一個(gè)個(gè)多帶帶的動(dòng)態(tài)鏈接庫(kù)中去。一個(gè)動(dòng)態(tài)鏈接庫(kù)中可以包含多個(gè)模塊。

當(dāng)需要導(dǎo)入的模塊存在于某個(gè)動(dòng)態(tài)鏈接庫(kù)中時(shí),這個(gè)模塊不能被再次被打包,而是去動(dòng)態(tài)鏈接庫(kù)中獲取。

當(dāng)需要導(dǎo)入的模塊存在于某個(gè)動(dòng)態(tài)鏈接庫(kù)中時(shí),這個(gè)模塊不能被再次被打包,而是去動(dòng)態(tài)鏈接庫(kù)中獲取。

為什么給 Web 項(xiàng)目構(gòu)建接入動(dòng)態(tài)鏈接庫(kù)的思想后,會(huì)大大提升構(gòu)建速度呢? 原因在于包含大量復(fù)用模塊的動(dòng)態(tài)鏈接庫(kù)只需要編譯一次,在之后的構(gòu)建過(guò)程中被動(dòng)態(tài)鏈接庫(kù)包含的模塊將不會(huì)在重新編譯,而是直接使用動(dòng)態(tài)鏈接庫(kù)中的代碼。 由于動(dòng)態(tài)鏈接庫(kù)中大多數(shù)包含的是常用的第三方模塊,例如 react、react-dom,只要不升級(jí)這些模塊的版本,動(dòng)態(tài)鏈接庫(kù)就不用重新編譯。

接入 Webpack

Webpack 已經(jīng)內(nèi)置了對(duì)動(dòng)態(tài)鏈接庫(kù)的支持,需要通過(guò)2個(gè)內(nèi)置的插件接入,它們分別是:

DllPlugin 插件:用于打包出一個(gè)個(gè)多帶帶的動(dòng)態(tài)鏈接庫(kù)文件。

DllReferencePlugin 插件:用于在主要配置文件中去引入 DllPlugin 插件打包好的動(dòng)態(tài)鏈接庫(kù)文件。

下面以基本的 React 項(xiàng)目為例,為其接入 DllPlugin,在開(kāi)始前先來(lái)看下最終構(gòu)建出的目錄結(jié)構(gòu):

├── main.js
├── polyfill.dll.js
├── polyfill.manifest.json
├── react.dll.js
└── react.manifest.json

其中包含兩個(gè)動(dòng)態(tài)鏈接庫(kù)文件,分別是:

polyfill.dll.js 里面包含項(xiàng)目所有依賴的 polyfill,例如 Promise、fetch 等 API。

react.dll.js 里面包含 React 的基礎(chǔ)運(yùn)行環(huán)境,也就是 reactreact-dom 模塊。

react.dll.js 文件為例,其文件內(nèi)容大致如下:

var _dll_react = (function(modules) {
  // ... 此處省略 webpackBootstrap 函數(shù)代碼
}([
  function(module, exports, __webpack_require__) {
    // 模塊 ID 為 0 的模塊對(duì)應(yīng)的代碼
  },
  function(module, exports, __webpack_require__) {
    // 模塊 ID 為 1 的模塊對(duì)應(yīng)的代碼
  },
  // ... 此處省略剩下的模塊對(duì)應(yīng)的代碼 
]));

可見(jiàn)一個(gè)動(dòng)態(tài)鏈接庫(kù)文件中包含了大量模塊的代碼,這些模塊存放在一個(gè)數(shù)組里,用數(shù)組的索引號(hào)作為 ID。 并且還通過(guò) _dll_react 變量把自己暴露在了全局中,也就是可以通過(guò) window._dll_react 可以訪問(wèn)到它里面包含的模塊。

其中 polyfill.manifest.jsonreact.manifest.json 文件也是由 DllPlugin 生成出,用于描述動(dòng)態(tài)鏈接庫(kù)文件中包含哪些模塊, 以 react.manifest.json 文件為例,其文件內(nèi)容大致如下:

See the Pen react.manifest.json by whjin (@whjin) on CodePen.


以上就是所有接入 DllPlugin 后最終編譯出來(lái)的代碼,接下來(lái)教你如何實(shí)現(xiàn)。

構(gòu)建出動(dòng)態(tài)鏈接庫(kù)文件

構(gòu)建輸出的以下這四個(gè)文件:

├── polyfill.dll.js
├── polyfill.manifest.json
├── react.dll.js
└── react.manifest.json

和以下這一個(gè)文件:

├── main.js

是由兩份不同的構(gòu)建分別輸出的。

動(dòng)態(tài)鏈接庫(kù)文件相關(guān)的文件需要由一份獨(dú)立的構(gòu)建輸出,用于給主構(gòu)建使用。新建一個(gè) Webpack 配置文件 webpack_dll.config.js 專門用于構(gòu)建它們,文件內(nèi)容如下:

See the Pen webpack_dll.config.js by whjin (@whjin) on CodePen.


注意:在 webpack_dll.config.js 文件中,DllPlugin 中的 name 參數(shù)必須和 output.library 中保持一致。 原因在于 DllPlugin 中的 name 參數(shù)會(huì)影響輸出的 manifest.json 文件中 name 字段的值, 而在 webpack.config.js 文件中 DllReferencePlugin 會(huì)去 manifest.json 文件讀取 name 字段的值, 把值的內(nèi)容作為在從全局變量中獲取動(dòng)態(tài)鏈接庫(kù)中內(nèi)容時(shí)的全局變量名。
執(zhí)行構(gòu)建

在修改好以上兩個(gè) Webpack 配置文件后,需要重新執(zhí)行構(gòu)建。 重新執(zhí)行構(gòu)建時(shí)要注意的是需要先把動(dòng)態(tài)鏈接庫(kù)相關(guān)的文件編譯出來(lái),因?yàn)橹?Webpack 配置文件中定義的 DllReferencePlugin 依賴這些文件。

執(zhí)行構(gòu)建時(shí)流程如下:

如果動(dòng)態(tài)鏈接庫(kù)相關(guān)的文件還沒(méi)有編譯出來(lái),就需要先把它們編譯出來(lái)。方法是執(zhí)行 webpack --config webpack_dll.config.js 命令。

在確保動(dòng)態(tài)鏈接庫(kù)存在時(shí),才能正常的編譯出入口執(zhí)行文件。方法是執(zhí)行 webpack 命令。這時(shí)你會(huì)發(fā)現(xiàn)構(gòu)建速度有了非常大的提升。

使用 HappyPack

由于有大量文件需要解析和處理,構(gòu)建是文件讀寫(xiě)和計(jì)算密集型的操作,特別是當(dāng)文件數(shù)量變多后,Webpack 構(gòu)建慢的問(wèn)題會(huì)顯得嚴(yán)重。 運(yùn)行在 Node.js 之上的 Webpack 是單線程模型的,也就是說(shuō) Webpack 需要處理的任務(wù)需要一件件挨著做,不能多個(gè)事情一起做。

文件讀寫(xiě)和計(jì)算操作是無(wú)法避免的,那能不能讓 Webpack 同一時(shí)刻處理多個(gè)任務(wù),發(fā)揮多核 CPU 電腦的威力,以提升構(gòu)建速度呢?

HappyPack 就能讓 Webpack 做到這點(diǎn),它把任務(wù)分解給多個(gè)子進(jìn)程去并發(fā)的執(zhí)行,子進(jìn)程處理完后再把結(jié)果發(fā)送給主進(jìn)程。

由于 JavaScript 是單線程模型,要想發(fā)揮多核 CPU 的能力,只能通過(guò)多進(jìn)程去實(shí)現(xiàn),而無(wú)法通過(guò)多線程實(shí)現(xiàn)。

分解任務(wù)和管理線程的事情 HappyPack 都會(huì)幫你做好,你所需要做的只是接入 HappyPack。 接入 HappyPack 的相關(guān)代碼如下:

See the Pen HappyPack by whjin (@whjin) on CodePen.


接入 HappyPack 后,你需要給項(xiàng)目安裝新的依賴:

npm i -D happypack
HappyPack 原理

在整個(gè) Webpack 構(gòu)建流程中,最耗時(shí)的流程可能就是 Loader 對(duì)文件的轉(zhuǎn)換操作了,因?yàn)橐D(zhuǎn)換的文件數(shù)據(jù)巨多,而且這些轉(zhuǎn)換操作都只能一個(gè)個(gè)挨著處理。 HappyPack 的核心原理就是把這部分任務(wù)分解到多個(gè)進(jìn)程去并行處理,從而減少了總的構(gòu)建時(shí)間。

從前面的使用中可以看出所有需要通過(guò) Loader 處理的文件都先交給了 happypack/loader 去處理,收集到了這些文件的處理權(quán)后 HappyPack 就好統(tǒng)一分配了。

每通過(guò) new HappyPack() 實(shí)例化一個(gè) HappyPack 其實(shí)就是告訴 HappyPack 核心調(diào)度器如何通過(guò)一系列 Loader 去轉(zhuǎn)換一類文件,并且可以指定如何給這類轉(zhuǎn)換操作分配子進(jìn)程。

核心調(diào)度器的邏輯代碼在主進(jìn)程中,也就是運(yùn)行著 Webpack 的進(jìn)程中,核心調(diào)度器會(huì)把一個(gè)個(gè)任務(wù)分配給當(dāng)前空閑的子進(jìn)程,子進(jìn)程處理完畢后把結(jié)果發(fā)送給核心調(diào)度器,它們之間的數(shù)據(jù)交換是通過(guò)進(jìn)程間通信 API 實(shí)現(xiàn)的。

核心調(diào)度器收到來(lái)自子進(jìn)程處理完畢的結(jié)果后會(huì)通知 Webpack 該文件處理完畢。

使用 ParallelUglifyPlugin

在使用 Webpack 構(gòu)建出用于發(fā)布到線上的代碼時(shí),都會(huì)有壓縮代碼這一流程。 最常見(jiàn)的 JavaScript 代碼壓縮工具是 UglifyJS,并且 Webpack 也內(nèi)置了它。

用過(guò) UglifyJS 的你一定會(huì)發(fā)現(xiàn)在構(gòu)建用于開(kāi)發(fā)環(huán)境的代碼時(shí)很快就能完成,但在構(gòu)建用于線上的代碼時(shí)構(gòu)建一直卡在一個(gè)時(shí)間點(diǎn)遲遲沒(méi)有反應(yīng),其實(shí)卡住的這個(gè)時(shí)候就是在進(jìn)行代碼壓縮。

由于壓縮 JavaScript 代碼需要先把代碼解析成用 Object 抽象表示的 AST 語(yǔ)法樹(shù),再去應(yīng)用各種規(guī)則分析和處理 AST,導(dǎo)致這個(gè)過(guò)程計(jì)算量巨大,耗時(shí)非常多。

為什么不把在使用 HappyPack中介紹過(guò)的多進(jìn)程并行處理的思想也引入到代碼壓縮中呢?

ParallelUglifyPlugin 就做了這個(gè)事情。 當(dāng) Webpack 有多個(gè) JavaScript 文件需要輸出和壓縮時(shí),原本會(huì)使用 UglifyJS 去一個(gè)個(gè)挨著壓縮再輸出, 但是 ParallelUglifyPlugin 則會(huì)開(kāi)啟多個(gè)子進(jìn)程,把對(duì)多個(gè)文件的壓縮工作分配給多個(gè)子進(jìn)程去完成,每個(gè)子進(jìn)程其實(shí)還是通過(guò) UglifyJS 去壓縮代碼,但是變成了并行執(zhí)行。 所以 ParallelUglifyPlugin 能更快的完成對(duì)多個(gè)文件的壓縮工作。

使用 ParallelUglifyPlugin 也非常簡(jiǎn)單,把原來(lái) Webpack 配置文件中內(nèi)置的 UglifyJsPlugin 去掉后,再替換成 ParallelUglifyPlugin,相關(guān)代碼如下:

See the Pen ParallelUglifyPlugin by whjin (@whjin) on CodePen.


給網(wǎng)頁(yè)注入以上腳本后,獨(dú)立打開(kāi)的網(wǎng)頁(yè)就能自動(dòng)刷新了。但是要注意在發(fā)布到線上時(shí)記得刪除掉這段用于開(kāi)發(fā)環(huán)境的代碼。

開(kāi)啟模塊熱替換

要做到實(shí)時(shí)預(yù)覽,除了在使用自動(dòng)刷新中介紹的刷新整個(gè)網(wǎng)頁(yè)外,DevServer 還支持一種叫做模塊熱替換(Hot Module Replacement)的技術(shù)可在不刷新整個(gè)網(wǎng)頁(yè)的情況下做到超靈敏的實(shí)時(shí)預(yù)覽。 原理是當(dāng)一個(gè)源碼發(fā)生變化時(shí),只重新編譯發(fā)生變化的模塊,再用新輸出的模塊替換掉瀏覽器中對(duì)應(yīng)的老模塊。

模塊熱替換技術(shù)的優(yōu)勢(shì)有:

實(shí)時(shí)預(yù)覽反應(yīng)更快,等待時(shí)間更短。

不刷新瀏覽器能保留當(dāng)前網(wǎng)頁(yè)的運(yùn)行狀態(tài),例如在使用 Redux 來(lái)管理數(shù)據(jù)的應(yīng)用中搭配模塊熱替換能做到代碼更新時(shí) Redux 中的數(shù)據(jù)還保持不變。

總的來(lái)說(shuō)模塊熱替換技術(shù)很大程度上的提高了開(kāi)發(fā)效率和體驗(yàn)。

模塊熱替換的原理

模塊熱替換的原理和自動(dòng)刷新原理類似,都需要往要開(kāi)發(fā)的網(wǎng)頁(yè)中注入一個(gè)代理客戶端用于連接 DevServer 和網(wǎng)頁(yè), 不同在于模塊熱替換獨(dú)特的模塊替換機(jī)制。

DevServer 默認(rèn)不會(huì)開(kāi)啟模塊熱替換模式,要開(kāi)啟該模式,只需在啟動(dòng)時(shí)帶上參數(shù) --hot,完整命令是 webpack-dev-server --hot。

除了通過(guò)在啟動(dòng)時(shí)帶上 --hot 參數(shù),還可以通過(guò)接入 Plugin 實(shí)現(xiàn),相關(guān)代碼如下:

const HotModuleReplacementPlugin = require("webpack/lib/HotModuleReplacementPlugin");

module.exports = {
  entry:{
    // 為每個(gè)入口都注入代理客戶端
    main:["webpack-dev-server/client?http://localhost:8080/", "webpack/hot/dev-server","./src/main.js"],
  },
  plugins: [
    // 該插件的作用就是實(shí)現(xiàn)模塊熱替換,實(shí)際上當(dāng)啟動(dòng)時(shí)帶上 `--hot` 參數(shù),會(huì)注入該插件,生成 .hot-update.json 文件。
    new HotModuleReplacementPlugin(),
  ],
  devServer:{
    // 告訴 DevServer 要開(kāi)啟模塊熱替換模式
    hot: true,      
  }  
};

在啟動(dòng) Webpack 時(shí)帶上參數(shù) --hot 其實(shí)就是自動(dòng)為你完成以上配置。

相比于自動(dòng)刷新的代理客戶端,多出了后三個(gè)用于模塊熱替換的文件,也就是說(shuō)代理客戶端更大了。

可見(jiàn)補(bǔ)丁中包含了 main.css 文件新編譯出來(lái) CSS 代碼,網(wǎng)頁(yè)中的樣式也立刻變成了源碼中描述的那樣。

但當(dāng)你修改 main.js 文件時(shí),會(huì)發(fā)現(xiàn)模塊熱替換沒(méi)有生效,而是整個(gè)頁(yè)面被刷新了,為什么修改 main.js 文件時(shí)會(huì)這樣呢?

Webpack 為了讓使用者在使用了模塊熱替換功能時(shí)能靈活地控制老模塊被替換時(shí)的邏輯,可以在源碼中定義一些代碼去做相應(yīng)的處理。

把的 main.js 文件改為如下:

See the Pen main.js by whjin (@whjin) on CodePen.


同時(shí),為了不讓 babel-loader 輸出 ES5 語(yǔ)法的代碼,需要去掉 .babelrc 配置文件中的 babel-preset-env,但是其它的 Babel 插件,比如 babel-preset-react 還是要保留, 因?yàn)檎?babel-preset-env 負(fù)責(zé)把 ES6 代碼轉(zhuǎn)換為 ES5 代碼。

壓縮 CSS

CSS 代碼也可以像 JavaScript 那樣被壓縮,以達(dá)到提升加載速度和代碼混淆的作用。 目前比較成熟可靠的 CSS 壓縮工具是 cssnano,基于 PostCSS。

cssnano 能理解 CSS 代碼的含義,而不僅僅是刪掉空格,例如:

margin: 10px 20px 10px 20px 被壓縮成 margin: 10px 20px

color: #ff0000 被壓縮成 color:red

還有很多壓縮規(guī)則可以去其官網(wǎng)查看,通常壓縮率能達(dá)到 60%。

cssnano 接入到 Webpack 中也非常簡(jiǎn)單,因?yàn)?css-loader 已經(jīng)將其內(nèi)置了,要開(kāi)啟 cssnano 去壓縮代碼只需要開(kāi)啟 css-loaderminimize 選項(xiàng)。 相關(guān) Webpack 配置如下:

See the Pen cssnano by whjin (@whjin) on CodePen.


app_a6976b6d.css內(nèi)容如下:

body{background:url(arch_ae805d49.png) repeat}h1{color:red}

可以看出到導(dǎo)入資源時(shí)都是通過(guò)相對(duì)路徑去訪問(wèn)的,當(dāng)把這些資源都放到同一個(gè) CDN 服務(wù)上去時(shí),網(wǎng)頁(yè)是能正常使用的。 但需要注意的是由于 CDN 服務(wù)一般都會(huì)給資源開(kāi)啟很長(zhǎng)時(shí)間的緩存,例如用戶從 CDN 上獲取到了 index.html 這個(gè)文件后, 即使之后的發(fā)布操作把 index.html 文件給重新覆蓋了,但是用戶在很長(zhǎng)一段時(shí)間內(nèi)還是運(yùn)行的之前的版本,這會(huì)新的導(dǎo)致發(fā)布不能立即生效。

要避免以上問(wèn)題,業(yè)界比較成熟的做法是這樣的:

針對(duì) HTML 文件:不開(kāi)啟緩存,把 HTML 放到自己的服務(wù)器上,而不是 CDN 服務(wù)上,同時(shí)關(guān)閉自己服務(wù)器上的緩存。自己的服務(wù)器只提供 HTML 文件和數(shù)據(jù)接口。

針對(duì)靜態(tài)的 JavaScript、CSS、圖片等文件:開(kāi)啟 CDN 和緩存,上傳到 CDN 服務(wù)上去,同時(shí)給每個(gè)文件名帶上由文件內(nèi)容算出的 Hash 值, 例如上面的 app_a6976b6d.css 文件。 帶上 Hash 值的原因是文件名會(huì)隨著文件內(nèi)容而變化,只要文件發(fā)生變化其對(duì)應(yīng)的 URL 就會(huì)變化,它就會(huì)被重新下載,無(wú)論緩存時(shí)間有多長(zhǎng)。

采用以上方案后,在 HTML 文件中的資源引入地址也需要換成 CDN 服務(wù)提供的地址,例如以上的 index.html 變?yōu)槿缦拢?/p>



  
  


并且 app_a6976b6d.css 的內(nèi)容也應(yīng)該變?yōu)槿缦拢?/p>

也就是說(shuō),之前的相對(duì)路徑,都變成了絕對(duì)的指向 CDN 服務(wù)的 URL 地址。

如果你對(duì)形如 //cdn.com/id/app_a6976b6d.css 這樣的 URL 感到陌生,你需要知道這種 URL 省掉了前面的 http: 或者 https: 前綴, 這樣做的好處時(shí)在訪問(wèn)這些資源的時(shí)候會(huì)自動(dòng)的根據(jù)當(dāng)前 HTML 的 URL 是采用什么模式去決定是采用 HTTP 還是 HTTPS 模式。

除此之外,如果你還知道瀏覽器有一個(gè)規(guī)則是同一時(shí)刻針對(duì)同一個(gè)域名的資源并行請(qǐng)求是有限制的話(具體數(shù)字大概4個(gè)左右,不同瀏覽器可能不同), 你會(huì)發(fā)現(xiàn)上面的做法有個(gè)很大的問(wèn)題。由于所有靜態(tài)資源都放到了同一個(gè) CDN 服務(wù)的域名下,也就是上面的 cdn.com。 如果網(wǎng)頁(yè)的資源很多,例如有很多圖片,就會(huì)導(dǎo)致資源的加載被阻塞,因?yàn)橥瑫r(shí)只能加載幾個(gè),必須等其它資源加載完才能繼續(xù)加載。 要解決這個(gè)問(wèn)題,可以把這些靜態(tài)資源分散到不同的 CDN 服務(wù)上去, 例如把 JavaScript 文件放到 js.cdn.com 域名下、把 CSS 文件放到 css.cdn.com 域名下、圖片文件放到 img.cdn.com 域名下, 這樣做之后 index.html 需要變成這樣:



  
  


使用了多個(gè)域名后又會(huì)帶來(lái)一個(gè)新問(wèn)題:增加域名解析時(shí)間。是否采用多域名分散資源需要根據(jù)自己的需求去衡量得失。 當(dāng)然你可以通過(guò)在 HTML HEAD 標(biāo)簽中 加入  去預(yù)解析域名,以降低域名解析帶來(lái)的延遲。
用 Webpack 實(shí)現(xiàn) CDN 的接入

總結(jié)上面所說(shuō)的,構(gòu)建需要實(shí)現(xiàn)以下幾點(diǎn):

靜態(tài)資源的導(dǎo)入 URL 需要變成指向 CDN 服務(wù)的絕對(duì)路徑的 URL 而不是相對(duì)于 HTML 文件的 URL。

靜態(tài)資源的文件名稱需要帶上有文件內(nèi)容算出來(lái)的 Hash 值,以防止被緩存。

不同類型的資源放到不同域名的 CDN 服務(wù)上去,以防止資源的并行加載被阻塞。

先來(lái)看下要實(shí)現(xiàn)以上要求的最終 Webpack 配置:

See the Pen CDN 的接入 by whjin (@whjin) on CodePen.


閱讀需要支付1元查看
<