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 技术实现。详细查看 渐进式网络应用程序
写在后面
实际项目应用:
-
点击查看代码
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, // 关闭性能分析,提示速度 };
-
点击查看代码
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, };