html公共模块提取出去,webpack 填坑之路--提取独立文件(模块)
前言
最近重新看了一遍 webpack 提取公共文件的配置。原來覺得這東西是個玄學(xué),都是 “憑感覺” 配置。這篇文章將以解決實(shí)際開發(fā)遇到的問題為核心,悉數(shù)利用 webpack 提取獨(dú)立文件(模塊)的應(yīng)用。
獨(dú)立文件在實(shí)際開發(fā)中一般有兩種:
第三方模塊 如 Vue React jQuery 等
項(xiàng)目開發(fā)編寫的獨(dú)立模塊(模塊),對于 MPA 多頁面開發(fā)來說是封裝出的一些方法庫比如 utils.getQueryString() 或者是每個頁面的共同操作;對于SPA 應(yīng)用來說沒有特別的需要分離出模塊,但是針對首屏渲染速度的提升,可以將 某些獨(dú)立模塊分離出來實(shí)現(xiàn)按需加載。
分離出獨(dú)立文件的目的:
獨(dú)立文件一般很少更改或者不會更改,webpack 沒必要每次打包進(jìn)一個文件中,獨(dú)立文件提取出可以長期緩存。
提升 webpack 打包速度
提取第三方模塊
配置externals
Webpack 可以配置 externals 來將依賴的庫指向全局變量,從而不再打包這個庫。
// 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)查看,這種方法不算是打包提取第三方模塊,只是一個變量引入,不是本文討論的重點(diǎn)。
利用CommonsChunkPlugin
CommonsChunkPlugin 插件是專門用來提取獨(dú)立文件的,它主要是提取多個入口 chunk 的公共模塊。他的配置介紹如下:
配置屬性
配置介紹
name 或者 names
chunk 的名稱 如果是names數(shù)組 相當(dāng)于對每個name進(jìn)行插件實(shí)例化
filename
這個common chunk 的文件輸出名
minChunks
通常情況為一個整數(shù),至少有minChunks個chunk使用了該模塊,該模塊才會被移入[common chunk]里 minChunks 還可以是Infinity意思為沒有任何模塊被移入,只是創(chuàng)建當(dāng)前這個 chunk,這通常用來生成 jquery 等第三方代碼庫。minChunks還可以是一個返回布爾值的函數(shù),返回 true 該模塊會被移入 common chunk,否則不會。默認(rèn)值是 chunks 的長度。
chunks
元素為chunk名稱的數(shù)組,插件將從該數(shù)組中提取common chunk 可見 minChunks 應(yīng)該小予chunks的長度,且大于1。如果沒有 所有的入口chunks 會被選中
children
默認(rèn)為false 如果為true 相當(dāng)于為上一項(xiàng)chunks配置為chunk的子chunk 用于代碼分割code split
async
默認(rèn)為false 如果為true 生成的common chunk 為異步加載,這個異步的 common chunk 是 name 這個 chunk 的子 chunk,而且跟 chunks 一起并行加載
minSize
如果有指定大小,那么 common chunk 的文件大小至少有 minSize 才會被創(chuàng)建。非必填項(xiàng)。
創(chuàng)建一個如下圖的目錄
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 修改之后都會造成 vendor重新打包。那么如何解決這個問題呢。
manifest 處理第三方模塊應(yīng)用
我們將 a.js 做一個簡單修改:
// 原來
- console.log('unique:' +arr);
// 修改后
+ console.log(arr);
重新打包發(fā)現(xiàn)vendor的hash變化了相當(dāng)于重新打包了underscore,解決的方法是利用一個 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
既然動態(tài)打包的時候建立 manifest 不行,那么能不能直接把他打包成一個純凈的依賴庫,本身無法運(yùn)行,只是讓我們的app 來引入。
那么我們需要完成兩步,先webpack.DllPlugin打包dll(純凈的第三方獨(dú)立文件),然后用DllReferencePlugin 在我們的應(yīng)用中引用,這樣的好處是如果下一個項(xiàng)目還是使用一樣的依賴比如react react-dom react-router,可以直接引入這個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)在有一個vender.js 和 manifest.json 注意這里輸出的路徑配置。DllPlugin配置介紹如下:
配置項(xiàng)
介紹
path
path 是 manifest.json 文件的輸出路徑,這個文件會用于后續(xù)的業(yè)務(wù)代碼打包;
name
name 是 dll 暴露的對象名,要跟 output.library 保持一致;
context
context 是解析包路徑的上下文,這個要跟接下來配置的 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會顯示
Uncaught ReferenceError: vendor is not defined
這里需要在 index.html 中 a.3e6285.js 插入 script 標(biāo)簽
再打開index.html 可以控制臺打印出了數(shù)組去重的結(jié)果。插入標(biāo)簽的這一步可以在打包好獨(dú)立文件之前,就在模板html 中插入。
到了這里,提取第三方模塊的方法,避免重復(fù)打包的方法都介紹完畢了。接下來是配置提取自己編寫的公共模塊方法。
提取項(xiàng)目公共模塊
單頁面應(yīng)用的公共模塊沒有必要提取出單獨(dú)的文件,因?yàn)椴槐乜紤]復(fù)用的情況。但是對于打包生成的文件過大,我們又想分離出幾個模塊有需要的時候才加載,其實(shí)這并不是提取公共模塊,而是代碼分割,通過:
require.ensure(dependencies: String[], callback: function(require), chunkName: String)
在callback中定義的 require的模塊將會獨(dú)立打包,并且插入在 html 的head標(biāo)簽,這里就不做更多介紹了。
多頁面應(yīng)用是有必要抽取公共模塊的,比如a.js 引用了lib1, b.js 也引用了 lib1 那么lib1,那么我們肯定希望在提取出 lib1 同時還可以提取出第三方庫,配置文件如下:
// 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ù)組的第一個模塊,然后name 數(shù)組后面的塊 'vendor' 依次打包(查找entry里的key,沒有找到相關(guān)的key就生成一個空的塊),最后一個塊包含webpack生成的在瀏覽器上使用各個塊的加載代碼,所以插入到頁面中最后一個塊要最先加載,加載順序由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,
}),
總結(jié)
以上是生活随笔為你收集整理的html公共模块提取出去,webpack 填坑之路--提取独立文件(模块)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js怎么实现对html代码加密解密,ja
- 下一篇: 山东省2O2021年普通高考成绩查询,2