成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

webpack優(yōu)化

ChanceWong / 1215人閱讀

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

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

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

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

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

縮小文件搜索范圍

使用 DllPlugin

使用 HappyPack

使用 ParallelUglifyPlugin

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

使用自動(dòng)刷新

開啟模塊熱替換

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

優(yōu)化輸出質(zhì)量的目的是為了給用戶呈現(xiàn)體驗(yàn)更好的網(wǎng)頁,例如減少首屏加載時(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

開啟 Scope Hoisting

縮小文件搜索范圍

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

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

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

優(yōu)化 loader 配置

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

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

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

module.exports = {
  module: {
    rules: [
      {
        // 如果項(xiàng)目源碼中只有 js 文件就不要寫成 /.jsx?$/,提升正則表達(dá)式性能
        test: /.js$/,
        // babel-loader 支持緩存轉(zhuǎn)換出的結(jié)果,通過 cacheDirectory 選項(xiàng)開啟
        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í)通過 include 去縮小命中范圍。
優(yōu)化 resolve.modules 配置

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

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

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

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

在 Resolve 中介紹過 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ī)制不一樣,在瀏覽器中通過原生的 fetch 或者 XMLHttpRequest 實(shí)現(xiàn),在 Node.js 中通過 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)建出的代碼無法正常運(yùn)行。
優(yōu)化 resolve.alias 配置

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

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

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

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

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

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

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

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

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

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

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

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

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

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

也就是說當(dāng)遇到 require("./data") 這樣的導(dǎo)入語句時(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àn)最高的文件后綴要優(yōu)先放在最前面,以做到盡快的退出尋找過程。

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

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

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

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

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

const path = require("path");

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

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

使用 DllPlugin

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

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

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

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

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

接入 Webpack

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

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

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

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

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

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

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)的代碼 
]));

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

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

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


可見 manifest.json 文件清楚地描述了與其對(duì)應(yīng)的 dll.js 文件中包含了哪些模塊,以及每個(gè)模塊的路徑和 ID。

main.js 文件是編譯出來的執(zhí)行入口文件,當(dāng)遇到其依賴的模塊在 dll.js 文件中時(shí),會(huì)直接通過 dll.js 文件暴露出的全局變量去獲取打包在 dll.js 文件的模塊。 所以在 index.html 文件中需要把依賴的兩個(gè) dll.js 文件給加載進(jìn)去,index.html 內(nèi)容如下:



  


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

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

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

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

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

├── main.js

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

動(dòng)態(tài)鏈接庫文件相關(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.


使用動(dòng)態(tài)鏈接庫文件

構(gòu)建出的動(dòng)態(tài)鏈接庫文件用于給其它地方使用,在這里也就是給執(zhí)行入口使用。

用于輸出 main.js 的主 Webpack 配置文件內(nèi)容如下:

See the Pen main.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)鏈接庫中內(nèi)容時(shí)的全局變量名。
執(zhí)行構(gòu)建

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

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

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

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

使用 HappyPack

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

文件讀寫和計(jì)算操作是無法避免的,那能不能讓 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 的能力,只能通過多進(jìn)程去實(shí)現(xiàn),而無法通過多線程實(shí)現(xiàn)。

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

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


以上代碼有兩點(diǎn)重要的修改:

在 Loader 配置中,所有文件的處理都交給了 happypack/loader 去處理,使用緊跟其后的 querystring ?id=babel 去告訴 happypack/loader 去選擇哪個(gè) HappyPack 實(shí)例去處理文件。

在 Plugin 配置中,新增了兩個(gè) HappyPack 實(shí)例分別用于告訴 happypack/loader 去如何處理 .js .css 文件。選項(xiàng)中的 id 屬性的值和上面 querystring 中的 ?id=babel 相對(duì)應(yīng),選項(xiàng)中的 loaders 屬性和 Loader 配置中一樣。

在實(shí)例化 HappyPack 插件的時(shí)候,除了可以傳入 idloaders 兩個(gè)參數(shù)外,HappyPack 還支持如下參數(shù):

threads 代表開啟幾個(gè)子進(jìn)程去處理這一類型的文件,默認(rèn)是3個(gè),類型必須是整數(shù)。

verbose 是否允許 HappyPack 輸出日志,默認(rèn)是 true

threadPool 代表共享進(jìn)程池,即多個(gè) HappyPack 實(shí)例都使用同一個(gè)共享進(jìn)程池中的子進(jìn)程去處理任務(wù),以防止資源占用過多,相關(guān)代碼如下:

See the Pen threadPool 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í)間。

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

每通過 new HappyPack() 實(shí)例化一個(gè) HappyPack 其實(shí)就是告訴 HappyPack 核心調(diào)度器如何通過一系列 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ù)交換是通過進(jìn)程間通信 API 實(shí)現(xiàn)的。

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

使用 ParallelUglifyPlugin

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

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

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

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

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

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

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


在通過 new ParallelUglifyPlugin() 實(shí)例化時(shí),支持以下參數(shù):

test:使用正則去匹配哪些文件需要被 ParallelUglifyPlugin 壓縮,默認(rèn)是 /.js$/,也就是默認(rèn)壓縮所有的 .js 文件。

include:使用正則去命中需要被 ParallelUglifyPlugin 壓縮的文件。默認(rèn)為 []。

exclude:使用正則去命中不需要被 ParallelUglifyPlugin 壓縮的文件。默認(rèn)為 []。

cacheDir:緩存壓縮后的結(jié)果,下次遇到一樣的輸入時(shí)直接從緩存中獲取壓縮后的結(jié)果并返回。cacheDir 用于配置緩存存放的目錄路徑。默認(rèn)不會(huì)緩存,想開啟緩存請(qǐng)?jiān)O(shè)置一個(gè)目錄路徑。

workerCount:開啟幾個(gè)子進(jìn)程去并發(fā)的執(zhí)行壓縮。默認(rèn)是當(dāng)前運(yùn)行電腦的 CPU 核數(shù)減去1。

sourceMap:是否輸出 Source Map,這會(huì)導(dǎo)致壓縮過程變慢。

uglifyJS:用于壓縮 ES5 代碼時(shí)的配置,Object 類型,直接透?jìng)鹘o UglifyJS 的參數(shù)。

