一、背景
- 项目采用vue-cli搭建,起初为单模板页面,后面为了将功能划分细致一些,采用了多模板的方式。
- 随着项目越来庞大,引入的第三方插件也越来越多。开发模式下热更新慢,编译速度慢,编译后的单文件也越来越大。
为了解决以上问题,参照网上已有解决方案,主要从以下几个方面入手进行项目结构的优化。
二、多模板页面的自动化
- 在未引入自动化之前多模板的目录结构如下:
src目录中卫主要的入口文件
修改相关配置文件 在build目录下:
webpack.base.conf.js
module.exports = {
entry: {
app: "./src/main.js",
login:'./src/login.js',
admin:'./src/admin.js',
},
}
复制代码
webpack.dev.conf.js
plugins: [
new HtmlWebpackPlugin({
filename: 'login.html',
template: 'login.html',
inject: true,
excludeChunks: ['app','admin']
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
excludeChunks: ['login','admin']
}),
new HtmlWebpackPlugin({
filename: 'admin.html',
template: 'admin.html',
inject: true,
excludeChunks: ['login','app']
}),
]
复制代码
webpack.prod.conf.js
plugins: [
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
excludeChunks: ['login','admin'],
chunksSortMode: 'dependency'
}),
new HtmlWebpackPlugin({
filename: config.build.login,
template: 'login.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
excludeChunks: ['app','admin'],
chunksSortMode: 'dependency'
}),
new HtmlWebpackPlugin({
filename: config.build.admin,
template: 'admin.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
excludeChunks: ['app','login'],
chunksSortMode: 'dependency'
}),
]
复制代码
相关字段的解释请查看 webpack 简介
配置完以上后能保证项目可以正常访问,能够正常使用,但是如果需要再增加模板文件后,就需要更改以上所有的地方,这样非常不方便项目的扩展。因此就优化了项目目录结构,将上述用到的地方都通过方法返回。
- 优化后的目录结构
在项目目录下新建template,各个模板文件如上图所示。
在build目录中的utils中增加函数entries
与htmlPlugin
// glob是webpack安装时依赖的一个第三方模块,还模块允许你使用 *等符号, 例如lib/*.js就是获取lib文件夹下的所有js后缀名的文件
var glob = require('glob');
//多入口配置
// 通过glob模块读取pages文件夹下的所有对应文件夹下的js后缀文件,如果该文件存在
// 那么就作为入口处理
exports.entries = function () {
var entryFiles = glob.sync(PAGE_PATH + '/*/*.js')
var map = {}
entryFiles.forEach((filePath) => {
var files=filePath.split('/');
var filename = files[files.length-2];
map[filename] = filePath;
})
return map
}
//多页面输出配置
// 与上面的多页面入口配置相同,读取pages文件夹下的对应的html后缀文件,然后放入数组中
exports.htmlPlugin = function () {
let entryHtml = glob.sync(PAGE_PATH + '/*/*.html')
let arr = []
var fileNames=[];
entryHtml.forEach((filePath)=>{
var files=filePath.split('/');
var filename = files[files.length-2];
let obj={
path:filePath,
name:filename
}
fileNames.push(obj);
})
fileNames.forEach((obj) => {
let conf = {
// 模板来源
template: obj.path,
// 文件名称
filename: obj.name +'.html',
// 页面模板需要加对应的js脚本,如果不加这行则每个页面都会引入所有的js脚本
inject: true,
excludeChunks: (function(){
let arr=[];
fileNames.forEach(item=>{
if(item.name!=obj.name){
arr.push(item.name);
}
})
return arr;
})()
}
if (process.env.NODE_ENV === 'production') {
conf = merge(conf, {
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: 'dependency'
})
}
arr.push(new HtmlWebpackPlugin(conf))
})
return arr
}
复制代码
webpack.base.conf.js
const utils = require('./utils');
module.exports = {
entry:utils.entries(),
}
复制代码
webpack.dev.conf.js
const utils = require('./utils');
plugins: [
new webpack.DefinePlugin({
'process.env': merge(require('../config/dev.env'),
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
new webpack.NoEmitOnErrorsPlugin(),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
].concat(utils.htmlPlugin())
复制代码
webpack.prod.conf.js
plugins:[].concat(utils.htmlPlugin())
复制代码
三、打包速度的优化
- 将webpack3升级为webpack4,传说速度能优化不少,但是没有试过。
- 引入
happypack
HappyPack 允许 Webpack 使用 Node多线程进行构建来提升构建的速度。参考请见webpack优化之HappyPack 实战 - 采用DllPlugin也能提升打包速度
- 将资源按需引入,
- 在html中引入压缩资源
四、优化编译后的单文件大小
在优化打包的单文件之前,可以先引入插件 webpack-bundle-analyzer
这个插件可以可视化分析打包文件的相关情况 使用方式
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [
new BundleAnalyzerPlugin(),
]
复制代码
运行截图,此截图为优化之后的结果
- 引入
DllReferencePlugin
- 在build目录下新建
vendor-manifest.json
与webpack.dll.config.js
文件,
webpack.dll.config.js
const path = require('path');
const webpack = require('webpack');
const glob = require('glob');
const fs = require('fs');
const {JSDOM} = require('jsdom');
// 取得相应的页面路径,因为之前的配置,所以是src文件夹下的pages文件夹
var PAGE_PATH = path.resolve(__dirname, '../template');
//动态的生成文件名,为了解决浏览器缓存问题,同文件名会因为浏览器缓存问题
const file_name = 'vendor.' + (new Date()).getTime() + '.dll.js';
/****
* 遍历template目录下的html文件,引入生成好的dll.js文件
***/
function set_html_vendor() {
//删除已有的vendor文件
var vendor_file = glob.sync(path.resolve(__dirname, '../static/js/') + '*/*.dll.js');
vendor_file.map(file => [
fs.unlink(file, function (err) {
if (err) {
throw err;
}
})
])
//找到所有的html模板
var entryFiles = glob.sync(PAGE_PATH + '/*/*.html');
entryFiles.map(item_path => {
fs.readFile(item_path, function (err, data) {
if (err) {
throw err;
}
var data_string = data.toString();
//转成dom对象方便读取
var {
document
} = (new JSDOM(data_string)).window;
//更改路径
document.querySelector('#vendor_script').src = '/static/js/' + file_name;
//将修改完成的写入到文件中
fs.writeFile(item_path, '\n\n' + document.querySelector('html').innerHTML + '\n', function (err) {
if (err) {
throw err;
}
})
})
})
}
set_html_vendor();
module.exports = {
entry: {
vendor: [
// 'element-ui',
'babel-polyfill',
'vue-awesome-swiper',
'vue-i18n',
'echarts',
'vue-amap',
'vue',
'vue-router',
'vuex',
'jspdf',
'html2canvas',
'js-md5',
'awe-dnd'
]
},
output: {
path: path.join(__dirname, '../static/js'), //生成dll.js的目录
filename: file_name,
library: '[name]_library' // vendor.dll.js中暴露出的全局变量名
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, '.', '[name]-manifest.json'),
name: '[name]_library'
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
};
复制代码
webpack.base.conf.js
plugins: [
// new webpack.optimize.CommonsChunkPlugin('common.js'), 这行需要注释掉
new webpack.DllReferencePlugin({
manifest: require('./vendor-manifest.json')
}),
],
复制代码
在template中的html文件中引入dll.js文件,此处为script定义了一个id,方便动态更改引入文件地址。
在package.json文件中加入命令
"build:dll": "webpack --config build/webpack.dll.config.js"
复制代码
执行npm run build:dll
执行结果:
在引入之前 vendor文件为5.45M
在引入dll文件之后wendor文件为4.77M
- jQuery的引入
在vue-cli的项目中引入jQuery修改webpack.base.conf.js
文件,打包出来jQuery约200k
plugins: [
new webpack.ProvidePlugin({
jQuery: "jquery",
$: "jquery"
}),
],
复制代码
通过这种方式引入jQuery会增大文件的打包大小,所以直接在html文件中引入jQuery,本文件大小约80k
<script src="/static/js/jquery-3.3.1.min.js">script>
复制代码
- 路由懒加载
参考官方链接: 路由懒加载
五、参考资料
使用 webpack 定制前端开发环境
vue-cli之webpack3构建全面提速优化 webpack进阶——DllPlugin优化打包性能(基于vue-cli)
结合vue-cli来谈webpack打包优化
记一次vue+element+echarts项目的优化(如何轻松将项目性能提升70%)
使用 HappyPack 和 DllPlugin 来提升你的 Webpack 构建速度