摘要:前言最近重新看了一遍提取公共文件的配置。這篇文章將以解決實(shí)際開發(fā)遇到的問題為核心,悉數(shù)利用提取獨(dú)立文件模塊的應(yīng)用。利用插件是專門用來提取獨(dú)立文件的,它主要是提取多個(gè)入口的公共模塊。插入標(biāo)簽的這一步可以在打包好獨(dú)立文件之前,就在模板中插入。
前言
最近重新看了一遍 webpack 提取公共文件的配置。原來覺得這東西是個(gè)玄學(xué),都是 “憑感覺” 配置。這篇文章將以解決實(shí)際開發(fā)遇到的問題為核心,悉數(shù)利用 webpack 提取獨(dú)立文件(模塊)的應(yīng)用。
獨(dú)立文件在實(shí)際開發(fā)中一般有兩種:
第三方模塊 如 Vue React jQuery 等
項(xiàng)目開發(fā)編寫的獨(dú)立模塊(模塊),對于 MPA 多頁面開發(fā)來說是封裝出的一些方法庫比如 utils.getQueryString() 或者是每個(gè)頁面的共同操作;對于SPA 應(yīng)用來說沒有特別的需要分離出模塊,但是針對首屏渲染速度的提升,可以將 某些獨(dú)立模塊分離出來實(shí)現(xiàn)按需加載。
分離出獨(dú)立文件的目的:
獨(dú)立文件一般很少更改或者不會(huì)更改,webpack 沒必要每次打包進(jìn)一個(gè)文件中,獨(dú)立文件提取出可以長期緩存。
提升 webpack 打包速度
提取第三方模塊配置externals
Webpack 可以配置 externals 來將依賴的庫指向全局變量,從而不再打包這個(gè)庫。
// webpack.config.js 中 module.exports = { entry: { app: __direname +"/app/index.js" } externals: { jquery: "window.jQuery" } ... } // 模板 html 中 ... ... // 入口文件 index.js import $ from "jquery"
其實(shí)就是 script 標(biāo)簽引入的jquery 掛載在window下 其他類型 externals 的配置可以去官網(wǎng)查看,這種方法不算是打包提取第三方模塊,只是一個(gè)變量引入,不是本文討論的重點(diǎn)。
利用CommonsChunkPlugin
CommonsChunkPlugin 插件是專門用來提取獨(dú)立文件的,它主要是提取多個(gè)入口 chunk 的公共模塊。他的配置介紹如下:
配置屬性 | 配置介紹 |
---|---|
name 或者 names | chunk 的名稱 如果是names數(shù)組 相當(dāng)于對每個(gè)name進(jìn)行插件實(shí)例化 |
filename | 這個(gè)common chunk 的文件輸出名 |
minChunks | 通常情況為一個(gè)整數(shù),至少有minChunks個(gè)chunk使用了該模塊,該模塊才會(huì)被移入[common chunk]里 minChunks 還可以是Infinity意思為沒有任何模塊被移入,只是創(chuàng)建當(dāng)前這個(gè) chunk,這通常用來生成 jquery 等第三方代碼庫。minChunks還可以是一個(gè)返回布爾值的函數(shù),返回 true 該模塊會(huì)被移入 common chunk,否則不會(huì)。默認(rèn)值是 chunks 的長度。 |
chunks | 元素為chunk名稱的數(shù)組,插件將從該數(shù)組中提取common chunk 可見 minChunks 應(yīng)該小予chunks的長度,且大于1。如果沒有 所有的入口chunks 會(huì)被選中 |
children | 默認(rèn)為false 如果為true 相當(dāng)于為上一項(xiàng)chunks配置為chunk的子chunk 用于代碼分割code split |
async | 默認(rèn)為false 如果為true 生成的common chunk 為異步加載,這個(gè)異步的 common chunk 是 name 這個(gè) chunk 的子 chunk,而且跟 chunks 一起并行加載 |
minSize | 如果有指定大小,那么 common chunk 的文件大小至少有 minSize 才會(huì)被創(chuàng)建。非必填項(xiàng)。 |
創(chuàng)建一個(gè)如下圖的目錄
package.json 如下
{ "name": "webpacktest", "version": "1.0.0", "description": "", "directories": { "doc": "doc" }, "scripts": { "start": "webpack" }, "author": "abzerolee", "license": "ISC", "devDependencies": { "html-webpack-plugin": "^2.30.1", "webpack": "^3.8.1" }, "dependencies": { "underscore": "^1.8.3", } }
a.js 引入了 underscore 需要進(jìn)行了數(shù)組去重操作,現(xiàn)在需要將underscore分離為獨(dú)立文件。
// webpack.config.js entry: { a: __dirname +"/app/a.js", vendor: ["underscore"] }, output: { path: __dirname +"/dist", filename: "[name].[chunkhash:6].js", chunkFilename: "[name].[id].[chunkhash:6].js" }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: "vendor", }), new HtmlWebpackPlugin({ template: __dirname +"/app/index.html" }) ] // a.js let _ = require("underscore"); let arr = _.filter([1,2,3,2,3,3,5,5], (v, i, self) => self.indexOf(v) === i); console.log("unique:" +arr);
這樣underscore就分離進(jìn)了 vendor 塊,注意的是需要在入口定義 要輸出的 [ 獨(dú)立文件名 ]: [ 需要分離的模塊數(shù)組 ], 然后在CommonsChunkPlugin中配置 name : [獨(dú)立文件名]。
當(dāng)然也可以不用在入口定義,如vue-cli 就是在 在CommonsChunk中配置了minChunks。我們的第三方模塊都是通過npm 安裝在node_modules 目錄下,我們可以通過minChunks 判斷模塊路徑是否含有node_module 來返回true 或 false,前文有介紹minChunks的含義。配置如下:
entry: { a: __dirname +"/app/a.js", // **注意** 入口沒定義vendor }, output: { path: __dirname +"/dist", filename: "[name].[chunkhash:6].js", chunkFilename: "[name].[id].[chunkhash:6].js" }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: function(module) { let flag = module.context && module.context.indexOf("node_modules") !== -1; console.log(module.context, flag); return flag; } }), new HtmlWebpackPlugin({ template: __dirname +"/app/index.html" }) ]
上述兩種方式,對于多頁面還是單頁面都是可應(yīng)用的。但是現(xiàn)在的問題是每次入口文件 a.js 修改之后都會(huì)造成 vendor重新打包。那么如何解決這個(gè)問題呢。
manifest 處理第三方模塊應(yīng)用我們將 a.js 做一個(gè)簡單修改:
// 原來 - console.log("unique:" +arr); // 修改后 + console.log(arr);
重新打包發(fā)現(xiàn)vendor的hash變化了相當(dāng)于重新打包了underscore,解決的方法是利用一個(gè) manifest 來記錄 vendor 的 id ,如果vendor沒改變,則不需要重新打包。這就有兩種解決方式 :
1. 利用manifest.js利用CommonsChunkPlugin的chunks特性,提取出 webpack定義的異步加載代碼,配置如下:
entry: { a: __dirname +"/app/a.js", }, output: { path: __dirname +"/dist", filename: "[name].[chunkhash:6].js", chunkFilename: "[name].[id].[chunkhash:6].js" }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: function(module) { let flag = module.context && module.context.indexOf("node_modules") !== -1; console.log(module.context, flag); return flag; } }), new webpack.optimize.CommonsChunkPlugin({ name: "manifest", chunks: ["vendor"], }), new HtmlWebpackPlugin({ template: __dirname +"/app/index.html" }) ]
還是修改了 a.js 之后發(fā)現(xiàn) vendor的 hash 值沒有變化,如下圖:
這里要注意的是chunks: [ 獨(dú)立文件名 ]。但是,又有但是,要是這么就配置沒問題了,就不能叫做玄學(xué)了,修改 a.js 的內(nèi)部代碼沒問題,如果修改了 require 的模塊引入,vendor的hash又有變化了,當(dāng)然我們可以盡量避免修改文件的依賴引入,但是終歸不是最完美的方式。那么終極解決方法是什么呢?DllReferencePlugin,DllPlugin。
2. 利用DllReferencePlugin,DllPlugin既然動(dòng)態(tài)打包的時(shí)候建立 manifest 不行,那么能不能直接把他打包成一個(gè)純凈的依賴庫,本身無法運(yùn)行,只是讓我們的app 來引入。
那么我們需要完成兩步,先webpack.DllPlugin打包dll(純凈的第三方獨(dú)立文件),然后用DllReferencePlugin 在我們的應(yīng)用中引用,這樣的好處是如果下一個(gè)項(xiàng)目還是使用一樣的依賴比如react react-dom react-router,可以直接引入這個(gè)dll。
配置文件如下:
entry: { vendor: ["underscore"] }, output: { path: __dirname +"/dist", filename: "[name].js", library: "[name]", }, plugins: [ new webpack.DllPlugin({ path: __dirname +"/dist/manifest.json", name: "[name]", context: __dirname, }), ],
根據(jù)上述配置打包結(jié)果如上圖,dist目錄下現(xiàn)在有一個(gè)vender.js 和 manifest.json 注意這里輸出的路徑配置。DllPlugin配置介紹如下:
配置項(xiàng) | 介紹 |
---|---|
path | path 是 manifest.json 文件的輸出路徑,這個(gè)文件會(huì)用于后續(xù)的業(yè)務(wù)代碼打包; |
name | name 是 dll 暴露的對象名,要跟 output.library 保持一致; |
context | context 是解析包路徑的上下文,這個(gè)要跟接下來配置的 webpack.config.js 一致。 |
之后在我們的應(yīng)用中引入中,配置如下:
entry: { a: __dirname +"/app/a.js", }, output: { path: __dirname +"/dist", filename: "[name].[chunkhash:6].js", chunkFilename: "[name].[id].[chunkhash:6].js" }, plugins: [ new webpack.DllReferencePlugin({ context: __dirname, manifest: require("./dist/manifest.json"), }), new HtmlWebpackPlugin({ template: __dirname +"/app/index.html" }) ]
根據(jù)上述配置打包得到a.3e6285.js index.html 如上圖,瀏覽器中打開index.html會(huì)顯示
Uncaught ReferenceError: vendor is not defined
這里需要在 index.html 中 a.3e6285.js 插入 script 標(biāo)簽
再打開index.html 可以控制臺(tái)打印出了數(shù)組去重的結(jié)果。插入標(biāo)簽的這一步可以在打包好獨(dú)立文件之前,就在模板html 中插入。
到了這里,提取第三方模塊的方法,避免重復(fù)打包的方法都介紹完畢了。接下來是配置提取自己編寫的公共模塊方法。
提取項(xiàng)目公共模塊單頁面應(yīng)用的公共模塊沒有必要提取出多帶帶的文件,因?yàn)椴槐乜紤]復(fù)用的情況。但是對于打包生成的文件過大,我們又想分離出幾個(gè)模塊有需要的時(shí)候才加載,其實(shí)這并不是提取公共模塊,而是代碼分割,通過:
require.ensure(dependencies: String[], callback: function(require), chunkName: String)
在callback中定義的 require的模塊將會(huì)獨(dú)立打包,并且插入在 html 的head標(biāo)簽,這里就不做更多介紹了。
多頁面應(yīng)用是有必要抽取公共模塊的,比如a.js 引用了lib1, b.js 也引用了 lib1 那么lib1,那么我們肯定希望在提取出 lib1 同時(shí)還可以提取出第三方庫,配置文件如下:
// a.js let _ = require("underscore"); let lib1 = require("./lib1"); console.log("this is entry_a import lib1"); let arr = _.filter([1,2,3,2,3,3,5,5], (v, i, self) => self.indexOf(v) === i); console.log(arr); // b.js require("./lib1"); var b = "b"; console.log("this is entry_b import lib1"); // webpack.config.js entry: { a: __dirname +"/app/a.js", b: __dirname +"/app/b.js", vendor: ["underscore"], }, output: { path: __dirname +"/dist", filename: "[name].[chunkhash:6].js", chunkFilename: "[name].[id].[chunkhash:6].js" }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: ["chunk", "vendor"], minChunks: 2, }), new webpack.optimize.CommonsChunkPlugin({ name: "manifest", chunks: ["vendor"] }), new HtmlWebpackPlugin({ template: __dirname +"/app/index.html", filename: __dirname +"/dist/a.html", chunks: ["a", "chunk", "vendor", "manifest"], }), new HtmlWebpackPlugin({ template: __dirname +"/app/index.html", filename: __dirname +"/dist/b.html", chunks: ["b", "chunk", "vendor", "manifest"], }), ] }
通過打包后發(fā)現(xiàn)生成了如下文件:
可以明確看出生成了chunk.d09623.js 而且 其中就是我們的lib1.js 的庫的代碼。這里要注意的是Commons.ChunkPlugin的配置 當(dāng)name 給定數(shù)組之后從入口文件中選取 共同引用超過 minChunks 次數(shù)的模塊打包進(jìn)name 數(shù)組的第一個(gè)模塊,然后name 數(shù)組后面的塊 "vendor" 依次打包(查找entry里的key,沒有找到相關(guān)的key就生成一個(gè)空的塊),最后一個(gè)塊包含webpack生成的在瀏覽器上使用各個(gè)塊的加載代碼,所以插入到頁面中最后一個(gè)塊要最先加載,加載順序由name數(shù)組自右向左。
這里我們使用manifest 去提取了 webpackJsonp 的加載代碼,為了防止重復(fù)打包庫文件,這在前文已經(jīng)提到過。所以vendor中的加載代碼在mainfest.js 中,修改a.js 的console.log, 重新打包后的文件可以發(fā)現(xiàn)chunk.d0962e.js, vendor.98054b.js都沒有重新打包
所以總結(jié)來講就是多入口配置CommonsChunk
new webpack.optimize.CommonsChunkPlugin({ name: ["生成的項(xiàng)目公共模塊文件名", "第三方模塊文件名"], minChunks: 2, }),
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/89601.html
摘要:去做想做的事,去愛值得的人去成為自己喜歡的模樣,去讓自己發(fā)光渾身充滿力量,充實(shí)的日子最美好各位早安,這里是平頭哥聯(lián)盟,我是首席填坑官蘇南,用心分享一起成長做有溫度的攻城獅。 showImg(https://segmentfault.com/img/bVbjIcs?w=1008&h=298); 前言 繼上一次webpack的基礎(chǔ)配置分享之后,本次將分享一些工作中項(xiàng)目常用的配置插件、也...
摘要:本文介紹了作者接手維護(hù)一個(gè)中型歷史項(xiàng)目時(shí)的一系列改進(jìn)實(shí)踐,包括模塊結(jié)構(gòu)拆分業(yè)務(wù)邏輯梳理打包優(yōu)化等。代碼中如菜單名稱結(jié)構(gòu)表單字段名等的各種硬編碼配置分散在各處。最后,在提升面向開發(fā)者的打包體驗(yàn)方面,本次優(yōu)化中主要實(shí)現(xiàn)的是與的解耦。 本文介紹了作者接手維護(hù)一個(gè)中型 React 歷史項(xiàng)目時(shí)的一系列改進(jìn)實(shí)踐,包括模塊結(jié)構(gòu)拆分、業(yè)務(wù)邏輯梳理、Webpack 打包優(yōu)化等。 背景 這是一個(gè) PC 的...
摘要:今天就嘗試著一起來聊聊吧,旨在幫大家加深理解新手更容易上路,都能從到搭建配置自定屬于自己的腳手架,或?qū)σ逊庋b好的腳手架有進(jìn)一步的鞏固,接下來蘇南會(huì)詳細(xì)講解中的每一個(gè)配置字段的作用部分為新增。 showImg(https://segmentfault.com/img/bVbjmMV?w=1008&h=298); 前言 經(jīng)常會(huì)有群友問起webpack、react、redux、甚至cre...
摘要:我的入門到放棄之路最近看到很多相關(guān)的問題跟討論,越來越多的小伙伴喜歡這個(gè)框架了,同時(shí)也在看到了有些入門的小伙伴遇到了各種各樣的問題,本人也是框架使用都一枚,公司是騰訊阿里平安三巨頭合資的一家公司,分別上海深圳杭州北京廣州等多個(gè)分部,前端人員 showImg(https://segmentfault.com/img/bVbhonB?w=1278&h=722); 我的react入門到放棄之...
閱讀 3830·2021-11-24 10:46
閱讀 1787·2021-11-15 11:38
閱讀 3851·2021-11-15 11:37
閱讀 3683·2021-10-27 14:19
閱讀 2038·2021-09-03 10:36
閱讀 2063·2021-08-16 11:02
閱讀 3065·2019-08-30 15:55
閱讀 2327·2019-08-30 15:44