uglifyES:用于壓縮 ES6 代碼時(shí)的配置,Object 類型,直接透?jìng)鹘o UglifyES 的參數(shù)。

其中的 testinclude、exclude 與配置 Loader 時(shí)的思想和用法一樣。

UglifyES 是 UglifyJS 的變種,專門用于壓縮 ES6 代碼,它們兩都出自于同一個(gè)項(xiàng)目,并且它們兩不能同時(shí)使用。

UglifyES 一般用于給比較新的 JavaScript 運(yùn)行環(huán)境壓縮代碼,例如用于 ReactNative 的代碼運(yùn)行在兼容性較好的 JavaScriptCore 引擎中,為了得到更好的性能和尺寸,采用 UglifyES 壓縮效果會(huì)更好。

ParallelUglifyPlugin 同時(shí)內(nèi)置了 UglifyJS 和 UglifyES,也就是說 ParallelUglifyPlugin 支持并行壓縮 ES6 代碼。

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

npm i -D webpack-parallel-uglify-plugin

安裝成功后,重新執(zhí)行構(gòu)建你會(huì)發(fā)現(xiàn)速度變快了許多。如果設(shè)置 cacheDir 開啟了緩存,在之后的構(gòu)建中會(huì)變的更快。

使用自動(dòng)刷新

在開發(fā)階段,修改源碼是不可避免的操作。 對(duì)于開發(fā)網(wǎng)頁來說,要想看到修改后的效果,需要刷新瀏覽器讓其重新運(yùn)行最新的代碼才行。 雖然這相比于開發(fā)原生 iOS 和 Android 應(yīng)用來說要方便很多,因?yàn)槟切枰匦戮幾g這個(gè)項(xiàng)目再運(yùn)行,但我們可以把這個(gè)體驗(yàn)優(yōu)化的更好。 借助自動(dòng)化的手段,可以把這些重復(fù)的操作交給代碼去幫我們完成,在監(jiān)聽到本地源碼文件發(fā)生變化時(shí),自動(dòng)重新構(gòu)建出可運(yùn)行的代碼后再控制瀏覽器刷新。

Webpack 把這些功能都內(nèi)置了,并且還提供多種方案可選。

文件監(jiān)聽

文件監(jiān)聽是在發(fā)現(xiàn)源碼文件發(fā)生變化時(shí),自動(dòng)重新構(gòu)建出新的輸出文件。

Webpack 官方提供了兩大模塊,一個(gè)是核心的 webpack,一個(gè)是在使用 DevServer 中提到的 webpack-dev-server 擴(kuò)展模塊。 而文件監(jiān)聽功能是 webpack 模塊提供的。

在其它配置項(xiàng)中曾介紹過 Webpack 支持文件監(jiān)聽相關(guān)的配置項(xiàng)如下:

module.export = {
  // 只有在開啟監(jiān)聽模式時(shí),watchOptions 才有意義
  // 默認(rèn)為 false,也就是不開啟
  watch: true,
  // 監(jiān)聽模式運(yùn)行時(shí)的參數(shù)
  // 在開啟監(jiān)聽模式時(shí),才有意義
  watchOptions: {
    // 不監(jiān)聽的文件或文件夾,支持正則匹配
    // 默認(rèn)為空
    ignored: /node_modules/,
    // 監(jiān)聽到變化發(fā)生后會(huì)等300ms再去執(zhí)行動(dòng)作,防止文件更新太快導(dǎo)致重新編譯頻率太高
    // 默認(rèn)為 300ms
    aggregateTimeout: 300,
    // 判斷文件是否發(fā)生變化是通過不停的去詢問系統(tǒng)指定文件有沒有變化實(shí)現(xiàn)的
    // 默認(rèn)每秒問 1000 次
    poll: 1000
  }
}

要讓 Webpack 開啟監(jiān)聽模式,有兩種方式:

在配置文件 webpack.config.js 中設(shè)置 watch: true

在執(zhí)行啟動(dòng) Webpack 命令時(shí),帶上 --watch 參數(shù),完整命令是 webpack --watch。

文件監(jiān)聽工作原理

在 Webpack 中監(jiān)聽一個(gè)文件發(fā)生變化的原理是定時(shí)的去獲取這個(gè)文件的最后編輯時(shí)間,每次都存下最新的最后編輯時(shí)間,如果發(fā)現(xiàn)當(dāng)前獲取的和最后一次保存的最后編輯時(shí)間不一致,就認(rèn)為該文件發(fā)生了變化。 配置項(xiàng)中的 watchOptions.poll 就是用于控制定時(shí)檢查的周期,具體含義是每秒檢查多少次。

當(dāng)發(fā)現(xiàn)某個(gè)文件發(fā)生了變化時(shí),并不會(huì)立刻告訴監(jiān)聽者,而是先緩存起來,收集一段時(shí)間的變化后,再一次性告訴監(jiān)聽者。 配置項(xiàng)中的 watchOptions.aggregateTimeout 就是用于配置這個(gè)等待時(shí)間。 這樣做的目的是因?yàn)槲覀冊(cè)诰庉嫶a的過程中可能會(huì)高頻的輸入文字導(dǎo)致文件變化的事件高頻的發(fā)生,如果每次都重新執(zhí)行構(gòu)建就會(huì)讓構(gòu)建卡死。

對(duì)于多個(gè)文件來說,原理相似,只不過會(huì)對(duì)列表中的每一個(gè)文件都定時(shí)的執(zhí)行檢查。 但是這個(gè)需要監(jiān)聽的文件列表是怎么確定的呢? 默認(rèn)情況下 Webpack 會(huì)從配置的 Entry 文件出發(fā),遞歸解析出 Entry 文件所依賴的文件,把這些依賴的文件都加入到監(jiān)聽列表中去。 可見 Webpack 這一點(diǎn)還是做的很智能的,不是粗暴的直接監(jiān)聽項(xiàng)目目錄下的所有文件。

由于保存文件的路徑和最后編輯時(shí)間需要占用內(nèi)存,定時(shí)檢查周期檢查需要占用 CPU 以及文件 I/O,所以最好減少需要監(jiān)聽的文件數(shù)量和降低檢查頻率。

優(yōu)化文件監(jiān)聽性能

在明白文件監(jiān)聽工作原理后,就好分析如何優(yōu)化文件監(jiān)聽性能了。

