提高webapck打包速度-DllPlugin的使用和不足
什麼是dll
DLL(Dynamic Link Library)文件爲動態鏈接庫文件,在Windows中,許多應用程序並不是一個完整的可執行文件,它們被分割成一些相對獨立的動態鏈接庫,即DLL文件,放置於系統中。當我們執行某一個程序時,相應的DLL文件就會被調用。
在項目中第三方的代碼庫就是獨立的鏈接庫,我們每次打包的時候都會將這些庫進行和我們自己編寫的代碼一樣的編譯壓縮檢查等等工作,dll就是可以把這些第三方的庫放在一起單獨打包,不需要每次都打包
基本使用
webpack 使用dll的方式是由兩個插件完成的
DllPlugin
DllReferencePlugin
- 使用DllPlugin 打包第三方庫,以react爲例,創建dll.base.js,entry 入口文件就是需要打包的js,文件名vendor
const webpack = require('webpack');
const path = require('path');
const vendor = [
'react',
'react-dom',
'react-router',
'react-router-dom',
// 其他插件
'react-transition-group',
'prop-types',
'classnames',
'history'
];
module.exports = {
output: {
path: path.resolve(__dirname, './dll'), // js 存放的地方
filename: '[name].js', // name -> 'verdor'
library: '[name]',
},
entry: {
vendor: vendor
},
plugins: [
new webpack.DllPlugin({
path: 'manifest.json',
name: '[name]', // 和上面的library一樣就行
}),
],
};
在package.json的scripts中添加運行這個文件的命令:
"dll": "webpack --config dll.base.js"
會在項目目錄下產生dll文件夾和manifest.json文件,dll中有vendor.js,manifest.json有兩個屬性 name文件名,content: 含有的的module信息
2.在正常生產打包過程中,通過DllReferencePlugin將打包好的庫和剩下的需要編譯的代碼在module依賴上結合起來
webpack.config.js 中pluging 配置,注意只需在生產環境配置這插件
plugins: [
new webpack.DllReferencePlugin({
manifest: require("./manifest.json"), // dll 中生成的json 文件
}),
],
在沒有添加這個plugin的時候,build過程用時4767ms,打包200個module,budle.js 大小201kb
添加DllReferencePlugin後, 用時3195ms,打包175個module, bundle.js 大小41kb
打包完的html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>React and Webpack4</title>
<link href="app.css" rel="stylesheet"></head>
<body>
<section id="index"></section>
<script type="text/javascript" src="app.bundle.js"></script></body>
</html>
可以看到只有bundle.js 這個引用,沒有vendor.js,需要用到HtmlWebpackPlugin 插件裏面的inject功能
- 用到HtmlWebpackPlugin 插件裏面的inject功能,引用vendor.js
在webpack.config.js中
const Manifest = require('./manifest.json');
const htmlPlugin = new HtmlWebpackPlugin({
template: './src/index.html',
filename: './index.html',
vendorName: '../dll/'+Manifest.name + '.js',
inject: true
});
template.html
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.vendorName %>"></script>
vendorName就是引用的完整的路徑
不足之處
dll中的文件需要鎖定版本,不能使用對應的contenthash
打包的第三方js不能夠加上文件名上的hash,可以在每次發新版的時候生成hash拼接query的方式解決,不過這種方式需要另外增加代碼,並且多人合作時不一定能有效控制每個包的版本。兩種以上的dll包使用起來麻煩
如果項目中有很多的第三方代碼(mvc庫,UI庫及其相關的庫,圖表圖(一般都很大),相應的poly-fill(兼容性代碼))如果都打包到一起就會是很大的一個文件,這時會通過分開打包讓瀏覽器能夠並行下載這些文件的方式來加快加載速度。 下面dll代碼會生成相應的三個dll文件 r1,t1,t2. 文件名是contenthash
const entry = {
// react相關
r1: [
'react',
'react-dom',
...
],
// bizchart
t1: [
'bizcharts',
],
// 其他1
t2: [
'jquery',
'moment',
'babel-polyfill',
'proxy-polyfill'
],
// // 其他2
// t3: [
// ''
// ]
};
module.exports = {
output: {
path: rootPath,
filename: 'dll.[name]-[contenthash].js',
library: '[name]',
},
entry,
多個文件的dll在build的使用方式,先假設文件名是r1,t1,t2 三個普通的
- 在webpack.config 中的plugin屬性上要添加對應的DllReferencePlugin.
plugin.push(
new Webpack.DllReferencePlugin({
manifest: require(r1的manifest json 的path), // dll 中生成的json 文件
}),
new Webpack.DllReferencePlugin({
manifest: require(t1的manifest json 的path), // dll 中生成的json 文件
}),
new Webpack.DllReferencePlugin({
manifest: require(t2的manifest json 的path), // dll 中生成的json 文件
})
)
- 同時在htmlplugin的配置用需要配置相應的script替換項
plugin [
new HTMLWebpackPlugin({
template: './index.html',
r1: 'dll文件夾path/r1.js',
t1: 'dll文件夾path/t1.js',
t2: 'dll文件夾path/t2.js',
});
]
- 在index.html中添加
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.r1%>"></script>
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.t1 %>"></script>
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.t2 %>"></script>
改進這個使用方式
其實多文件dll的build過程就是,Webpack.DllReferencePlugin中把生成的每個json添加這個插件一次,在html添加上對應的文件名稱和路徑。
- 第一點可以通過遍歷dll包中的json文件寫入DllReferencePlugin,walk 方法是遍歷文件夾
var getDllConfigs = (dllPath) => {
const result = [];
var walk = function (dir) {
var results = []
var list = fs.readdirSync(dir)
list.forEach(function (file) {
file = dir + '/' + file
var stat = fs.statSync(file)
if (stat && stat.isDirectory()) results = results.concat(walk(file))
else results.push(file)
});
return results
};
const dllFiles = walk(dllPath);
dllFiles.forEach((filePath) => {
if (path.extname(filePath) === '.json') {
result.push({
manifest: require(filePath), // dll 中生成的json 文件
});
}
});
return result;
};
getDllConfigs(dll_path).forEach(item => {
WebpackBaseConfig.plugins.push(new Webpack.DllReferencePlugin(item))
});
- htmlPlugin 可以通過寫一個小插件來完成
//insert html
var fs = require('fs');
var path = require('path');
var walk = function (dir) {
var results = []
var list = fs.readdirSync(dir)
list.forEach(function (file) {
file = dir + '/' + file
var stat = fs.statSync(file)
if (stat && stat.isDirectory()) results = results.concat(walk(file))
else results.push(file)
});
return results
};
function insertHtmlDllPlugin(options) {
this.options = options;
}
insertHtmlDllPlugin.prototype.apply = function (compiler) {
var self = this;
// webpack 4 support
compiler.hooks.compilation.tap('insertHtmlDllPlugin', (compilation) => {
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tapAsync('insertHtmlDllPlugin', (htmlPluginData, callback) => {
self.onAfterHtmlProcessing(htmlPluginData, callback);
});
});
}
insertHtmlDllPlugin.prototype.onAfterHtmlProcessing = function (htmlPluginData, callback) {
var enable = typeof this.options.enable !== 'undefined' ? this.options.enable : true;
if (!enable) {
return;
}
var dllPath = this.options.dllPath;
if (!dllPath || !fs.existsSync(path.resolve(dllPath))) {
throw new Error('dll path don\'t exist');
}
// 遍歷dll文件夾找到對應的js,寫入html的header中
var dllFiles = walk(dllPath);
var scriptsStr = '';
var publicPath = this.options.publicPath || '';
dllFiles.forEach((filePath) => {
const fileName = path.basename(filePath);
if(path.extname(filePath) === '.js'){
scriptsStr += '<script type="text/javascript" src="' + (publicPath + fileName) + '"></script>';
}
});
//
console.log(this.options);
htmlPluginData.html = htmlPluginData.html.replace('</head>', scriptsStr + '</head>');
callback(null, htmlPluginData);
};
module.exports = insertHtmlDllPlugin;
將所有的js寫入head 標籤中
- 不能很好地支持異步的文件(也許是沒研究出來)
參考鏈接
2.demo