// webpack.dev
const path = require(;path;)
const { DefinePlugin } = require(;webpack;)
const EslintWebpackPlugin = require(;eslint-webpack-plugin;)
const HtmlWebpackPlugin = require(;html-webpack-plugin;)
const { VueLoaderPlugin } = require(;vue-loader;)
// 返回处理样式loader函数
const getStyleLoaders = (pre) => {
return [
;vue-style-loader;,
;css-loader;,
{
// 处理css兼容性问题
// 配合package.json中browserslist来指定兼容性
loader: ;postcss-loader;,
options: {
postcssOptions: {
plugins: [;postcss-preset-env;],
},
},
},
pre,
].filter(Boolean)
}
module.exports = {
entry: ;./src/main.js;,
output: {
path: undefined,
filename: ;static/js/[name].js;,
chunkFilename: ;static/js/[name].chunk.js;,
assetModuleFilename: ;static/media/[hash:10][ext][query];,
},
module: {
rules: [
// 处理css
{
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: /.(jpe?g|png|gif|webp|svg)$/,
type: ;asset;,
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
// 处理其他资源
{
test: /.(woff2?|ttf)$/,
type: ;asset/resource;,
},
// 处理js
{
test: /.js$/,
include: path.resolve(__dirname, ;../src;),
loader: ;babel-loader;,
options: {
cacheDirectory: true,
cacheCompression: false,
},
},
// 处理vue
{
test: /.vue$/,
loader: ;vue-loader;,
},
],
},
// 处理html
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 VueLoaderPlugin(),
// cross-env定义的环境变量给打包工具使用
// DefinePlugin定义环境变量给源代码使用;从而解决vue3页面警告的问题
new DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
}),
],
mode: ;development;,
devtool: ;cheap-module-source-map;,
optimization: {
splitChunks: {
chunks: ;all;,
},
runtimeChunk: {
name: (entrypoint) => ;runtime~${entrypoint.name}.js;,
},
},
// webpack解析模块加载选项
resolve: {
// 自动补全文件扩展名
extensions: [;.vue;, ;.js;, ;.json;],
},
devServer: {
host: ;localhost;,
port: 3000,
open: true,
hot: true, // 开启HMR
historyApiFallback: true, // 解决前端路由刷新404问题
},
}
// webpack.prod.js
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 { VueLoaderPlugin } = require(;vue-loader;);
const { DefinePlugin } = require(;webpack;);
const getStyleLoaders = (preProcessor) => {
return [
MiniCssExtractPlugin.loader,
;css-loader;,
{
loader: ;postcss-loader;,
options: {
postcssOptions: {
plugins: [
;postcss-preset-env;, // 能解决大多数样式兼容性问题
],
},
},
},
preProcessor,
].filter(Boolean);
};
module.exports = {
entry: ;./src/main.js;,
output: {
path: undefined,
filename: ;static/js/[name].[contenthash:10].js;,
chunkFilename: ;static/js/[name].[contenthash:10].chunk.js;,
assetModuleFilename: ;static/js/[hash:10][ext][query];,
clean: true,
},
module: {
rules: [
{
// 用来匹配 .css 结尾的文件
test: /.css$/,
// use 数组里面 Loader 执行顺序是从右到左
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, // 小于10kb的图片会被base64处理
},
},
},
{
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,
},
},
],
}),
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;,
}),
],
optimization: {
// 压缩的操作
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;,
},
runtimeChunk: {
name: (entrypoint) => ;runtime~${entrypoint.name};,
},
},
resolve: {
extensions: [;.vue;, ;.js;, ;.json;],
},
mode: ;production;,
devtool: ;source-map;,
};
{
;name;: ;vue-cli;,
;version;: ;1.0.0;,
;description;: ;;,
;main;: ;main.js;,
;scripts;: {
;start;: ;npm run dev;,
;dev;: ;cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js;,
;build;: ;cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js;
},
;keywords;: [],
;author;: ;;,
;license;: ;ISC;,
;devDependencies;: {
;;babel/core;: ;^7.17.10;,
;;babel/eslint-parser;: ;^7.17.0;,
;;vue/cli-plugin-babel;: ;^5.0.4;,
;babel-loader;: ;^8.2.5;,
;copy-webpack-plugin;: ;^10.2.4;,
;cross-env;: ;^7.0.3;,
;css-loader;: ;^6.7.1;,
;css-minimizer-webpack-plugin;: ;^3.4.1;,
;eslint-plugin-vue;: ;^8.7.1;,
;eslint-webpack-plugin;: ;^3.1.1;,
;html-webpack-plugin;: ;^5.5.0;,
;image-minimizer-webpack-plugin;: ;^3.2.3;,
;imagemin;: ;^8.0.1;,
;imagemin-gifsicle;: ;^7.0.0;,
;imagemin-jpegtran;: ;^7.0.0;,
;imagemin-optipng;: ;^8.0.0;,
;imagemin-svgo;: ;^10.0.1;,
;less-loader;: ;^10.2.0;,
;mini-css-extract-plugin;: ;^2.6.0;,
;postcss-preset-env;: ;^7.5.0;,
;sass-loader;: ;^12.6.0;,
;stylus-loader;: ;^6.2.0;,
;vue-loader;: ;^17.0.0;,
;vue-style-loader;: ;^4.1.3;,
;vue-template-compiler;: ;^2.6.14;,
;webpack;: ;^5.72.0;,
;webpack-cli;: ;^4.9.2;,
;webpack-dev-server;: ;^4.9.0;
},
;dependencies;: {
;vue;: ;^3.2.33;,
;vue-router;: ;^4.0.15;
},
;browserslist;: [;last 2 version;, ;> 1%, ;not dead;]
}
.eslintrc.js
module.exports = {
root: true,
env: {
node: true,
},
extends: [;plugin:vue/vue3-essential;, ;eslint:recommended;],
parserOptions: {
parser: ;;babel/eslint-parser;,
},
};
babel.config.js
module.exports = {
presets: [;;vue/cli-plugin-babel/preset;],
};
为了提高代码复用性;我们将开发和生产模式两个代码合并;合并的核心在于通过process.env.NODE_ENV判断处于什么模式
// webpack.config.js
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 { VueLoaderPlugin } = require(;vue-loader;);
const { DefinePlugin } = require(;webpack;);
const CopyPlugin = require(;copy-webpack-plugin;);
// 需要通过 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,
].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: [
{
// 用来匹配 .css 结尾的文件
test: /.css$/,
// use 数组里面 Loader 执行顺序是从右到左
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, // 小于10kb的图片会被base64处理
},
},
},
{
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;,
}),
].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;,
},
runtimeChunk: {
name: (entrypoint) => ;runtime~${entrypoint.name};,
},
},
resolve: {
extensions: [;.vue;, ;.js;, ;.json;],
},
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;,
};
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,
};