開啟監(jiān)聽模式時(shí),默認(rèn)情況下會(huì)監(jiān)聽配置的 Entry 文件和所有其遞歸依賴的文件。 在這些文件中會(huì)有很多存在于 node_modules 下,因?yàn)槿缃竦?Web 項(xiàng)目會(huì)依賴大量的第三方模塊。 在大多數(shù)情況下我們都不可能去編輯 node_modules 下的文件,而是編輯自己建立的源碼文件。 所以一個(gè)很大的優(yōu)化點(diǎn)就是忽略掉 node_modules 下的文件,不監(jiān)聽它們。相關(guān)配置如下:

module.export = {
  watchOptions: {
    // 不監(jiān)聽的 node_modules 目錄下的文件
    ignored: /node_modules/,
  }
}

采用這種方法優(yōu)化后,你的 Webpack 消耗的內(nèi)存和 CPU 將會(huì)大大降低。

有時(shí)你可能會(huì)覺得 node_modules 目錄下的第三方模塊有 bug,想修改第三方模塊的文件,然后在自己的項(xiàng)目中試試。 在這種情況下如果使用了以上優(yōu)化方法,我們需要重啟構(gòu)建以看到最新效果。 但這種情況畢竟是非常少見的。

除了忽略掉部分文件的優(yōu)化外,還有如下兩種方法:

watchOptions.aggregateTimeout 值越大性能越好,因?yàn)檫@能降低重新構(gòu)建的頻率。

watchOptions.poll 值越小越好,因?yàn)檫@能降低檢查的頻率。

但兩種優(yōu)化方法的后果是會(huì)讓你感覺到監(jiān)聽模式的反應(yīng)和靈敏度降低了。

自動(dòng)刷新瀏覽器

監(jiān)聽到文件更新后的下一步是去刷新瀏覽器,webpack 模塊負(fù)責(zé)監(jiān)聽文件,webpack-dev-server 模塊則負(fù)責(zé)刷新瀏覽器。 在使用 webpack-dev-server 模塊去啟動(dòng) webpack 模塊時(shí),webpack 模塊的監(jiān)聽模式默認(rèn)會(huì)被開啟。 webpack 模塊會(huì)在文件發(fā)生變化時(shí)告訴 webpack-dev-server 模塊。

自動(dòng)刷新的原理

控制瀏覽器刷新有三種方法:

借助瀏覽器擴(kuò)展去通過瀏覽器提供的接口刷新,WebStorm IDE 的 LiveEdit 功能就是這樣實(shí)現(xiàn)的。

往要開發(fā)的網(wǎng)頁中注入代理客戶端代碼,通過代理客戶端去刷新整個(gè)頁面。

把要開發(fā)的網(wǎng)頁裝進(jìn)一個(gè) iframe 中,通過刷新 iframe 去看到最新效果。

DevServer 支持第2、3種方法,第2種是 DevServer 默認(rèn)采用的刷新方法。

優(yōu)化自動(dòng)刷新的性能

在DevServer中曾介紹過 devServer.inline 配置項(xiàng),它就是用來控制是否往 Chunk 中注入代理客戶端的,默認(rèn)會(huì)注入。 事實(shí)上,在開啟 inline 時(shí),DevServer 會(huì)為每個(gè)輸出的 Chunk 中注入代理客戶端的代碼,當(dāng)你的項(xiàng)目需要輸出的 Chunk 有很多個(gè)時(shí),這會(huì)導(dǎo)致你的構(gòu)建緩慢。 其實(shí)要完成自動(dòng)刷新,一個(gè)頁面只需要一個(gè)代理客戶端就行了,DevServer 之所以粗暴的為每個(gè) Chunk 都注入,是因?yàn)樗恢滥硞€(gè)網(wǎng)頁依賴哪幾個(gè) Chunk,索性就全部都注入一個(gè)代理客戶端。 網(wǎng)頁只要依賴了其中任何一個(gè) Chunk,代理客戶端就被注入到網(wǎng)頁中去。

這里優(yōu)化的思路是關(guān)閉還不夠優(yōu)雅的 inline 模式,只注入一個(gè)代理客戶端。 為了關(guān)閉 inline 模式,在啟動(dòng) DevServer 時(shí),可通過執(zhí)行命令 webpack-dev-server --inline false(也可以在配置文件中設(shè)置)。

要開發(fā)的網(wǎng)頁被放進(jìn)了一個(gè) iframe 中,編輯源碼后,iframe 會(huì)被自動(dòng)刷新。 同時(shí)你會(huì)發(fā)現(xiàn)構(gòu)建時(shí)間從 1566ms 減少到了 1130ms,說明優(yōu)化生效了。構(gòu)建性能提升的效果在要輸出的 Chunk 數(shù)量越多時(shí)會(huì)顯得越突出。

在你關(guān)閉了 inline 后,DevServer 會(huì)自動(dòng)地提示你通過新網(wǎng)址 http://localhost:8080/webpack-dev-server/ 去訪問,這點(diǎn)是做的很人心化的。

如果你不想通過 iframe 的方式去訪問,但同時(shí)又想讓網(wǎng)頁保持自動(dòng)刷新功能,你需要手動(dòng)往網(wǎng)頁中注入代理客戶端腳本,往 index.html 中插入以下標(biāo)簽:



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

開啟模塊熱替換

要做到實(shí)時(shí)預(yù)覽,除了在使用自動(dòng)刷新中介紹的刷新整個(gè)網(wǎng)頁外,DevServer 還支持一種叫做模塊熱替換(Hot Module Replacement)的技術(shù)可在不刷新整個(gè)網(wǎng)頁的情況下做到超靈敏的實(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ùn)行狀態(tài),例如在使用 Redux 來管理數(shù)據(jù)的應(yīng)用中搭配模塊熱替換能做到代碼更新時(shí) Redux 中的數(shù)據(jù)還保持不變。

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

模塊熱替換的原理

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

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

除了通過在啟動(dòng)時(shí)帶上 --hot 參數(shù),還可以通過接入 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 要開啟模塊熱替換模式
    hot: true,      
  }  
};

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

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

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

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

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

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

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


其中的 module.hot 是當(dāng)開啟模塊熱替換后注入到全局的 API,用于控制模塊熱替換的邏輯。

