1.初始化安装
#全局安装
npm i webpack webpack-cli -g
#本地安装,两个指令都需要运行
npm i webpack webpack-cli -D
安装完毕后创建src和build两个文件夹分别作为原文件文件夹和打包文件文件夹
创建webpack.config.js文件指示webpack干什么活
2.打包指令
#开发环境
webpack ./src/index.js -o ./bulid/bulit.js --mode=development
#生产环境
webpack ./src/index.js -o ./bulid/bulit.js --mode=production
3.webpack配置文件
//webpack配置文件
const { resolve } = require("path");
//plugin:下载,引入,使用
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
//入口
entry: "./src/index.js",
//输出
output: {
//输出文件名
filename: "built.js",
//输出路径
path: resolve(__dirname, "build"),
},
//loader配置
module: {
rules: [
{
//匹配文件
test: /\.css$/,
//loader执行顺序是从右到左,从下到上的
use: ["style-loader", "css-loader"],
},
//不同文件需要写不同的oader,此处以less举例
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
],
},
//插件
plugins: [
//自动打包引入所有资源
new HtmlWebpackPlugin({
//默认会创建一个空的html,带上template作为模板
template: "./src/index.html",
}),
],
//模式
mode: "development",
//mode:'production
};
4.dev-server
自动化
//自动化
//只会在内存中编译打包,不会有输出
//启动指令:npx webpack serve
devServer: {
//运行目录
static: resolve(__dirname, "bulid"),
//启动gzip压缩
compress: true,
port: 3000,
//自动打开浏览器
open: true,
},
跨域代理
//在devServer中设置proxy
proxy:{
'/api':'http://localhost:9000'
}
模块热替换
//在devServer中设置hot
devServer:{
hot:true
}
//在入口文件中添加调用
if(module.hot){
module.hot.accept('./xxx.js',()=>{
//此处是回调
})
}
5.生产环境(production)
在生产环境下,js会自动压缩
html压缩:在HtmlWebpackPlugin中配置参数
new HtmlWebpackPlugin({
template: "./src/index.html",
//配置
minify: {
//移除空格
collapseWhitespace: true,
//移出注释
removeComments: true,
},
}),
6.assert module
webpack5的新打包资源模块
asset/resource
打包出来展示的是url
//方法一:assetModuleFilename
output: {
//输出文件名
filename: "js/built.js",
//输出路径
path: resolve(__dirname, "build"),
//清理上一次包内容
clean: true,
//设置asset打包文件名
// assetModuleFilename: "image/[contenthash][ext]",
},
//方法二:generator中filename
{
test: /\.(jpg|png|gif)$/,
//新版打包,url-loader和file-loader淘汰了,展示url
type: "asset/resource",
generator: {
filename: "img/[hash:6].[ext]",
},
},
asset/inline
打包出来展示的是base64
{
test: /\.svg$/,
//展示base64
type: "asset/inline",
},
asset/source
导出源码
{
test: /\.txt$/,
//导出源码
type: "asset/source",
}
asset
自动在resource和inline间选择,小于8kb作为inline,大于则视为resouce
可以通过配置调整大小
7.抽离压缩
css
//webpack配置文件
const { resolve } = require("path");
//plugin:下载,引入,使用
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
//入口
entry: "./src/index.js",
//输出
output: {
//输出文件名
filename: "js/built.js",
//输出路径
path: resolve(__dirname, "build"),
//清理上一次包内容
clean: true,
},
//loader配置
module: {
rules: [
{
//匹配文件
test: /\.css$/,
//抽离需要新添加一个loader,并且删除style-loader
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
//插件
plugins: [
//自动打包引入所有资源
new HtmlWebpackPlugin({
//默认会创建一个空的html,带上template作为模板
template: "./src/index.html",
minify: {
//移除空格
collapseWhitespace: true,
//移出注释
removeComments: true,
},
}),
//抽离压缩CSS
new MiniCssExtractPlugin({
//设置文件名
filename: "./css/[contenthash].css",
}),
],
//优化选项
optimization: {
minimizer: [new CssMinimizerWebpackPlugin()],
},
//当进行优化时,需要改为production
mode: "production",
};
js
下载插件
npm i terser-webpack-plugin -D
//生产环境下才能压缩,开发环境不压缩
const Terserwebpackplugin = require("terser-webpack-plugin")
minimizer: [new CssMinimizerWebpackPlugin(), new Terserwebpackplugin()],
8.其他文件类型加载
csv,tsv,xml
toml,yaml,json5
9.ES6转换ES5
10.代码分离
第一种方法(有重复)
会导致公共模块重复
//入口分离
entry: {
index: "./src/index.js",
another: "./src/another.js",
},
output: {
//修改输出文件名
filename: "js/[name].js",
path: resolve(__dirname, "build"),
clean: true,
},
第二种方法(去重复)
去除了重复
index: {
import: "./src/index.js",
dependOn: "shared",
},
another: {
import: "./src/another.js",
dependOn: "shared",
},
//共享模块
shared: "lodash",
第三种方法(去重复)
配置新选项,和方法二类似,自动抽离
entry: {
//第三种方法
index: "./src/index.js",
another: "./src/another.js",
},
optimization: {
splitChunks: {
chunks: "all",
},
},
第四种方法(动态导入)
使用import
如果使用了静态方法,需要将方法三开启。
async-module.js
function getComponet() {
return import("lodash").then(({ default: _ }) => {
const element = document.createElement("div");
element.innerHTML = _.join(["hello", "webpack"], " ");
return element;
});
}
getComponet().then((element) => {
document.body.appendChild(element);
});
entry: {
index: "./src/index.js",
another: "./src/another.js",
},
浏览器懒加载
可以查看浏览器网络,没有点击按钮之前没有发送math.js
math.js
export const add = (x, y) => {
return x + y;
};
export const minus = (x, y) => {
return x - y;
};
index.js
const button = document.createElement("button");
button.textContent = "减法运算";
button.addEventListener("click", () => {
//魔法注释修改打包后的文件名
import(/*webpackChunkName:'math'*/ "./math.js").then(({ minus }) => {
console.log(minus(5, 4));
});
});
document.body.appendChild(button);
浏览器预加载
预加载:webpackPrefetch,在浏览器空闲时间进行加载
还有一个webpackPreload,在父chunk加载时并行加载
//新增魔法注释
import(/*webpackChunkName:'math',webpackPrefetch:true*/ "./math.js").then(({ minus }) => {
console.log(minus(5, 4));
});
11.缓存
将本地文件的文件名添加一个内容hash,第三方文件使用缓存,不更新
output: {
//修改输出文件名
//添加hash
filename: "js/[name][contenthash].js",
path: resolve(__dirname, "build"),
clean: true,
},
splitChunks: {
// chunks: "all",
//添加缓存组
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all",
},
},
},
12.开发环境和生产环境分离
设置公共路径
添加publicPath,在link标签中,会添加访问前缀
环境变量
设置用户参数接收
return后面接正常的配置即可
根据接收设置
使用命令
webpack --env development/production
拆分配置文件
webpack -c ./config/webpack.config.dev.js
webpack -c ./config/webpack.config.prod.js
npm脚本
创建package.json文件(一般来说直接写在package-lock.json中)
{
"scripts": {
"start": "webpack -c ./config/webpack.config.dev.js",
"build": "webpack -c ./config/webpack.config.prod.js"
}
}
npm run start
npm run build
公共配置提取
创建webpack.config.common.js
将两个模式中相同部分进行提取
然后使用webpack-merge进行合并(npm i webpack-merge -D)
在config文件夹中创建webpack.config.js进行配置
//webpack.config.js
const { merge } = require("webpack-merge");
const commonConfig = require("./webpack.config.common");
const prodConfig = require("./webpack.config.prod");
const devConfig = require("./webpack.config.dev");
module.exports = (env) => {
switch (true) {
case env.development:
return merge(commonConfig, devConfig);
case env.production:
return merge(commonConfig, prodConfig);
default:
return new Error("没有匹配到config");
}
};
//修改脚本文件
{
"scripts": {
"start": "webpack -c ./config/webpack.config.js --env development",
"build": "webpack -c ./config/webpack.config.js --env production"
}
}
13.模块解析
//resolve
const path = require('path')
resolve{
//alias起别名,可以使用别名代替路径
alias:{
'@':path.resolve(__dirname,'./src')
},
//extensions,自动识别扩展名,优先级为从左到右
extensions:['.json','.js','.vue']
}
14.外部扩展
当要使用第三方扩展时(不下载到本地),需要使用external
externalType:'script'
external:{
jquery:[
//三方链接
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js',
//在浏览器上暴露的对象
'$'
]
}
15.多页面
entry: {
main: {
import: ["./src/app.js", "./src/app2.js"],
filename: "index1/[name].js",
dependOn: "common",
},
main2: {
import: ["./src/app3.js"],
filename: "index2/[name].js",
dependOn: "common",
},
common: {
import: "lodash",
filename: "common/[name].js",
},
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
//设置script标签在body还是head
inject: "body",
filename: "index1/index.html",
}),
new HtmlWebpackPlugin({
template: "./src/index2.html",
inject: "head",
filename: "index2/index2.html",
}),
],
16.全局变量
//设置全局变量,在入口文件中就不需要进行导包了
const webpack = require('webpack')
plugins:[
new webpack.ProvidePlugin({
_:'lodash'
})
]
17.修改this指向
npm i imports-loader -D
module:{
rules:[
{
test:require.resolve('./src/index.js'),
use:'import-loader?wrapper=window'
}
]
}
18.模块化使用(模块联邦)
//在每一个模块中声明,不同模块名字路径不同
const { ModuleFederationPlugin } = require("webpack").container;
plugins: [
new HtmlWbepackPlugin(),
new ModuleFederationPlugin({
name: "header",
filename: "remoteEntry.js",
//使用其他暴露组件
remotes: {},
//暴露的组件
exposes: {
"./Header": "./src/Header.js",
},
//共享第三方模块
shared: {},
}),
],
//在引用模块中使用:
new ModuleFederationPlugin({
name: "home",
filename: "remoteEntry.js",
//使用其他暴露组件
remotes: {
header: "header@http://127.0.0.1:8080/remoteEntry.js",
footer: "footer@http://127.0.0.1:8081/remoteEntry.js",
},
//暴露的组件
exposes: {
"./home": "./src/home.js",
},
//共享第三方模块
shared: {},
}),
//引用模块配置
import Home from "./home";
// import("header/Header").then((Header) => {
// const div = document.createElement("div");
// document.body.appendChild(Header.default());
// div.appendChild(Home());
// document.body.appendChild(div);
// });
Promise.all([import("header/Header"), import("footer/Footer")]).then(
([{ default: Header }, { default: Footer }]) => {
document.body.appendChild(Header());
const div = document.createElement("div");
div.appendChild(Home());
document.body.appendChild(div);
document.body.appendChild(Footer());
}
);
19.SourceMap
运行打包后项目,报错时提示的错误位置有误,是编译后的位置
解决:使用devtool
//开发模式:
module.exports = {
//其他省略
mode:"development",
//优点:打包编译速度快,只包含行映射
//缺点:没有列映射
devtool:"cheap-module-source-map",
};
//生产模式
module.exports = {
//其他省略
mode:"production",
//优点:包含行列映射
//缺点:打包编译速度慢
devtool:"source-map",
};
20.oneOf
用于module,使用了oneOf包裹的loader,每个文件只能被其中一个loader配置处理
提升构建速度
module.exports = {
//loader配置
module: {
rules: [
//oneOf
{oneOf:[
{
//匹配文件
test: /\.css$/,
//loader执行顺序是从右到左,从下到上的
use: ["style-loader", "css-loader"],
},
//不同文件需要写不同的oader,此处以less举例
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},]
},
//处理图片
{
test: /\.(jpg|png|gif)$/,
loader: "url-loader",
options: {
limit: 8 * 1024,
name: "[hash:5].[ext]",
outputPath: "imgs",
},
},
{
test: /\.html$/,
loader: "html-loader",
},
//处理html中图片
],
},
};
21.多进程打包
仅在特别耗时的操作中使用,每个进程启动也需要耗时间
使用thread-loader
const os = require("os");
const threads = os.cpus().length; //cpu核数
module: {
rules: [
{
//匹配文件
test: /\.js/,
use: [
//放在需要多线程打包loader的前面
{
loader:"thread-loader",//开启多进程
options{
works:threads,//进程数量
}
},
{
loader:"babel-loader",
options{
cacheDirectory:true,//开启babel缓存
cacheCompression:false,//关闭缓存文件压缩
}
}
],
},
optimization: {
minimizer: [new TerserWebpackPlugin({//压缩js
parallel:threads,//开启多进程和进程数
})],
},
],
},
22.减少Babel生成文件体积
使用插件@babel/plugin-transform-runtime
在babel-loader的option中添加plugins:[“@babel/plugin-transform-runtime”]
23.runtimeChunk
设置runtimeChunk是将包含chunks 映射关系
的 list单独从 app.js里提取出来,因为每一个 chunk 的 id 基本都是基于内容 hash 出来的,所以每次改动都会影响它,如果不将它提取出来的话,等于app.js每次都会改变。缓存就失效了。设置runtimeChunk之后,webpack就会生成一个个runtime~xxx.js的文件。
然后每次更改所谓的运行时代码文件时,打包构建时app.js的hash值是不会改变的。如果每次项目更新都会更改app.js的hash值,那么用户端浏览器每次都需要重新加载变化的app.js,如果项目大切优化分包没做好的话会导致第一次加载很耗时,导致用户体验变差。现在设置了runtimeChunk,就解决了这样的问题。所以这样做的目的是避免文件的频繁变更导致浏览器缓存失效,所以其是更好的利用缓存。提升用户体验。
optimization:{
runtimeChunk:{
name:(entrypoint) => `runtime~${entrypoint.name}.js`,
}
}
24.解决js兼容问题
解决promise等语法的兼容
使用core-js
npm i core-js
import 'core-js'
也可以按需加载
import 'core-js/es/promise'
也可以babel只能识别按需加载
babel.config.js
优化总结
基本配置
version1
//webpack配置文件
const { resolve } = require("path");
//plugin:下载,引入,使用
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
//入口
entry: "./src/index.js",
//输出
output: {
//输出文件名
filename: "js/built.js",
//输出路径
path: resolve(__dirname, "build"),
},
//loader配置
module: {
rules: [
{
//匹配文件
test: /\.css$/,
//loader执行顺序是从右到左,从下到上的
use: ["style-loader", "css-loader"],
},
//不同文件需要写不同的oader,此处以less举例
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
//处理图片
{
test: /\.(jpg|png|gif)$/,
loader: "url-loader",
options: {
limit: 8 * 1024,
name: "[hash:5].[ext]",
outputPath: "imgs",
},
},
{
test: /\.html$/,
loader: "html-loader",
},
//处理html中图片
],
},
//插件
plugins: [
//自动打包引入所有资源
new HtmlWebpackPlugin({
//默认会创建一个空的html,带上template作为模板
template: "./src/index.html",
}),
],
//模式
mode: "development",
//mode:'production
//自动化
//只会在内存中编译打包,不会有输出
//启动指令:npx webpack serve
devServer: {
//运行目录
static: resolve(__dirname, "bulid"),
//启动gzip压缩
compress: true,
port: 3000,
//自动打开浏览器
open: true,
},
};
version2
//webpack配置文件
const { resolve } = require("path");
//plugin:下载,引入,使用
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
//入口
entry: "./src/index.js",
//输出
output: {
//输出文件名
filename: "js/built.js",
//输出路径
path: resolve(__dirname, "build"),
},
//loader配置
module: {
rules: [
{
//匹配文件
test: /\.css$/,
//loader执行顺序是从右到左,从下到上的
use: ["style-loader", "css-loader"],
},
//不同文件需要写不同的oader,此处以less举例
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
//处理图片
{
test: /\.(jpg|png|gif)$/,
//新版打包,url-loader和file-loader淘汰了
type: "asset/resource",
generator: {
filename: "img/[hash:6].[ext]",
},
},
],
},
//插件
plugins: [
//自动打包引入所有资源
new HtmlWebpackPlugin({
//默认会创建一个空的html,带上template作为模板
template: "./src/index.html",
minify: {
//移除空格
collapseWhitespace: true,
//移出注释
removeComments: true,
},
}),
],
//模式
mode: "development",
//mode:'production
//自动化
//只会在内存中编译打包,不会有输出
//启动指令:npx webpack serve
devServer: {
//运行目录
static: resolve(__dirname, "bulid"),
//启动gzip压缩
compress: true,
port: 3000,
//自动打开浏览器
open: true,
},
};
React脚手架配置
开发模式
// webpack.dev.js
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const getStyleLoaders = (preProcessor) => {
return [
"style-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].js",
chunkFilename: "static/js/[name].chunk.js",
assetModuleFilename: "static/js/[hash:10][ext][query]",
},
module: {
rules: [
{
oneOf: [
{
// 用来匹配 .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中包含了
"react-refresh/babel", // 开启js的HMR功能
],
},
},
],
},
],
},
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 ReactRefreshWebpackPlugin(), // 解决js的HMR功能运行时全局变量的问题
// 将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,
},
},
],
}),
],
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`,
},
},
resolve: {
extensions: [".jsx", ".js", ".json"], // 自动补全文件扩展名,让jsx可以使用
},
devServer: {
open: true,
host: "localhost",
port: 3000,
hot: true,
compress: true,
historyApiFallback: true, // 解决react-router刷新404问题
},
mode: "development",
devtool: "cheap-module-source-map",
};
生产模式
// 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 TerserWebpackPlugin = require("terser-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
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: path.resolve(__dirname, "../dist"),
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: [
{
oneOf: [
{
// 用来匹配 .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中包含了
],
},
},
],
},
],
},
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 MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:10].css",
chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
}),
// 将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,
},
},
],
}),
],
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: [".jsx", ".js", ".json"],
},
mode: "production",
devtool: "source-map",
};
其他配置
- package.json
{
"name": "react-cli",
"version": "1.0.0",
"description": "",
"main": "index.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",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
"babel-loader": "^8.2.5",
"babel-preset-react-app": "^10.0.1",
"copy-webpack-plugin": "^10.2.4",
"cross-env": "^7.0.3",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1",
"eslint-config-react-app": "^7.0.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-loader": "^6.2.1",
"postcss-preset-env": "^7.5.0",
"react-refresh": "^0.13.0",
"sass-loader": "^12.6.0",
"style-loader": "^3.3.1",
"stylus-loader": "^6.2.0",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.0"
},
"dependencies": {
"antd": "^4.20.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-router-dom": "^6.3.0"
},
"browserslist": ["last 2 version", "> 1%", "not dead"]
}
- .eslintrc.js
module.exports = {
extends: ["react-app"], // 继承 react 官方规则
parserOptions: {
babelOptions: {
presets: [
// 解决页面报错问题
["babel-preset-react-app", false],
"babel-preset-react-app/prod",
],
},
},
};
- babel.config.js
module.exports = {
// 使用react官方规则
presets: ["react-app"],
};
合并开发和生产配置
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");
// 需要通过 cross-env 定义环境变量
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,
].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: [
{
// 用来匹配 .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, // 开启babel编译缓存
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(),
].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",
// 其他都用默认值
},
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",
};
- 修改运行指令 package.json
{
"name": "react-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.17.10",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
"babel-loader": "^8.2.5",
"babel-preset-react-app": "^10.0.1",
"cross-env": "^7.0.3",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1",
"eslint-config-react-app": "^7.0.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",
"react-refresh": "^0.13.0",
"sass-loader": "^12.6.0",
"style-loader": "^3.3.1",
"stylus-loader": "^6.2.0",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.0"
},
"dependencies": {
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-router-dom": "^6.3.0"
},
"browserslist": ["last 2 version", "> 1%", "not dead"]
}
优化配置
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脚手架配置
开发模式配置
// webpack.dev.js
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
const { DefinePlugin } = require("webpack");
const CopyPlugin = require("copy-webpack-plugin");
const getStyleLoaders = (preProcessor) => {
return [
"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: undefined,
filename: "static/js/[name].js",
chunkFilename: "static/js/[name].chunk.js",
assetModuleFilename: "static/js/[hash:10][ext][query]",
},
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 VueLoaderPlugin(),
// 解决页面警告
new DefinePlugin({
__VUE_OPTIONS_API__: "true",
__VUE_PROD_DEVTOOLS__: "false",
}),
],
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`,
},
},
resolve: {
extensions: [".vue", ".js", ".json"], // 自动补全文件扩展名,让vue可以使用
},
devServer: {
open: true,
host: "localhost",
port: 3000,
hot: true,
compress: true,
historyApiFallback: true, // 解决vue-router刷新404问题
},
mode: "development",
devtool: "cheap-module-source-map",
};
生产模式配置
// 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",
};
其他配置
- package.json
{
"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"],
};
合并开发和生产配置
// 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,
};