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

資訊專欄INFORMATION COLUMN

webpack優(yōu)化

ChanceWong / 1168人閱讀

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

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

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

優(yōu)化開發(fā)體驗

優(yōu)化構(gòu)建速度。在項目龐大時構(gòu)建耗時可能會變的很長,每次等待構(gòu)建的耗時加起來也會是個大數(shù)目。

縮小文件搜索范圍

使用 DllPlugin

使用 HappyPack

使用 ParallelUglifyPlugin

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

使用自動刷新

開啟模塊熱替換

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

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

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

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

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

壓縮代碼

CDN 加速

使用 Tree Shaking

提取公共代碼

按需加載

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

使用 Prepack

開啟 Scope Hoisting

縮小文件搜索范圍

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

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

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

優(yōu)化 loader 配置

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

在 Module 中介紹過在使用 Loader 時可以通過 testinclude 、 exclude 三個配置項來命中 Loader 要應(yīng)用規(guī)則的文件。 為了盡可能少的讓文件被 Loader 處理,可以通過 include 去命中只有哪些文件需要被處理。

以采用 ES6 的項目為例,在配置 babel-loader 時,可以這樣:

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

resolve.alias 配置項通過別名來把原導(dǎo)入路徑映射成一個新的導(dǎo)入路徑。

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

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

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

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

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

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

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

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

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

但是對于有些庫使用本優(yōu)化方法后會影響到后面要講的使用 Tree-Shaking 去除無效代碼的優(yōu)化,因為打包好的完整文件中有部分代碼你的項目可能永遠(yuǎn)用不上。 一般對整體性比較強的庫采用本方法優(yōu)化,因為完整文件中的代碼是一個整體,每一行都是不可或缺的。 但是對于一些工具類的庫,例如 lodash,你的項目可能只用到了其中幾個工具函數(shù),你就不能使用本方法去優(yōu)化,因為這會導(dǎo)致你的輸出代碼中包含很多永遠(yuǎn)不會執(zhí)行的代碼。

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

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

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

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

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

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

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

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

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

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

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

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

const path = require("path");

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

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

使用 DllPlugin

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

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

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

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

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

接入 Webpack

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

DllPlugin 插件:用于打包出一個個多帶帶的動態(tài)鏈接庫文件。

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

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

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

其中包含兩個動態(tài)鏈接庫文件,分別是:

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

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

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

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

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

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

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


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

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

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

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

和以下這一個文件:

├── main.js

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

動態(tài)鏈接庫文件相關(guān)的文件需要由一份獨立的構(gòu)建輸出,用于給主構(gòu)建使用。新建一個 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ù)會影響輸出的 manifest.json 文件中 name 字段的值, 而在 webpack.config.js 文件中 DllReferencePlugin 會去 manifest.json 文件讀取 name 字段的值, 把值的內(nèi)容作為在從全局變量中獲取動態(tài)鏈接庫中內(nèi)容時的全局變量名。
執(zhí)行構(gòu)建

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

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

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

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

使用 HappyPack

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

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

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

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

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

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


接入 HappyPack 后,你需要給項目安裝新的依賴:

npm i -D happypack
HappyPack 原理

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

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

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

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

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

使用 ParallelUglifyPlugin

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

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

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

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

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

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

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


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

開啟模塊熱替換

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

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

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

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

總的來說模塊熱替換技術(shù)很大程度上的提高了開發(fā)效率和體驗。

模塊熱替換的原理

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

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

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

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

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

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

相比于自動刷新的代理客戶端,多出了后三個用于模塊熱替換的文件,也就是說代理客戶端更大了。

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

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

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

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

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


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

壓縮 CSS

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

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

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

color: #ff0000 被壓縮成 color:red

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

cssnano 接入到 Webpack 中也非常簡單,因為 css-loader 已經(jīng)將其內(nèi)置了,要開啟 cssnano 去壓縮代碼只需要開啟 css-loaderminimize 選項。 相關(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)入資源時都是通過相對路徑去訪問的,當(dāng)把這些資源都放到同一個 CDN 服務(wù)上去時,網(wǎng)頁是能正常使用的。 但需要注意的是由于 CDN 服務(wù)一般都會給資源開啟很長時間的緩存,例如用戶從 CDN 上獲取到了 index.html 這個文件后, 即使之后的發(fā)布操作把 index.html 文件給重新覆蓋了,但是用戶在很長一段時間內(nèi)還是運行的之前的版本,這會新的導(dǎo)致發(fā)布不能立即生效。

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

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

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

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



  
  


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

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

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

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



  
  


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

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

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

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

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

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

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


閱讀需要支付1元查看
<