現(xiàn)在修改 AppComponent.js 文件,把 Hello,Webpack 改成 Hello,World,你會(huì)發(fā)現(xiàn)模塊熱替換生效了。 但是當(dāng)你編輯 main.js 時(shí),你會(huì)發(fā)現(xiàn)整個(gè)網(wǎng)頁被刷新了。為什么修改這兩個(gè)文件會(huì)有不一樣的表現(xiàn)呢?

當(dāng)子模塊發(fā)生更新時(shí),更新事件會(huì)一層層往上傳遞,也就是從 AppComponent.js 文件傳遞到 main.js 文件, 直到有某層的文件接受了當(dāng)前變化的模塊,也就是 main.js 文件中定義的 module.hot.accept(["./AppComponent"], callback), 這時(shí)就會(huì)調(diào)用 callback 函數(shù)去執(zhí)行自定義邏輯。如果事件一直往上拋到最外層都沒有文件接受它,就會(huì)直接刷新網(wǎng)頁。

那為什么沒有地方接受過 .css 文件,但是修改所有的 .css 文件都會(huì)觸發(fā)模塊熱替換呢? 原因在于 style-loader 會(huì)注入用于接受 CSS 的代碼。

請(qǐng)不要把模塊熱替換技術(shù)用于線上環(huán)境,它是專門為提升開發(fā)效率生的。
優(yōu)化模塊熱替換

其中的 Updated modules: 68 是指 ID 為68的模塊被替換了,這對(duì)開發(fā)者來說很不友好,因?yàn)殚_發(fā)者不知道 ID 和模塊之間的對(duì)應(yīng)關(guān)系,最好是把替換了的模塊的名稱輸出出來。 Webpack 內(nèi)置的 NamedModulesPlugin 插件可以解決該問題,修改 Webpack 配置文件接入該插件:

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

module.exports = {
  plugins: [
    // 顯示出被替換模塊的名稱
    new NamedModulesPlugin(),
  ],
};

除此之外,模塊熱替換還面臨著和自動(dòng)刷新一樣的性能問題,因?yàn)樗鼈兌夹枰O(jiān)聽文件變化和注入客戶端。 要優(yōu)化模塊熱替換的構(gòu)建性能,思路和在使用自動(dòng)刷新中提到的很類似:監(jiān)聽更少的文件,忽略掉 node_modules 目錄下的文件。 但是其中提到的關(guān)閉默認(rèn)的 inline 模式手動(dòng)注入代理客戶端的優(yōu)化方法不能用于在使用模塊熱替換的情況下, 原因在于模塊熱替換的運(yùn)行依賴在每個(gè) Chunk 中都包含代理客戶端的代碼。

區(qū)分環(huán)境 為什么需要區(qū)分環(huán)境

在開發(fā)網(wǎng)頁的時(shí)候,一般都會(huì)有多套運(yùn)行環(huán)境,例如:

在開發(fā)過程中方便開發(fā)調(diào)試的環(huán)境。

發(fā)布到線上給用戶使用的運(yùn)行環(huán)境。

這兩套不同的環(huán)境雖然都是由同一套源代碼編譯而來,但是代碼內(nèi)容卻不一樣,差異包括:

線上代碼被通過壓縮代碼 中提到的方法壓縮過。

開發(fā)用的代碼包含一些用于提示開發(fā)者的提示日志,這些日志普通用戶不可能去看它。

開發(fā)用的代碼所連接的后端數(shù)據(jù)接口地址也可能和線上環(huán)境不同,因?yàn)橐苊忾_發(fā)過程中造成對(duì)線上數(shù)據(jù)的影響。

為了盡可能的復(fù)用代碼,在構(gòu)建的過程中需要根據(jù)目標(biāo)代碼要運(yùn)行的環(huán)境而輸出不同的代碼,我們需要一套機(jī)制在源碼中去區(qū)分環(huán)境。 幸運(yùn)的是 Webpack 已經(jīng)為我們實(shí)現(xiàn)了這點(diǎn)。

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

具體區(qū)分方法很簡(jiǎn)單,在源碼中通過如下方式:

if (process.env.NODE_ENV === "production") {
  console.log("你正在線上環(huán)境");
} else {
  console.log("你正在使用開發(fā)環(huán)境");
}

其大概原理是借助于環(huán)境變量的值去判斷執(zhí)行哪個(gè)分支。

當(dāng)你的代碼中出現(xiàn)了使用 process 模塊的語句時(shí),Webpack 就自動(dòng)打包進(jìn) process 模塊的代碼以支持非 Node.js 的運(yùn)行環(huán)境。 當(dāng)你的代碼中沒有使用 process 時(shí)就不會(huì)打包進(jìn) process 模塊的代碼。這個(gè)注入的 process 模塊作用是為了模擬 Node.js 中的 process,以支持上面使用的 process.env.NODE_ENV === "production" 語句。

在構(gòu)建線上環(huán)境代碼時(shí),需要給當(dāng)前運(yùn)行環(huán)境設(shè)置環(huán)境變量 NODE_ENV = "production",Webpack 相關(guān)配置如下:

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

module.exports = {
  plugins: [
    new DefinePlugin({
      // 定義 NODE_ENV 環(huán)境變量為 production
      "process.env": {
        NODE_ENV: JSON.stringify("production")
      }
    }),
  ],
};


注意在定義環(huán)境變量的值時(shí)用 JSON.stringify 包裹字符串的原因是環(huán)境變量的值需要是一個(gè)由雙引號(hào)包裹的字符串,而 JSON.stringify("production")的值正好等于""production""。

執(zhí)行構(gòu)建后,你會(huì)在輸出的文件中發(fā)現(xiàn)如下代碼:

if (true) {
  console.log("你正在使用線上環(huán)境");
} else {
  console.log("你正在使用開發(fā)環(huán)境");
}

定義的環(huán)境變量的值被代入到了源碼中,process.env.NODE_ENV === "production" 被直接替換成了 true。 并且由于此時(shí)訪問 process 的語句被替換了而沒有了,Webpack 也不會(huì)打包進(jìn) process 模塊了。

DefinePlugin 定義的環(huán)境變量只對(duì) Webpack 需要處理的代碼有效,而不會(huì)影響 Node.js 運(yùn)行時(shí)的環(huán)境變量的值。

