前言
在上一篇我们讲到了webpack的初级使用,接下来我们讲一下进阶的使用方式。本篇的进阶方式主要是用在生产环境的打包上,开发环境由于只是在本地运行,webpack在热更新方面速度也还可以接受,所以开发环境一般优化需求不大。
多页面打包
有时候我们做一个web应用是多页面的,如果按照以前webpack的配置方式,我们可能需要写多个entry和HtmlWebpackPlugin。显然这种重复繁琐的功能不是我们程序员想要的,于是我们可以这么做:
const setMPA = () => {
const entry = {};
const htmlWebpackPlugins = [];
const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));
Object.keys(entryFiles)
.map((index) => {
const entryFile = entryFiles[index];
//我们默认多页面规范的入口是src里面每个文件夹里的index.js
const match = entryFile.match(/src\/(.*)\/index\.js/);
const pageName = match && match[1];
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
template: path.join(__dirname, `src/${pageName}/index.html`),
filename: `${pageName}.html`,
chunks: [pageName],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
);
});
return {
entry,
htmlWebpackPlugins
}
}
const { entry, htmlWebpackPlugins } = setMPA();
module.exports = {
entry: entry,
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
plugins: [
].concat(htmlWebpackPlugins)
};
压缩js
TerserPlugin 是一个比较成熟的js压缩插件,可以帮助我们压缩js代码,清除注释、console等
npm install terser-webpack-plugin --save-dev
const TerserPlugin = require('terser-webpack-plugin');
optimization: {
concatenateModules:true,
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments:false
},
cache: true,
extractComments:false
}
})
]
}
压缩css,补充浏览器兼容前缀
optimize-css-assets-webpack-plugin 能够较好地帮助我们压缩css,结合cssnano可以自动补全代码
npm install --save-dev optimize-css-assets-webpack-plugin
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
optimization: {
minimizer: [
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.optimize\.css$/g,
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
preset: ['default', { discardComments: { removeAll: true } }],
},
canPrint: true
})
]
},
自定义分包
webpack提供了默认的分包机制,通常情况下可以直接使用。但是我们仍然可以根据项目需要来制定我们的最佳分包方式
optimization: {
splitChunks:{
chunks:"all",
minSize: 30000, //表示在压缩前的最小模块大小,默认值是30kb
minChunks: 1, // 表示被引用次数,默认为1;
maxAsyncRequests: 5, //所有异步请求不得超过5个
maxInitialRequests: 3, //初始话并行请求不得超过3个
automaticNameDelimiter:'~',//名称分隔符,默认是~
name: true, //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔
cacheGroups: { //设置缓存组用来抽取满足不同规则的chunk
// 举例,echarts是登陆后才需要引入的,单独拿出来
echarts:{
minChunks:1,
test:/[\\/]node_modules[\\/](echarts|echart-gl)/,
priority:10,
name:'echarts'
},
// 举例,demoCommonModule是被很多其他模块调用的,单独拿出来,不然会被分散重复打到其他的包去
demoCommonModule:{
minChunks:1,
test:/[\\/]views[\\/]demoCommonModule/,
priority:10,
name:'echarts'
}
... 其他模块也采用类似的方式抽出来,具体要看BundleAnalyzerPlugin分析是否合理
}
}
标签式引入第三方库
有时候对于一些第三方类库,我们并不希望webpack将他打包进bundle里面,而是采用script标签的形式引入。这种情况下HtmlWebpackExternalsPlugin可以帮我们实现。(主要注意的是你仍需要在代码里面进行import)
npm install --save-dev html-webpack-externals-plugin
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'vue',
entry: 'https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js',
global: 'Vue',
}
]
}),
速度分析
有时候项目过大会导致构建起来非常耗时,处于对构建优化的考虑,我们可能需要知道哪个构建步骤运行的时间比较久,从而有针对性地进行优化。speed-measure-webpack-plugin就可以帮我们计算各构建步骤的耗时时间。但需要注意的是:只在优化的过程中使用,优化完之后应该禁用该插件,因为这个插件本身也会带来一些时间消耗。
npm install --save-dev speed-measure-webpack-plugin
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const webpackConfig = smp.wrap({
plugins: [
new MyPlugin(),
new MyOtherPlugin()
]
});
体积分析
webpack-bundle-analyzer是个优化项目的神兵利器,他可以帮助我们查看打出来的bundle都包含了哪些文件,从而让我们识别构建是否合理,分包是否正确。
npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [
new BundleAnalyzerPlugin()
]
gzip压缩
通常,用户访问web项目时加载资源文件最好使用gzip的格式,而这个格式转化是由服务器来完成的。但是毕竟服务器转化格式也需要耗时,所以我们可以将gzip压缩这个步骤在前端完成。compression-webpack-plugin就是做这个事情。
npm i -D compression-webpack-plugin
const CompressionPlugin = require("compression-webpack-plugin")
module.exports = {
plugins: [
new CompressionPlugin()
]
}
文件复制
npm install --save-dev copy-webpack-plugin
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
context: path.join(__dirname, 'app'),
devServer: {
// This is required for older versions of webpack-dev-server
// if you use absolute 'to' paths. The path should be an
// absolute path to your build destination.
outputPath: path.join(__dirname, 'build')
},
plugins: [
new CopyWebpackPlugin([
// {output}/to/directory/file.txt
{ from: 'from/file.txt', to: 'to/directory' },
// Copy directory contents to {output}/to/directory/
{ from: 'from/directory', to: 'to/directory' },
])
]
};
webpack自定义bundle名称以及预加载
import(/* webpackChunkName:"global" */ /* webpackPreFetch:true*/ "@/utils/globalImport.js");
构建使用缓存
webpack每次构建的时候都会去遍历编译每一个依赖的文件,这种做法有时候在二次编译时往往低效,因为只有少量的文件被更改了。于是我们可以配置使用缓存的方式,将已经编译过的并且没有更改的文件缓存起来,提高二次编译速度。
npm install --save-dev cache-loader
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
'cache-loader',
...loaders
],
include: path.resolve('src')
}
]
}
}
也可以使用bableLoader 本身支持的cache功能
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory
}
多线程打包
我们知道JavaScript是单线程的,但是在构建中我们依然可以使用多线程构建我们的应用,从而加快构建速度。
npm install --save-dev thread-loader
const threadLoader = require('thread-loader');
//线程预热
threadLoader.warmup({}, [
'babel-loader',
'babel-preset-es2015',
'sass-loader',
]);
rules: [
{
test: /\.js$/,
include: path.resolve("src"),
use: [
"thread-loader",
// your expensive loader (e.g babel-loader)
]
}
]
结语
以上就是webpack的一些使用方式,后续仍会继续补充,也欢迎更多的同行参与讨论,批评指正。
最终webpack.config.prod.js
const path=require('path');
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const CompressionPlugin = require("compression-webpack-plugin")
const threadLoader = require('thread-loader');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
//线程预热
threadLoader.warmup({}, [
'babel-loader',
'sass-loader',
'vue-loader'
]);
const webpackconfig = {
entry:{
index:'./src/main.js'
},
output:{
filename:'[name].js',
path: path.resolve(__dirname, "dist"),
},
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments:false
},
extractComments:false
}
}),
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.optimize\.css$/g,
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
preset: ['default', { discardComments: { removeAll: true } }],
},
canPrint: true
})
],
splitChunks:{
chunks:"all",
minSize: 30000, //表示在压缩前的最小模块大小,默认值是30kb
minChunks: 1, // 表示被引用次数,默认为1;
maxAsyncRequests: 5, //所有异步请求不得超过5个
maxInitialRequests: 3, //初始话并行请求不得超过3个
automaticNameDelimiter:'~',//名称分隔符,默认是~
name: true, //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔
cacheGroups: { //设置缓存组用来抽取满足不同规则的chunk
// 举例,echarts是登陆后才需要引入的,单独拿出来
echarts:{
minChunks:1,
test:/[\\/]node_modules[\\/](echarts|echart-gl)/,
priority:10,
name:'echarts'
},
// 举例,demoCommonModule是被很多其他模块调用的,单独拿出来,不然会被分散重复打到其他的包去
demoCommonModule:{
minChunks:1,
test:/[\\/]views[\\/]demoCommonModule/,
priority:10,
name:'echarts'
}
}
}
},
mode:"production",
module:{
rules:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: [
'cache-loader',
'babel-loader',
]
},
{
test: /\.vue$/,
loader: [
'cache-loader',
'vue-loader'
]
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
],
},
{
test: /.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
]
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
],
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
esModule: false, // 不加的话会有这种情况 img属性src="[object Module]"
limit: 1024 * 1,
name: 'assets/images/[name]-[contenthash:8].[ext]'
}
}
]
},
{
test: /\.(ttf|woff)$/,
use: [
{
loader: 'file-loader',
options: {
name:'./assets/styles/font/[name]-[contenthash:8].[ext]'
}
},
{
loader: 'url-loader',
options: {
limit: 10240
}
}
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: "index.html",
template: "./src/template/index.html",
inlineSource: '.css$',
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
}),
// new HtmlWebpackExternalsPlugin({
// externals: [
// {
// module: 'vue',
// entry: 'https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js',
// global: 'Vue',
// }
// ]
// }),
new MiniCssExtractPlugin({
chunkFilename: 'assets/styles/[id]-[contenthash:8].css',
ignorOrder:true
}),
new VueLoaderPlugin(),
// new BundleAnalyzerPlugin(),
new CompressionPlugin()
]
}
module.exports= smp.wrap(webpackconfig)