webpack优化项
在上一篇webpack基础配置及常用loader中,详细介绍了webpack基础知识及常用loader的使用方法,本篇是对上一篇的延续,对webpack搭建前端工程常用的优化项简单进行一下归纳
noParse
-
noParse作用主要是过滤不需要解析的文件,比如打包的时候依赖了三方库(jquyer、lodash)等,而这些三方库里面没有其他依赖,可以通过配置noParse不去解析文件,提高打包效率
-
配置项
module: {
noParse: '/jquery|lodash/'
}
IgnorePlugin
- IgnorePlugin 是一个 webpack 内置的插件,用于忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去,比如我们使用
moment.js
,直接引用后,里边有大量的 i18n 的代码,导致最后打包出来的文件比较大,而实际场景并不需要这些 i18n 的代码,这时可以通过webpack的IgnorePlugin忽略locale下的文件,使用时只加载需要的语言包 - 配置项
// webapck.config.js
plugins: [
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
]
// index.js
import moment from 'moment';
import 'moment/locale/zh-cn'; // 主动引入所需语言包
moment.locale('zh-cn'); // 设置语言
Dllplugin
通常来说,我们的代码分为业务代码和第三方库。如果不做处理,每次构建时都需要把所有的代码重新构建一次,耗费大量的时间。在我们的依赖版本没有升级的情况下,很多第三方库的代码并不会发生变更,这时就可以用到dll,把第三方模块打包到动态链接库中,每次构建只重新打包业务代码。
使用dll时,可以把构建过程分成dll构建过程和主构建过程,所以需要两个构建配置文件,例如webpack.config.js和webpack.dll.config.js(只需要在版本升级的时候构建一次即可)。
此插件会生成一个名为
manifest.json
的文件,这个文件是用于让DllReferencePlugin
能够映射到相应的依赖上
- 在项目build目录下新建一个webpack.config.dll.js,具体用法在webpack环境区分有讲解
Thread-loader
-
多进程打包,提升打包速度
-
webpack4及以上其实已经融合了多线程机制(terser-webpack-plugin),因此针对小项目来说
Thread-loader
的作用不是很明显。 -
HappyPack 和 Thread-loader,由于 HappyPack 官方已经不再维护了,多进程这方面采用与其类似的 Thread-loader
-
配置时需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行
-
安装依赖
npm i thread-loader -D
- 增加配置项
{
test: /\.m?js$/,
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', {
useBuiltIns: "entry",
corejs: 3,
targets: {
chrome: "58",
ie: "11"
}
}]],
plugins: ["@babel/plugin-transform-runtime"]
}
}, {
loader: "thread-loader", // 开启多进程,这种方法同样适用于css、vue等处理
}]
},
HMR
Hot Module Replacement,也称为 HMR,是一种在应用程序运行时替换、添加或删除模块的技术,无需完全刷新页面就能更新这些模块
修改 JS 文件,无需刷新页面,而能够直接在页面进行代码更新。
修改 CSS 文件,无需刷新页面,改动的样式能直接呈现
webpack-dev-server默认会开启HMR,但需要做一些特殊的配置,而且HRM只能在开发环境中使用
- webpack-dev-server开启HMR
// webpack.config.js
const webpack = require('webpack');
module.exports = {
devServer: {
hot: true
},
plugins: [
// 模块热替换插件
new webpack.HotModuleReplacementPlugin()
]
}
- 业务代码中使用
// 入口文件中,index.js
if(module.hot) {
module.hot.accept()
}
webpack环境区分
配置文件拆分
- 目录结构
│ package-lock.json
│ package.json
│ postcss.config.js
│
├─build
│ webpack.config.base.js
│ webpack.config.dev.js
│ webpack.config.dll.js
│ webpack.config.prod.js
│
├─config
│ dev.env.js
│ prod.env.js
│ test.env.js
│
├─public
│ ├─lib
│ │ vendor.dll.dev.js
│ │ vendor.dll.prod.js
│ │ vendor.manifest.dev.json
│ │ vendor.manifest.prod.json
│ │
│ └─others
└─src
│ a.js
│ App.vue
│ index.css
│ index.html
│ index.js
│ other.js
│
├─assets
│ ├─fonts
│ │ DIGITAL-Regular.ttf
│ │
│ ├─images
│ │ logo.png
│ │
│ └─medias
└─pages
index.vue
- 模板html修改(html-webpack-plugin默认支持此语法)
<title><%= htmlWebpackPlugin.options.title %></title>
<body>
<div id="app"></div>
<!-- 引入通过Dllplugin生成的第三方依赖包 -->
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.path %>"></script>
</body>
- 配置文件的合并,需要通过
webpack-merge
插件
npm i webpack-merge -D
- 定义多个配置文件,区分开发和生产环境
项目下新建build文件夹,并依次创建以下文件
- webpack.config.base.js
- webpack.config.dev.js
- webpack.config.prod.js
- webpack.config.dll.js
- 修改打包命令脚本
"scripts": {
"dev": "cross-env ENV_CONFIG=dev webpack-dev-server --config build/webpack.config.dev.js",
"build": "cross-env ENV_CONFIG=prod webpack --config build/webpack.config.prod.js",
"dll:dev": "cross-env ENV_CONFIG=dev webpack --config build/webpack.config.dll.js",
"dll:prod": "cross-env ENV_CONFIG=prod webpack --config build/webpack.config.dll.js"
},
webpack.config.dll.js
/**
* 提取第三方依赖,单独打包
* 只有当相关依赖有更新、升级或着在该文件中添加vendor时,需重新打包
* npm run dll:dev 开发环境使用
* npm run dll:prod 线上环境使用
* 打包后会在项目根目录下的public/lib文件夹下生成对应的清单及文件
* 项目打包发布时会自动将public/lib文件夹拷贝至dist目录
*/
const path = require('path');
const webpack = require('webpack');
const TerserJSPlugin = require('terser-webpack-plugin');
const DllPlugin = require('webpack/lib/DllPlugin');
const envMode = process.env.ENV_CONFIG === 'dev' ? 'development' : 'production'
const dllMode = process.env.ENV_CONFIG
module.exports = {
mode: envMode,
entry: {
vendor: [
"core-js",
"element-plus",
"vue",
"vue-router",
"vuex"
]
},
output: {
path: path.resolve(__dirname, `../public/lib`),
filename: `[name].dll.${dllMode}.js`, // 打包文件的名字
library: '[name]_library' // 暴露出的全局变量名
},
plugins: [
new DllPlugin({
path: path.resolve(__dirname, `../public/lib/[name].manifest.${dllMode}.json`), //描述生成的manifest文件
name: '[name]_library', // 暴露出的全局变量名
})
],
optimization: {
minimizer: [
new TerserJSPlugin({ // 优化js
parallel: true,
extractComments: false, //不将注释提取到单独的文件中
terserOptions: {
compress: {
drop_console: false, // 移除console
drop_debugger: true, // 移除debugger
},
},
}),
],
},
}
webpack.config.base.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { VueLoaderPlugin } = require('vue-loader')
const webpack = require('webpack');
const envConfig = require(`../config/${process.env.ENV_CONFIG}.env.js`)
module.exports = {
// 解析入口点(entry point)和加载器(loader)的上下文
// 也即配置文件中的相对路径参照点,这里使用的是项目根目录
context: path.resolve(__dirname, '../'),
entry: { // 配置多入口
'home': './src/index.js',
'other': './src/other.js'
},
output: {
filename: 'js/[name].[contenthash].js', // 文件添加hash戳
path: path.resolve(__dirname, '../dist'), // 在根目录生成一个dist的文件夹
clean: true, // 在生成文件之前清空 output 目录
},
plugins: [
// 字符串务必用两个引号,如 '"production"'
new webpack.DefinePlugin({
_WBPACK_ENV_VARIABLE: JSON.stringify(envConfig)
}),
// vue-loader在15.*之后的版本需要添加此插件
new VueLoaderPlugin()
],
module: {
rules: [
// 务必把此loader放在第一个
{
test: /\.vue$/,
use: [
{
loader: 'vue-loader',
options: {
hotReload: true // 开启HMR
}
},
'thread-loader'
]
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'thread-loader' // 使用多进程处理
]
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'less-loader',
'thread-loader' // 使用多进程处理
]
},
{
test: /\.(png|jp?g|gif|svg)(\?.*)?$/,
type: 'javascript/auto',
use: [
{
loader: 'url-loader',
options: {
limit: 10 * 1024,
esModule: false,
outputPath: "images",
}
}
],
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
type: 'javascript/auto',
use: [
{
loader: 'url-loader',
options: {
limit: 10 * 1024,
esModule: false,
outputPath: "fonts",
}
}
],
},
{
test: /\.(mp4?|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
type: 'javascript/auto',
use: [
{
loader: 'url-loader',
options: {
limit: 10 * 1024,
esModule: false,
outputPath: "medias",
}
}
],
},
]
},
resolve: {
modules: [path.resolve('node_modules')], // 缩小查找范围
extensions: ['.js', '.css', '.json', '.vue'], // 引入这些类型的文件,可以不用写后缀
alias: { // 给路径起别名
'@': path.resolve(__dirname, '../src'),
'@assets': path.resolve(__dirname, '../src/assets'),
}
}
}
webpack.config.dev.js
const { merge } = require('webpack-merge')
const base = require('./webpack.config.base.js')
const webpack = require('webpack');
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const publicPath = '/'
module.exports = merge(base, {
mode: 'development',
devtool: 'source-map',
target: 'web',
output: {
publicPath: publicPath, // 开发环境只能是/或者为空,否则热更新失效或无法访问
},
devServer: {
port: 3001, // 服务端口
host: '0.0.0.0', // 服务器可以被外部访问
client: {
progress: true, // 在浏览器中以百分比显示编译进度
},
compress: true, // 是否启用gzip压缩
/**
* 开发时,我们修改了其中一个模块代码,webpack 默认会将所有模块全部重新打包,速度很慢
* 开启HMR后,只有改动的模块重新打包,其他的模块不变,这样打包速度就可以加快
* hot: true|false 开启 HMR,默认是 true
* 开发环境下,css 文件默认支持 HMR,是因为 style-loader 做了支持,
* 如果使用 MiniCssExtractPlugin.loader 则 css 的 HMR 会失效,
* 所以建议开发环境下使用 style-loader,生产环境下使用 MiniCssExtractPlugin.loader
*/
hot: true,
proxy: [{ // 配置代理信息
context: ['/api'],
target: 'http://localhost:3000',
}],
},
plugins: [
// 将打包后的js文件注入到html中,可通过chunks属性选择性注入
// 如果有多个页面,需要写多个插件
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['home'], // 将对应的打包文件对号入座
hash: true, // 在引入js文件时,在路径后面添加hash戳
inject: 'body', // 打包文件插入位置 head或body
title: 'webpack前端工程搭建',
path: `${publicPath}lib/vendor.dll.dev.js` // 引用动态链接库时使用
}),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'main.html',
chunks: ['other'],
hash: true,
inject: 'body',
title: '多页面打包测试',
path: `${publicPath}/lib/vendor.dll.dev.js`
}),
new MiniCssExtractPlugin({
// 低版本使用需要把[fullhash]替换为[hash]
// 开发环境热更新不兼容,去掉fullhash就好了,不知道为什么???
filename: 'css/[name].css' //: '[name].[fullhash].css'
}),
// 将那些文件拷贝到打包后的文件夹中
new CopyWebpackPlugin({
patterns: [
{
from: './public',
to: './',
globOptions: {
ignore: [
'**/vendor.dll.prod.js',
'**/vendor.manifest.prod.json'
],
},
},
],
}),
// 告诉webpack那些库不参与打包
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, `../public/lib/vendor.manifest.dev.json`)
}),
// 热更新模块配置
new webpack.HotModuleReplacementPlugin()
]
})
webpack.config.prod.js
const { merge } = require('webpack-merge')
const base = require('./webpack.config.base.js')
const path = require('path');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
/**
*
* 打包后的文件添加公共前缀,可以是相对路径、绝对路径或域名
* 绝对路径:'/dist/',部署服务器如nginx、或live-server等
* 相对路径:'./',可通过浏览器直接打开访问(相关loader需要添加publicPath)
* 域名:'http://www.xx.com/',域名访问
*
*/
const publicPath = '/dist/'
module.exports = merge(base, {
mode: 'production',
devtool: 'eval-source-map',
output: {
publicPath: publicPath,
},
optimization: {
minimizer: [
new CssMinimizerPlugin({ // 优化css
parallel: true, // 多进程并发执行,提升构建速度
}),
new TerserJSPlugin({ // 优化js
parallel: true, // 多进程并发执行,提升构建速度
extractComments: false, // 是否将注释提取到单独的文件中
terserOptions: {
compress: {
drop_console: false, // 移除console
drop_debugger: true, // 移除debugger
},
},
}),
],
// 分割代码块
// splitChunks: {
// /*
// initial 入口 chunk,对于异步导入的文件不处理
// async 异步 chunk,只对异步导入的文件处理
// all 全部 chunk
// */
// chunks: 'all',
// // 缓存分组
// cacheGroups: {
// // 第三方模块
// vendor: {
// name: 'vendor', // chunk 名称
// priority: 1, // 权限更高,优先抽离
// test: /node_modules/, // 一般第三方模块都在node_modules文件夹
// minSize: 0, // 大小限制
// minChunks: 1 // 最少复用过几次
// },
// // 公共的模块
// common: {
// name: 'common', // chunk 名称
// priority: 0, // 优先级
// minSize: 0, // 公共模块的大小限制
// minChunks: 2 // 公共模块最少复用过几次
// }
// }
// }
},
module: {
rules: [
// 为提升编译速度,开发环境可以不启用babel转换,只在生产环境使用
{
test: /\.m?js$/,
include: path.resolve(__dirname, '../src'),
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', {
useBuiltIns: "entry",
corejs: 3,
targets: {
chrome: "58",
ie: "11"
}
}]],
plugins: ["@babel/plugin-transform-runtime"]
}
}, {
loader: "thread-loader", // 使用多进程处理
}]
}],
},
plugins: [
// 将打包后的js文件注入到html中,可通过chunks属性选择性注入
// 如果有多个页面,需要写多个插件
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
//对打包的html页面进行最小化操作
minify: {
removeAttributeQuotes: true, //删除属性的双引号
collapseWhitespace: true //所有代码一行显示,折叠空行
},
chunks: ['home'], // 将对应的打包文件对号入座
hash: true, // 在引入js文件时,在路径后面添加hash戳
inject: 'body', // 打包文件插入位置 head或body
title: 'webpack前端工程搭建',
path: `${publicPath}lib/vendor.dll.prod.js` // 引用动态链接库时使用
}),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'main.html',
chunks: ['other'], // 将对应的打包文件对号入座
hash: true, //在引入js文件时,在路径后面添加hash戳
inject: 'body',
title: '多页面打包测试',
path: `${publicPath}/lib/vendor.dll.prod.js` // 引用动态链接库时使用
}),
new MiniCssExtractPlugin({
// 解决和开发环境不兼容问题
filename: 'css/[name].[fullhash].css'
}),
// 将那些文件拷贝到打包后的文件夹中
new CopyWebpackPlugin({
patterns: [
{
from: './public',
to: './',
globOptions: {
ignore: [
'**/vendor.dll.dev.js',
'**/vendor.manifest.dev.json'
],
},
},
],
}),
// 告诉webpack那些库不参与打包
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, `../public/lib/vendor.manifest.prod.json`)
}),
]
})