通過 Shell 腳本的方式去定義的環(huán)境變量,例如 NODE_ENV=production webpack,Webpack 是不認(rèn)識(shí)的,對(duì) Webpack 需要處理的代碼中的環(huán)境區(qū)分語句是沒有作用的。

也就是說只需要通過 DefinePlugin 定義環(huán)境變量就能使上面介紹的環(huán)境區(qū)分語句正常工作,沒必要又通過 Shell 腳本的方式去定義一遍。

如果你想讓 Webpack 使用通過 Shell 腳本的方式去定義的環(huán)境變量,你可以使用 EnvironmentPlugin,代碼如下:

new webpack.EnvironmentPlugin(["NODE_ENV"])

以上這句代碼實(shí)際上等價(jià)于:

new webpack.DefinePlugin({
  "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
})
結(jié)合 UglifyJS

其實(shí)以上輸出的代碼還可以進(jìn)一步優(yōu)化,因?yàn)?if(true) 語句永遠(yuǎn)只會(huì)執(zhí)行前一個(gè)分支中的代碼,也就是說最佳的輸出其實(shí)應(yīng)該直接是:

console.log("你正在線上環(huán)境");

Webpack 沒有實(shí)現(xiàn)去除死代碼功能,但是 UglifyJS 可以做這個(gè)事情,如何使用請(qǐng)閱讀 壓縮代碼 中的壓縮 JavaScript。

第三方庫中的環(huán)境區(qū)分

除了在自己寫的源碼中可以有環(huán)境區(qū)分的代碼外,很多第三方庫也做了環(huán)境區(qū)分的優(yōu)化。 以 React 為例,它做了兩套環(huán)境區(qū)分,分別是:

開發(fā)環(huán)境:包含類型檢查、HTML 元素檢查等等針對(duì)開發(fā)者的警告日志代碼。

線上環(huán)境:去掉了所有針對(duì)開發(fā)者的代碼,只保留讓 React 能正常運(yùn)行的部分,以優(yōu)化大小和性能。

例如 React 源碼中有大量類似下面這樣的代碼:

if (process.env.NODE_ENV !== "production") {
  warning(false, "%s(...): Can only update a mounted or mounting component.... ")
}

如果你不定義 NODE_ENV=production 那么這些警告日志就會(huì)被包含到輸出的代碼中,輸出的文件將會(huì)非常大。

process.env.NODE_ENV !== "production" 中的 NODE_ENV"production" 兩個(gè)值是社區(qū)的約定,通常使用這條判斷語句在區(qū)分開發(fā)環(huán)境和線上環(huán)境。

壓縮代碼

瀏覽器從服務(wù)器訪問網(wǎng)頁時(shí)獲取的 JavaScript、CSS 資源都是文本形式的,文件越大網(wǎng)頁加載時(shí)間越長(zhǎng)。 為了提升網(wǎng)頁加速速度和減少網(wǎng)絡(luò)傳輸流量,可以對(duì)這些資源進(jìn)行壓縮。 壓縮的方法除了可以通過 GZIP 算法對(duì)文件壓縮外,還可以對(duì)文本本身進(jìn)行壓縮。

對(duì)文本本身進(jìn)行壓縮的作用除了有提升網(wǎng)頁加載速度的優(yōu)勢(shì)外,還具有混淆源碼的作用。 由于壓縮后的代碼可讀性非常差,就算別人下載到了網(wǎng)頁的代碼,也大大增加了代碼分析和改造的難度。

下面來一一介紹如何在 Webpack 中壓縮代碼。

壓縮 JavaScript

目前最成熟的 JavaScript 代碼壓縮工具是 UglifyJS , 它會(huì)分析 JavaScript 代碼語法樹,理解代碼含義,從而能做到諸如去掉無效代碼、去掉日志輸出代碼、縮短變量名等優(yōu)化。

要在 Webpack 中接入 UglifyJS 需要通過插件的形式,目前有兩個(gè)成熟的插件,分別是:

UglifyJsPlugin:通過封裝 UglifyJS 實(shí)現(xiàn)壓縮。

ParallelUglifyPlugin:多進(jìn)程并行處理壓縮,使用 ParallelUglifyPlugin 中有詳細(xì)介紹。

由于 ParallelUglifyPlugin 在 4-4使用ParallelUglifyPlugin 中介紹過就不再復(fù)述, 這里重點(diǎn)介紹如何配置 UglifyJS 以達(dá)到最優(yōu)的壓縮效果。

UglifyJS 提供了非常多的選擇用于配置在壓縮過程中采用哪些規(guī)則,所有的選項(xiàng)說明可以在 其官方文檔 上看到。 由于選項(xiàng)非常多,就挑出一些常用的拿出來詳細(xì)講解其應(yīng)用方式:

sourceMap:是否為壓縮后的代碼生成對(duì)應(yīng)的 Source Map,默認(rèn)為不生成,開啟后耗時(shí)會(huì)大大增加。一般不會(huì)把壓縮后的代碼的 Source Map 發(fā)送給網(wǎng)站用戶的瀏覽器,而是用于內(nèi)部開發(fā)人員調(diào)試線上代碼時(shí)使用。

beautify: 是否輸出可讀性較強(qiáng)的代碼,即會(huì)保留空格和制表符,默認(rèn)為是,為了達(dá)到更好的壓縮效果,可以設(shè)置為 false。

comments:是否保留代碼中的注釋,默認(rèn)為保留,為了達(dá)到更好的壓縮效果,可以設(shè)置為 false。

compress.warnings:是否在 UglifyJs 刪除沒有用到的代碼時(shí)輸出警告信息,默認(rèn)為輸出,可以設(shè)置為 false 以關(guān)閉這些作用不大的警告。

drop_console:是否剔除代碼中所有的 console 語句,默認(rèn)為不剔除。開啟后不僅可以提升代碼壓縮效果,也可以兼容不支持 console 語句 IE 瀏覽器。

collapse_vars:是否內(nèi)嵌定義了但是只用到一次的變量,例如把 var x = 5; y = x 轉(zhuǎn)換成 y = 5,默認(rèn)為不轉(zhuǎn)換。為了達(dá)到更好的壓縮效果,可以設(shè)置為 false。

