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