系统化学习前端之webpack(进阶)
webpack 高级配置
Webpack 发展至今,除了编译资源外,还拓展了许多 loader 和 plugin 来辅助提升项目性能。
写在前面
webpack 功能之强大,令吾等为之惊叹,欢迎挖宝。
提升开发体验
SourceMap
使用 webpack 解析 js 和 css 文件,然后输出到指定文件,代码经过编译输出以后,我们在开发过程中是很难定位 bug 在哪个文件,哪一行的。因此,我们需要通过 SourceMap(源代码映射) 来帮助我们定位 bug 位置。其原理:代码构建过程中,会生成 .map 文件,包含源码与编译代码的映射关系,通过映射可以快速定位 bug。
-
开发模式配置 SourceMap
module.exports = { mode: "development", devtool: "cheap-module-source-map", };
-
生产模式配置 SourceMap
module.exports = { mode: "production", devtool: "source-map", };
注意: 开发模式配置 SourceMap 值为:
cheap-module-source-map
,只包含行映射,没有列映射,打包速度快; 生产模式配置:source-map
,包含行、列映射,打包速度慢。根据实际情况,可以自行设置devtool 值 ,其他 devtool 配置 。
提升打包构建速度
HotModuleReplacement(HMR)
webpack 默认会编译打包项目中所有模块,但是我们开发过程中可能只会针对某一模块修改,因此 HMR 帮助我们只编译打包修改后的模块,其他未修改的模块不参与打包,提高打包速度。其原理:开启 HMR ,修改模块是更新模块内容,页面不刷新,关闭 HMR,修改模块是更新整个页面,页面刷新。
-
开启 HMR
module.exports = { devServer: { host: "localhost", port: "3000", open: true, hot: true, // 开启HMR功能(只用于开发环境) }, };
-
配置 loader
css 通过 style-loader 处理后具备 HMR 功能;vue 通过 vue-loader 处理后具备 HMR 功能;react 通过 react-hot-loader 处理后具备 HMR 功能;js 文件只能通过在入口文件,使用 module.hot.accept('./xx.js') 重新加载文件方式实现 HMR 功能;
oneOf
使用 loader 时,use 可以配置多个loader,多种文件也会配置使用多个 loader。文件经过 loader 需要先验证,后处理。test 正则使得 loader 不处理部分文件, 但是每个文件处理会默认通过所有 loader 验证,因此降低打包构建速度。而 oneOf 类似正则中的贪婪匹配,只匹配一个 loader,一旦验证且 test 通过,不经过其他 loader 。
module.exports = {
module: {
rules: [
{
oneOf: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
{
test: /\.s[ac]ss$/,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.styl$/,
use: ["style-loader", "css-loader", "stylus-loader"],
},
{
test: /\.(png|jpe?g|gif|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
generator: {
filename: "static/imgs/[hash:8][ext][query]",
},
},
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",
generator: {
filename: "static/media/[hash:8][ext][query]",
},
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
},
],
},
],
},
}
Include/Exclude
使用 loader 时,可以使用 test 正则匹配,也可以使用 Include 指定,或者 Exclude 排除部分文件;
module.exports = {
module: {
rules: [
{
oneOf: [
{
test: /\.js$/,
// exclude: /node_modules/, // 排除node_modules代码不编译
include: path.resolve(__dirname, "../src") // 只包含src下的 js 文件
loader: "babel-loader", // 只使用一个 loader,可以不是用 use
}
]
}
]
},
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", // 默认值
}),
]
}
Cache
js 文件每次编译打包都需要经过 Eslint 检查和 Babel 编译,影响打包构建速度,使用 Cache 缓存之前的检查和编译结果,提升第二次及后续打包构建速度。
module.exports = {
module: {
rules: [
{
oneOf: [
{
test: /\.js$/,
// exclude: /node_modules/, // 排除node_modules代码不编译
loader: "babel-loader", // 只使用一个 loader,可以不是用 use
options: {
cacheDirectory: true, // 开启babel编译缓存
cacheCompression: false, // 缓存文件不要压缩
}
}
]
}
]
},
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", // 默认值
cache: true, // 开启缓存
cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"), // 缓存目录
}),
]
}
Thead
针对较大的项目,可以使用 Thead 开启多进程,提高打包构建速度。之所以针对较大的项目,是因为每个进程启动会损耗大约 600ms 的开销,较大的项目会有所提升,而小项目可能带来多余的开销。
-
安装依赖
npm i thread-loader -D
-
webpack 配置
const os = require("os"); const TerserPlugin = require("terser-webpack-plugin"); const threads = os.cpus().length; // 进程数 == CPU核数 module.exports = { module: { rules: [ { oneOf: [ { test: /\.js$/, include: path.resolve(__dirname, "../src"), use: [ { loader: "thread-loader", // 开启多进程 options: { workers: threads, // 数量 }, }, { loader: "babel-loader", options: { cacheDirectory: true, // 开启babel编译缓存 }, }, ], } ] } ] }, plugins: [ new ESLintWebpackPlugin({ context: path.resolve(__dirname, "../src"), exclude: "node_modules", cache: true, cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"), threads, // 开启多进程 }), ], optimization: { minimize: true, minimizer: [ new CssMinimizerPlugin(), // css压缩同样可以写到optimization.minimizer里面 new TerserPlugin({ // 生产模式会默认开启TerserPlugin,但需要进行其他配置,需要重新写 parallel: threads // 开启多进程 }) ], }, }
减少代码体积
Tree Shaking
开发中引入函数库或者组件库,实际开发只使用了部分函数和组件,Tree Shaking 使得按需引入,webpack 默认开启 Tree Shaking。
注意: Tree Shaking 依赖 ES Module,使用CommonJS导入无法使用。
Babel
Babel 在编译的过程中,会为每个文件的 js 都插入辅助代码,对于一些公共方法会重复添加辅助代码,增大代码体积。@babel/plugin-transform-runtime
是禁用了 Babel 注入辅助代码,为 js 提供辅助代码外部源,所有 js 文件都通过 @babel/plugin-transform-runtime
引用辅助代码。
-
安装依赖
npm i @babel/plugin-transform-runtime -D
-
webpack 配置
module.exports = { module: { rules: [ { oneOf: [ { test: /\.js$/, include: path.resolve(__dirname, "../src"), use: [ { loader: "thread-loader", // 开启多进程 options: { workers: threads, // 数量 }, }, { loader: "babel-loader", options: { cacheDirectory: true, // 开启babel编译缓存 cacheCompression: false, // 缓存文件不要压缩 plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积 }, }, ], } ] } ] }, }
Image Minimizer
项目开发引入图片资源体积较大,可以通过 image-minimizer-webpack-plugin
对图片资源压缩,减少图片体积。
-
安装依赖
-
无损压缩
npm i image-minimizer-webpack-plugin imagemin -D npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
-
有损压缩
npm i image-minimizer-webpack-plugin imagemin -D npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
-
-
webpack 配置
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); module.exports = { optimization: { minimizer: [ new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.imageminGenerate, options: { plugins: [ ["gifsicle", { interlaced: true }], ["jpegtran", { progressive: true }], ["optipng", { optimizationLevel: 5 }], [ "svgo", { plugins: [ "preset-default", "prefixIds", { name: "sortAttrs", params: { xmlnsOrder: "alphabetical", }, }, ], }, ], ], }, }, }), ] } }
注意:由于包安装的原因,可能导致压缩库下载失败,如果编译过程报错:Error: xxxxx.exe问题,执行下一步。
-
安装压缩软件
-
jpegtran 官网 下载 jpegtran.exe 配置项目
node_modules\jpegtran-bin\vendor
下; -
OptiPNG 官网 下载 optipng.exe 配置项目
node_modules\optipng-bin\vendor
下。
-
优化代码运行性能
Code Split
webpack 编译打包 js 默认会打包到一个文件中,文件体积较大,影响页面加载。因此需要 Code Split 分割 js 文件,按需加载。
-
多入口多出口
module.exports = { entry: { main: "./src/main.js", app: "./src/app.js" }, output: { path: path.resolve(__dirname, "dist"), filename: "js/[name].js", clean: true, } }
注意: 配置了多个入口,会输出多个 js 文件。
-
单入口模块分割
module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 filename: "static/js/[name].js", // 入口文件打包输出资源命名方式 chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式 assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash) clean: true, }, module: { rules: [ { oneOf: [ { test: /\.(png|jpe?g|gif|svg)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 }, }, }, { test: /\.(ttf|woff2?)$/, type: "asset/resource", }, ] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: "static/css/[name].css", chunkFilename: "static/css/[name].chunk.css", }), ], splitChunks: { chunks: "all", // 对所有模块都进行分割 // 以下是默认值 // minSize: 20000, // 分割代码最小的大小 // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0 // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割 // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量 // maxInitialRequests: 30, // 入口js文件最大并行请求数量 // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests) // cacheGroups: { // 组,哪些模块要打包到一个组 // defaultVendors: { // 组名 // test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块 // priority: -10, // 权重(越大越高) // reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块 // }, // default: { // 其他没有写的配置会使用上面的默认值 // minChunks: 2, // 这里的minChunks权重更大 // priority: -20, // reuseExistingChunk: true, // }, // }, }, }
Preload / Prefetch
Preload / Prefetch 指定浏览器如何加载资源,均只会加载资源,并缓存,不执行资源。Preload
浏览器立即加载资源,只能加载当前页面需要的资源;Prefetch
浏览器在空闲时才开始加载资源,可以加载当前页面及下一个页面需要的资源;但 Preload 相对于 Prefetch 兼容性好一点。
-
安装依赖
npm i @vue/preload-webpack-plugin -D
-
webpack 配置
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin"); module.exports = { plugins: [ new PreloadWebpackPlugin({ // rel: 'prefetch' // prefetch兼容性更差 rel: "preload", // preload兼容性更好 as: "script", }), ] }
Network Cache
缓存,又爱又恨!静态资源可以通过缓存来提高访问速度,但是文件更新以后,文件名未发生变化会导致浏览器直接读取缓存,不会加载新资源。因此通过hash解决文件名更新是优化缓存使用的根本手段。
-
fullhash
webpack4 是 hash,每次修改文件,fullhash都会改变,文件缓存失效。
-
chunkhash
根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。js 和 css 是同一个入口文件引入,会共享一个 hash 值。
-
contenthash
根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。
module.exports = { output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 filename: "static/js/[name].[contenthash:8].js", // 入口文件打包输出资源命名方式 chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式 assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash) clean: true, }, plugins: [ new MiniCssExtractPlugin({ filename: "static/css/[name].[contenthash:8].css", chunkFilename: "static/css/[name].[contenthash:8].chunk.css", }), ] }
注意:当某一文件内容改变,其 contenthash 值改变,那引入该文件的主文件内容也会发生变化,如 import name.xxx.js 会变成 import name.yyy.js。如此,间接导致主文件缓存失效。针对这一问题,可以将 hash 与文件映射保存在单独 runtime 文件中,runtime 文件小,更新开销小。
修正后
module.exports = { output: { path: path.resolve(__dirname, "../dist"), // 生产模式需要输出 filename: "static/js/[name].[contenthash:8].js", // 入口文件打包输出资源命名方式 chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式 assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash) clean: true, }, plugins: [ new MiniCssExtractPlugin({ filename: "static/css/[name].[contenthash:8].css", chunkFilename: "static/css/[name].[contenthash:8].chunk.css", }), ], runtimeChunk: { name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则 }, }
Core-js
Babel 可以处理 js 文件中 ES6 语法,如箭头函数,解构等,但是async, promise, 数组部分方法 includes 等是不能处理的,使用 core-js 解决兼容性问题。
-
安装依赖
npm i @babel/eslint-parser -D npm i core-js
-
eslint 配置(.eslintrc.js)
module.exports = { // 继承 Eslint 规则 extends: ["eslint:recommended"], parser: "@babel/eslint-parser", // 支持最新的 ECMAScript 标准 env: { node: true, // 启用node中全局变量 browser: true, // 启用浏览器中全局变量 }, plugins: ["import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的 parserOptions: { ecmaVersion: 6, // es6 sourceType: "module", // es module }, rules: { "no-var": 2, // 不能使用 var 定义变量 }, }
-
babel 配置(babel.config.js)
module.exports = { presets: [ [ "@babel/preset-env", { useBuiltIns: "usage", corejs: { version: "3", proposals: true } }, // 按需加载core-js的polyfill ], ], };
PWA
离线访问,PWA应用,内部通过 Service Workers 技术实现。详细查看 渐进式网络应用程序
写在后面
实际项目应用:
React-cli
点击查看代码
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const isProduction = process.env.NODE_ENV === "production";
const getStyleLoaders = (preProcessor) => {
return [
isProduction ? MiniCssExtractPlugin.loader : "style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env",
],
},
},
},
preProcessor && {
loader: preProcessor,
options:
preProcessor === "less-loader"
? {
// antd的自定义主题
lessOptions: {
modifyVars: {
// 其他主题色:https://ant.design/docs/react/customize-theme-cn
"@primary-color": "#1DA57A", // 全局主色
},
javascriptEnabled: true,
},
}
: {},
},
].filter(Boolean);
};
module.exports = {
entry: "./src/main.js",
output: {
path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
filename: isProduction
? "static/js/[name].[contenthash:10].js"
: "static/js/[name].js",
chunkFilename: isProduction
? "static/js/[name].[contenthash:10].chunk.js"
: "static/js/[name].chunk.js",
assetModuleFilename: "static/js/[hash:10][ext][query]",
clean: true,
},
module: {
rules: [
{
oneOf: [
{
test: /\.css$/,
use: getStyleLoaders(),
},
{
test: /\.less$/,
use: getStyleLoaders("less-loader"),
},
{
test: /\.s[ac]ss$/,
use: getStyleLoaders("sass-loader"),
},
{
test: /\.styl$/,
use: getStyleLoaders("stylus-loader"),
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",
},
{
test: /\.(jsx|js)$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: [
// "@babel/plugin-transform-runtime", // presets中包含了
!isProduction && "react-refresh/babel",
].filter(Boolean),
},
},
],
},
],
},
plugins: [
new ESLintWebpackPlugin({
extensions: [".js", ".jsx"],
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true,
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
isProduction &&
new MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:10].css",
chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
}),
!isProduction && new ReactRefreshWebpackPlugin(),
// 将public下面的资源复制到dist目录去(除了index.html)
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"),
to: path.resolve(__dirname, "../dist"),
toType: "dir",
noErrorOnMissing: true, // 不生成错误
globOptions: {
// 忽略文件
ignore: ["**/index.html"],
},
info: {
// 跳过terser压缩js
minimized: true,
},
},
],
}),
].filter(Boolean),
optimization: {
minimize: isProduction,
// 压缩的操作
minimizer: [
// 压缩css
new CssMinimizerPlugin(),
// 压缩js
new TerserWebpackPlugin(),
// 压缩图片
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
[
"svgo",
{
plugins: [
"preset-default",
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnsOrder: "alphabetical",
},
},
],
},
],
],
},
},
}),
],
// 代码分割配置
splitChunks: {
chunks: "all",
cacheGroups: {
// layouts通常是admin项目的主体布局组件,所有路由组件都要使用的
// 可以单独打包,从而复用
// 如果项目中没有,请删除
layouts: {
name: "layouts",
test: path.resolve(__dirname, "../src/layouts"),
priority: 40,
},
// 如果项目中使用antd,此时将所有node_modules打包在一起,那么打包输出文件会比较大。
// 所以我们将node_modules中比较大的模块单独打包,从而并行加载速度更好
// 如果项目中没有,请删除
antd: {
name: "chunk-antd",
test: /[\\/]node_modules[\\/]antd(.*)/,
priority: 30,
},
// 将react相关的库单独打包,减少node_modules的chunk体积。
react: {
name: "react",
test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
chunks: "initial",
priority: 20,
},
libs: {
name: "chunk-libs",
test: /[\\/]node_modules[\\/]/,
priority: 10, // 权重最低,优先考虑前面内容
chunks: "initial",
},
},
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`,
},
},
resolve: {
extensions: [".jsx", ".js", ".json"],
},
devServer: {
open: true,
host: "localhost",
port: 3000,
hot: true,
compress: true,
historyApiFallback: true,
},
mode: isProduction ? "production" : "development",
devtool: isProduction ? "source-map" : "cheap-module-source-map",
performance: false, // 关闭性能分析,提示速度
};
Vue-cli
点击查看代码
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
const { DefinePlugin } = require("webpack");
const AutoImport = require("unplugin-auto-import/webpack");
const Components = require("unplugin-vue-components/webpack");
const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");
// 需要通过 cross-env 定义环境变量
const isProduction = process.env.NODE_ENV === "production";
const getStyleLoaders = (preProcessor) => {
return [
isProduction ? MiniCssExtractPlugin.loader : "vue-style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
preProcessor && {
loader: preProcessor,
options:
preProcessor === "sass-loader"
? {
// 自定义主题:自动引入我们定义的scss文件
additionalData: `@use "@/styles/element/index.scss" as *;`,
}
: {},
},
].filter(Boolean);
};
module.exports = {
entry: "./src/main.js",
output: {
path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
filename: isProduction
? "static/js/[name].[contenthash:10].js"
: "static/js/[name].js",
chunkFilename: isProduction
? "static/js/[name].[contenthash:10].chunk.js"
: "static/js/[name].chunk.js",
assetModuleFilename: "static/js/[hash:10][ext][query]",
clean: true,
},
module: {
rules: [
{
test: /\.css$/,
use: getStyleLoaders(),
},
{
test: /\.less$/,
use: getStyleLoaders("less-loader"),
},
{
test: /\.s[ac]ss$/,
use: getStyleLoaders("sass-loader"),
},
{
test: /\.styl$/,
use: getStyleLoaders("stylus-loader"),
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",
},
{
test: /\.(jsx|js)$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: [
// "@babel/plugin-transform-runtime" // presets中包含了
],
},
},
// vue-loader不支持oneOf
{
test: /\.vue$/,
loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
options: {
// 开启缓存
cacheDirectory: path.resolve(
__dirname,
"node_modules/.cache/vue-loader"
),
},
},
],
},
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true,
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"),
to: path.resolve(__dirname, "../dist"),
toType: "dir",
noErrorOnMissing: true,
globOptions: {
ignore: ["**/index.html"],
},
info: {
minimized: true,
},
},
],
}),
isProduction &&
new MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:10].css",
chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
}),
new VueLoaderPlugin(),
new DefinePlugin({
__VUE_OPTIONS_API__: "true",
__VUE_PROD_DEVTOOLS__: "false",
}),
// 按需加载element-plus组件样式
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [
ElementPlusResolver({
importStyle: "sass", // 自定义主题
}),
],
}),
].filter(Boolean),
optimization: {
minimize: isProduction,
// 压缩的操作
minimizer: [
new CssMinimizerPlugin(),
new TerserWebpackPlugin(),
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
[
"svgo",
{
plugins: [
"preset-default",
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnsOrder: "alphabetical",
},
},
],
},
],
],
},
},
}),
],
splitChunks: {
chunks: "all",
cacheGroups: {
// layouts通常是admin项目的主体布局组件,所有路由组件都要使用的
// 可以单独打包,从而复用
// 如果项目中没有,请删除
layouts: {
name: "layouts",
test: path.resolve(__dirname, "../src/layouts"),
priority: 40,
},
// 如果项目中使用element-plus,此时将所有node_modules打包在一起,那么打包输出文件会比较大。
// 所以我们将node_modules中比较大的模块单独打包,从而并行加载速度更好
// 如果项目中没有,请删除
elementUI: {
name: "chunk-elementPlus",
test: /[\\/]node_modules[\\/]_?element-plus(.*)/,
priority: 30,
},
// 将vue相关的库单独打包,减少node_modules的chunk体积。
vue: {
name: "vue",
test: /[\\/]node_modules[\\/]vue(.*)[\\/]/,
chunks: "initial",
priority: 20,
},
libs: {
name: "chunk-libs",
test: /[\\/]node_modules[\\/]/,
priority: 10, // 权重最低,优先考虑前面内容
chunks: "initial",
},
},
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`,
},
},
resolve: {
extensions: [".vue", ".js", ".json"],
alias: {
// 路径别名
"@": path.resolve(__dirname, "../src"),
},
},
devServer: {
open: true,
host: "localhost",
port: 3000,
hot: true,
compress: true,
historyApiFallback: true, // 解决vue-router刷新404问题
},
mode: isProduction ? "production" : "development",
devtool: isProduction ? "source-map" : "cheap-module-source-map",
performance: false,
};
本文来自博客园,作者:深巷酒,转载请注明原文链接:https://www.cnblogs.com/huangminghua/p/17140761.html