reduce_vars: 是否提取出出現(xiàn)多次但是沒有定義成變量去引用的靜態(tài)值,例如把 x = "Hello"; y = "Hello" 轉(zhuǎn)換成 var a = "Hello"; x = a; y = b,默認(rèn)為不轉(zhuǎn)換。為了達(dá)到更好的壓縮效果,可以設(shè)置為 false。

也就是說,在不影響代碼正確執(zhí)行的前提下,最優(yōu)化的代碼壓縮配置為如下:

const UglifyJSPlugin = require("webpack/lib/optimize/UglifyJsPlugin");

module.exports = {
  plugins: [
    // 壓縮輸出的 JS 代碼
    new UglifyJSPlugin({
      compress: {
        // 在UglifyJs刪除沒有用到的代碼時(shí)不輸出警告
        warnings: false,
        // 刪除所有的 `console` 語句,可以兼容ie瀏覽器
        drop_console: true,
        // 內(nèi)嵌定義了但是只用到一次的變量
        collapse_vars: true,
        // 提取出出現(xiàn)多次但是沒有定義成變量去引用的靜態(tài)值
        reduce_vars: true,
      },
      output: {
        // 最緊湊的輸出
        beautify: false,
        // 刪除所有的注釋
        comments: false,
      }
    }),
  ],
};

從以上配置中可以看出 Webpack 內(nèi)置了 UglifyJsPlugin,需要指出的是 UglifyJsPlugin 當(dāng)前采用的是 UglifyJS2 而不是老的 UglifyJS1, 這兩個(gè)版本的 UglifyJS 在配置上有所區(qū)別,看文檔時(shí)注意版本。

除此之外 Webpack 還提供了一個(gè)更簡(jiǎn)便的方法來接入 UglifyJSPlugin,直接在啟動(dòng) Webpack 時(shí)帶上 --optimize-minimize 參數(shù),即 webpack --optimize-minimize, 這樣 Webpack 會(huì)自動(dòng)為你注入一個(gè)帶有默認(rèn)配置的 UglifyJSPlugin。

壓縮 ES6

雖然當(dāng)前大多數(shù) JavaScript 引擎還不完全支持 ES6 中的新特性,但在一些特定的運(yùn)行環(huán)境下已經(jīng)可以直接執(zhí)行 ES6 代碼了,例如最新版的 Chrome、ReactNative 的引擎 JavaScriptCore。

運(yùn)行 ES6 的代碼相比于轉(zhuǎn)換后的 ES5 代碼有如下優(yōu)點(diǎn):

一樣的邏輯用 ES6 實(shí)現(xiàn)的代碼量比 ES5 更少。

JavaScript 引擎對(duì) ES6 中的語法做了性能優(yōu)化,例如針對(duì) const 申明的變量有更快的讀取速度。

所以在運(yùn)行環(huán)境允許的情況下,我們要盡可能的使用原生的 ES6 代碼去運(yùn)行,而不是轉(zhuǎn)換后的 ES5 代碼。

在你用上面所講的壓縮方法去壓縮 ES6 代碼時(shí),你會(huì)發(fā)現(xiàn) UglifyJS 會(huì)報(bào)錯(cuò)退出,原因是 UglifyJS 只認(rèn)識(shí) ES5 語法的代碼。 為了壓縮 ES6 代碼,需要使用專門針對(duì) ES6 代碼的 UglifyES。

UglifyES 和 UglifyJS 來自同一個(gè)項(xiàng)目的不同分支,它們的配置項(xiàng)基本相同,只是接入 Webpack 時(shí)有所區(qū)別。 在給 Webpack 接入 UglifyES 時(shí),不能使用內(nèi)置的 UglifyJsPlugin,而是需要多帶帶安裝和使用最新版本的 uglifyjs-webpack-plugin。 安裝方法如下:

npm i -D uglifyjs-webpack-plugin@beta

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

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


同時(shí),為了不讓 babel-loader 輸出 ES5 語法的代碼,需要去掉 .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)置了,要開啟 cssnano 去壓縮代碼只需要開啟 css-loaderminimize 選項(xiàng)。 相關(guān) Webpack 配置如下:

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


CDN 加速

雖然前面通過了壓縮代碼的手段來減小網(wǎng)絡(luò)傳輸大小,但實(shí)際上最影響用戶體驗(yàn)的還是網(wǎng)頁首次打開時(shí)的加載等待。 導(dǎo)致這個(gè)問題的根本是網(wǎng)絡(luò)傳輸過程耗時(shí)大,CDN 的作用就是加速網(wǎng)絡(luò)傳輸。

CDN 又叫內(nèi)容分發(fā)網(wǎng)絡(luò),通過把資源部署到世界各地,用戶在訪問時(shí)按照就近原則從離用戶最近的服務(wù)器獲取資源,從而加速資源的獲取速度。 CDN 其實(shí)是通過優(yōu)化物理鏈路層傳輸過程中的光速有限、丟包等問題來提升網(wǎng)速的,其大致原理可以如下:

在本節(jié)中你不必理解 CDN 的具體運(yùn)行流程和實(shí)現(xiàn)原理,你可以簡(jiǎn)單的把 CDN 服務(wù)看作成速度更快的 HTTP 服務(wù)。 并且目前很多大公司都會(huì)建立自己的 CDN 服務(wù),就算你自己沒有資源去搭建一套 CDN 服務(wù),各大云服務(wù)提供商都提供了按量收費(fèi)的 CDN 服務(wù)。

接入 CDN

要給網(wǎng)站接入 CDN,需要把網(wǎng)頁的靜態(tài)資源上傳到 CDN 服務(wù)上去,在服務(wù)這些靜態(tài)資源的時(shí)候需要通過 CDN 服務(wù)提供的 URL 地址去訪問。

舉個(gè)詳細(xì)的例子,有一個(gè)單頁應(yīng)用,構(gòu)建出的代碼結(jié)構(gòu)如下:

