webpack.config.js
const path = require('path');
const webpack = require('webpack');
const { VueLoaderPlugin } = require("vue-loader")
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const { ModuleFederationPlugin } = require('webpack').container;
const isProdEnv = process.env.NODE_ENV === "production";
const publicPath = isProdEnv ? "/test/" : '/';
module.exports = {
stats: {
// 禁用所有警告
warnings: false
},
mode: isProdEnv ? 'production' : "development",
entry: path.resolve(__dirname, 'src/main.js'),
output: {
publicPath: publicPath,
// 打包产物的输出目录
path: path.join(__dirname,"dist"),
filename: 'static/js/[name].[contenthash:8].js',
// 清空产物文件夹
clean: true,
},
devtool: isProdEnv ? false : 'eval-cheap-source-map',
devServer: {
port: 8080,
open: true,
hot: true,
proxy: [
{
context: '/api',
target: 'http://127.0.0.1:9000',
headers: {
Referer: 'http://127.0.0.1:9000',
},
secure: false,
changeOrigin: true,
pathRewrite: {
'^/api': '/api'
}
}
],
headers: {
'Access-Control-Allow-Origin': '*',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Resource-Policy': 'cross-origin'
},
historyApiFallback: true,
client: {
overlay: {
warnings: false,
errors: false,
},
},
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
title: '标题',
template: './public/index.html',
// 自定义属性
prodPublicPath: publicPath,
}),
// 用于分析 Webpack 打包后的文件体积和依赖关系
new BundleAnalyzerPlugin({
// 禁用默认启动,手动触发
// server, static, json, disabled
analyzerMode: "disabled",
// 是否自动打开浏览器
openAnalyzer: false,
analyzerPort: 8888,
analyzerHost: "127.0.0.1",
defaultSizes: "parsed",
reportFilename: "report.html",
// 是否生成 stats.json 文件
generateStatsFile: false,
statsFilename: "stats.json",
// 控制 stats 文件的输出内容
statsOptions: {
source: false
},
logLevel: "info",
// 排除 .map 文件
excludeAssets: /\.map$/,
}),
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, `/public/favicon.ico`),
to: 'favicon.ico',
},
]
}),
new webpack.DefinePlugin({
// Vue 3 必需的定义
__VUE_OPTIONS_API__: JSON.stringify(true),
__VUE_PROD_DEVTOOLS__: JSON.stringify(false),
// 环境变量定义
'process.env': JSON.stringify({
BASE_URL: publicPath,
NODE_ENV: process.env.NODE_ENV === 'production' ? 'production' : 'development',
})
}),
new ModuleFederationPlugin({
shared: {
'vue': {
singleton: true,
requiredVersion: '^3.2.40',
eager: true
},
'vue-router': {
singleton: true,
requiredVersion: '^4.0.12',
eager: true
},
'vuex': {
requiredVersion: '^4.0.2',
eager: true
},
'axios': {
singleton: true,
requiredVersion: '^1.12.0',
eager: true
},
'vue-i18n': {
requiredVersion: '^9.1.7',
eager: true
},
'echarts': {
requiredVersion: '^5.4.0',
eager: true
},
}
})
],
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
exclude: /node_modules/
},
{
test: /\.(js|mjs|jsx|cjs)$/i,
exclude: /node_modules/,
include: [path.resolve(__dirname, 'src'), path.resolve(__dirname, "../public")],
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
['@vue/babel-plugin-jsx', { compositionAPI: true }]
]
}
},
generator: {
filename: 'static/js/[name].[hash:8][ext]'
}
},
// 处理css资源
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
generator: {
filename: 'static/css/[name].[hash:8][ext]'
}
},
// 处理图片资源
{
test: /\.(png|jpg|svg|jpeg|gif)$/i,
type: 'asset/resource',
generator: {
filename: 'static/img/[name].[hash:8][ext]'
}
},
// 样式文件规则:重点在这里
{
test: /\.module\.less$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
// Vue CLI 默认使用
auto: true,
localIdentName: '[name]_[local]_[hash:base64:5]',
},
importLoaders: 2,
sourceMap: true,
esModule: false, // 关键:让 default export 为 locals
},
},
{
loader: 'less-loader',
options: {
sourceMap: true,
}
},
],
},
{
test: /\.less$/,
exclude: /\.module\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'less-loader',
options: {
sourceMap: true,
additionalData: `
@isDev: ${process.env.NODE_ENV === 'development'};
lessOptions: {
modifyVars: {
"font-size-base": "12px",
},
javascriptEnabled: true,
},
}
}
],
generator: {
filename: 'static/css/[name].[hash:8][ext]'
}
}, {
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use: {
loader: 'file-loader',
options: {
name: 'static/fonts/[name].[ext]',
esModule: false
}
},
generator: {
filename: 'static/fonts/[name].[hash:8][ext]'
}
}
]
},
resolve: {
// 指定模块导入时,可省略的扩展名
extensions: ['.js', '.vue', '.jsx', '.json'],
// 路径别名
alias: {
'vue$': 'vue/dist/vue.esm-bundler.js',
'@': path.join(__dirname, 'src'),
},
modules: [path.resolve(__dirname, "node_modules"), path.resolve(__dirname, "../node_modules")],
},
optimization: {
// 提取 webpack runtime 到一个单独的 chunk
runtimeChunk: 'single',
// 代码分割配置
splitChunks: {
chunks: 'async',
cacheGroups: {
// 异步导入的图标组件打包成一个js
asyncComponents: {
test: /[\\/]public[\\/]components[\\/]IconSvg[\\/]/,
name: 'IconSvg-AsyncComponents',
minChunks: 1,
priority: 20,
chunks: 'all',
reuseExistingChunk: true,
enforce: true,
}
}
},
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
// 可选:移除 console
// drop_console: true,
},
},
// 打包产物不提取注释到 .LICENSE.txt
extractComments: false,
}),
],
},
}
package.json
{
"name": "test",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "cross-env NODE_ENV=development node --max-old-space-size=2048 ./node_modules/webpack/bin/webpack.js serve",
"build": "cross-env NODE_ENV=production node --max-old-space-size=4096 ./node_modules/webpack/bin/webpack.js"
},
"dependencies": {},
"devDependencies": {
"@babel/core": "^7.28.5",
"@babel/preset-env": "^7.28.5",
"@vue/babel-plugin-jsx": "^2.0.1",
"babel-loader": "^10.0.0",
"copy-webpack-plugin": "^12.0.2",
"cross-env": "^10.1.0",
"css-loader": "^7.1.2",
"eslint": "^8.57.1",
"eslint-plugin-vue": "^10.5.1",
"html-webpack-plugin": "^5.6.3",
"less": "^4.5.1",
"less-loader": "^12.3.0",
"style-loader": "^4.0.0",
"terser-webpack-plugin": "^5.3.16",
"vue-loader": "^17.4.2",
"webpack": "^5.99.9",
"webpack-bundle-analyzer": "^4.10.2",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"Chrome >= 78"
]
}