dist
|-- app_9d89c964.js
|-- app_a6976b6d.css
|-- arch_ae805d49.png
`-- index.html

其中 index.html 內(nèi)容如下:



  
  


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

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

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

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

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

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

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



  
  


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

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

如果你對(duì)形如 //cdn.com/id/app_a6976b6d.css 這樣的 URL 感到陌生,你需要知道這種 URL 省掉了前面的 http: 或者 https: 前綴, 這樣做的好處時(shí)在訪問這些資源的時(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è)很大的問題。由于所有靜態(tài)資源都放到了同一個(gè) CDN 服務(wù)的域名下,也就是上面的 cdn.com。 如果網(wǎng)頁的資源很多,例如有很多圖片,就會(huì)導(dǎo)致資源的加載被阻塞,因?yàn)橥瑫r(shí)只能加載幾個(gè),必須等其它資源加載完才能繼續(xù)加載。 要解決這個(gè)問題,可以把這些靜態(tài)資源分散到不同的 CDN 服務(wù)上去, 例如把 JavaScript 文件放到 js.cdn.com 域名下、把 CSS 文件放到 css.cdn.com 域名下、圖片文件放到 img.cdn.com 域名下, 這樣做之后 index.html 需要變成這樣:



  
  


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

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

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

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

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

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

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


以上代碼中最核心的部分是通過 publicPath 參數(shù)設(shè)置存放靜態(tài)資源的 CDN 目錄 URL, 為了讓不同類型的資源輸出到不同的 CDN,需要分別在:

output.publicPath 中設(shè)置 JavaScript 的地址。

css-loader.publicPath 中設(shè)置被 CSS 導(dǎo)入的資源的的地址。

WebPlugin.stylePublicPath 中設(shè)置 CSS 文件的地址。

設(shè)置好 publicPath 后,WebPlugin 在生成 HTML 文件和 css-loader 轉(zhuǎn)換 CSS 代碼時(shí),會(huì)考慮到配置中的 publicPath,用對(duì)應(yīng)的線上地址替換原來的相對(duì)地址。

使用 Tree Shaking

Tree Shaking 可以用來剔除 JavaScript 中用不上的死代碼。它依賴靜態(tài)的 ES6 模塊化語法,例如通過 importexport 導(dǎo)入導(dǎo)出。 Tree Shaking 最先在 Rollup 中出現(xiàn),Webpack 在 2.0 版本中將其引入。

為了更直觀的理解它,來看一個(gè)具體的例子。假如有一個(gè)文件 util.js 里存放了很多工具函數(shù)和常量,在 main.js 中會(huì)導(dǎo)入和使用 util.js,代碼如下:

util.js 源碼:

export function funcA() {
}

export function funB() {
}

main.js 源碼:

import {funcA} from "./util.js";
funcA();

Tree Shaking 后的 util.js

export function funcA() {
}

由于只用到了 util.js 中的 funcA,所以剩下的都被 Tree Shaking 當(dāng)作死代碼給剔除了。

需要注意的是要讓 Tree Shaking 正常工作的前提是交給 Webpack 的 JavaScript 代碼必須是采用 ES6 模塊化語法的, 因?yàn)?ES6 模塊化語法是靜態(tài)的(導(dǎo)入導(dǎo)出語句中的路徑必須是靜態(tài)的字符串,而且不能放入其它代碼塊中),這讓 Webpack 可以簡(jiǎn)單的分析出哪些 export 的被 import 過了。 如果你采用 ES5 中的模塊化,例如 module.export={...}require(x+y)、if(x){require("./util")},Webpack 無法分析出哪些代碼可以剔除。

接入 Tree Shaking

上面講了 Tree Shaking 是做什么的,接下來一步步教你如何配置 Webpack 讓 Tree Shaking 生效。

首先,為了把采用 ES6 模塊化的代碼交給 Webpack,需要配置 Babel 讓其保留 ES6 模塊化語句,修改 .babelrc 文件為如下:

{
  "presets": [
    [
      "env",
      {
        "modules": false
      }
    ]
  ]
}

其中 "modules": false 的含義是關(guān)閉 Babel 的模塊轉(zhuǎn)換功能,保留原本的 ES6 模塊化語法。

配置好 Babel 后,重新運(yùn)行 Webpack,在啟動(dòng) Webpack 時(shí)帶上 --display-used-exports 參數(shù),以方便追蹤 Tree Shaking 的工作, 這時(shí)你會(huì)發(fā)現(xiàn)在控制臺(tái)中輸出了如下的日志:

> webpack --display-used-exports
bundle.js  3.5 kB       0  [emitted]  main
   [0] ./main.js 41 bytes {0} [built]
   [1] ./util.js 511 bytes {0} [built]
       [only some exports used: funcA]

其中 [only some exports used: funcA] 提示了 util.js 只導(dǎo)出了用到的 funcA,說明 Webpack 確實(shí)正確的分析出了如何剔除死代碼。

但當(dāng)你打開 Webpack 輸出的 bundle.js 文件看下時(shí),你會(huì)發(fā)現(xiàn)用不上的代碼還在里面,如下:

/* harmony export (immutable) */
__webpack_exports__["a"] = funcA;

/* unused harmony export funB */

function funcA() {
  console.log("funcA");
}

function funB() {
  console.log("funcB");
}

Webpack 只是指出了哪些函數(shù)用上了哪些沒用上,要剔除用不上的代碼還得經(jīng)過 UglifyJS 去處理一遍。 要接入 UglifyJS 也很簡(jiǎn)單,不僅可以通過4-8壓縮代碼中介紹的加入 UglifyJSPlugin 去實(shí)現(xiàn), 也可以簡(jiǎn)單的通過在啟動(dòng) Webpack 時(shí)帶上 --optimize-minimize 參數(shù),為了快速驗(yàn)證 Tree Shaking 我們采用較簡(jiǎn)單的后者來實(shí)驗(yàn)下。

通過 webpack --display-used-exports --optimize-minimize 重啟 Webpack 后,打開新輸出的 bundle.js,內(nèi)容如下:

function r() {
  console.log("funcA")
}

t.a = r

可以看出 Tree Shaking 確實(shí)做到了,用不上的代碼都被剔除了。

當(dāng)你的項(xiàng)目使用了大量第三方庫時(shí),你會(huì)發(fā)現(xiàn) Tree Shaking 似乎不生效了,原因是大部分 Npm 中的代碼都是采用的 CommonJS 語法, 這導(dǎo)致 Tree Shaking 無法正常工作而降級(jí)處理。 但幸運(yùn)的時(shí)有些庫考慮到了這點(diǎn),這些庫在發(fā)布到 Npm 上時(shí)會(huì)同時(shí)提供兩份代碼,一份采用 CommonJS 模塊化語法,一份采用 ES6 模塊化語法。 并且在 package.json 文件中分別指出這兩份代碼的入口。

redux 庫為例,其發(fā)布到 Npm 上的目錄結(jié)構(gòu)為:

node_modules/redux
|-- es
|   |-- index.js # 采用 ES6 模塊化語法
|-- lib
|   |-- index.js # 采用 ES5 模塊化語法
|-- package.json

package.json 文件中有兩個(gè)字段:

{
  "main": "lib/index.js", // 指明采用 CommonJS 模塊化的代碼入口
  "jsnext:main": "es/inde           
               
                                           
                       
                 

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/95302.html

相關(guān)文章

  • 淺談webpack4.0 性能優(yōu)化

    摘要:中在性能優(yōu)化所做的努力,也大抵圍繞著這兩個(gè)大方向展開。因此,將依賴模塊從業(yè)務(wù)代碼中分離是性能優(yōu)化重要的一環(huán)。大型庫是否可以通過定制功能的方式減少體積。這又違背了性能優(yōu)化的基礎(chǔ)。接下來可以抓住一些細(xì)節(jié)做更細(xì)的優(yōu)化。中,為默認(rèn)啟動(dòng)這一優(yōu)化。 前言:在現(xiàn)實(shí)項(xiàng)目中,我們可能很少需要從頭開始去配置一個(gè)webpack 項(xiàng)目,特別是webpack4.0發(fā)布以后,零配置啟動(dòng)一個(gè)項(xiàng)目成為一種標(biāo)配。正因?yàn)?..

    leanxi 評(píng)論0 收藏0
  • webpack打包分析與性能優(yōu)化

    摘要:打包分析與性能優(yōu)化背景在去年年末參與的一個(gè)項(xiàng)目中,項(xiàng)目技術(shù)棧使用,生產(chǎn)環(huán)境全量構(gòu)建將近三分鐘,項(xiàng)目業(yè)務(wù)模塊多達(dá)數(shù)百個(gè),項(xiàng)目依賴數(shù)千個(gè),并且該項(xiàng)目協(xié)同前后端開發(fā)人員較多,提高構(gòu)建效率,成為了改善團(tuán)隊(duì)開發(fā)效率的關(guān)鍵之一。 webpack打包分析與性能優(yōu)化 背景 在去年年末參與的一個(gè)項(xiàng)目中,項(xiàng)目技術(shù)棧使用react+es6+ant-design+webpack+babel,生產(chǎn)環(huán)境全量構(gòu)建將...

    joy968 評(píng)論0 收藏0
  • webpack 使用優(yōu)化(一)

    摘要:原因就是默認(rèn)會(huì)把最重要的東西放到公共里,這里面包含啟動(dòng)應(yīng)用程序的依賴項(xiàng)模塊與模塊的依賴關(guān)系以及文件的版本號(hào)等信息。 之前寫了一篇關(guān)于webpack 如何使用的文章:webpack 單頁面應(yīng)用實(shí)戰(zhàn),并且寫了一個(gè) 單頁面應(yīng)用的小項(xiàng)目 放到了github上。正巧公司前段時(shí)間用webpack 做了一個(gè)項(xiàng)目,項(xiàng)目不大,是基于單頁面應(yīng)用的。但是上線后才發(fā)現(xiàn)了一些問題,原來還是有一些要優(yōu)化改進(jìn)的地方...

    Caicloud 評(píng)論0 收藏0
  • webpack 基礎(chǔ)與項(xiàng)目優(yōu)化實(shí)踐總結(jié)

    摘要:前言本文基于,主要涉及基本概念基本配置和實(shí)際項(xiàng)目打包優(yōu)化。關(guān)于概念方面參考官網(wǎng),常用配置來自于網(wǎng)絡(luò)資源,在文末有相關(guān)參考鏈接,實(shí)踐部分基于自己的項(xiàng)目進(jìn)行優(yōu)化配置。同一文件中,修改某個(gè)影響其他。 前言:本文基于weboack4.x,主要涉及webpack4 基本概念、基本配置和實(shí)際項(xiàng)目打包優(yōu)化。關(guān)于概念方面參考官網(wǎng),常用配置來自于網(wǎng)絡(luò)資源,在文末有相關(guān)參考鏈接,實(shí)踐部分基于自己的項(xiàng)目進(jìn)行...

    Scorpion 評(píng)論0 收藏0
  • 【Vue項(xiàng)目總結(jié)】webpack常規(guī)打包優(yōu)化方案

    摘要:由于新建項(xiàng)目發(fā)版打包時(shí)間大概需要分鐘,發(fā)版時(shí)嚴(yán)重拖慢下班時(shí)間,所以特意查看了相關(guān)文檔來優(yōu)化打包速度,爭(zhēng)取早點(diǎn)下班,。分析打包文件要優(yōu)化,先分析。 由于新建項(xiàng)目發(fā)版打包時(shí)間大概需要30分鐘,發(fā)版時(shí)嚴(yán)重拖慢下班時(shí)間,所以特意查看了相關(guān)文檔來優(yōu)化打包速度,爭(zhēng)取早點(diǎn)下班,^_^。 分析打包文件 要優(yōu)化,先分析。我們先要知道到底是哪里拖慢我們的打包速度呢? 打包后生成文件分析 可以利用webpa...

    andong777 評(píng)論0 收藏0
  • webpack優(yōu)化

    摘要:優(yōu)化在我們構(gòu)建單頁面應(yīng)用或封裝插件時(shí)很大機(jī)會(huì)用到這個(gè)的打包工具但項(xiàng)目代碼日益增多時(shí)一些簡(jiǎn)單的配置會(huì)暴露種種弊端打包時(shí)間長(zhǎng)代碼大以下是結(jié)合自身開發(fā)和學(xué)習(xí)過程解決問題的總結(jié)打包時(shí)間長(zhǎng)很多人都知道這個(gè)插件是用來提取公共庫的但這個(gè)插件也解決不了公共 webpack優(yōu)化 在我們構(gòu)建單頁面應(yīng)用(vue, React)或封裝插件時(shí),很大機(jī)會(huì)用到webpack,這個(gè)JavaScript的打包工具.但項(xiàng)...

    hatlonely 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<