一直以来都是使用脚手架创建应用,现在有空认真研究了一下webpack5,从零开始搭建项目,受益颇多,记下心得,以供参考,生产环境请参考生产环境配置。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="favicon.ico" ref="shortcut" type="image/x-iocn" />
<title>Webpack React Clititle>
head>
<body>
<div id="app">div>
body>
html>
// main.js
import React from "react";
import ReactDom from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
const root = ReactDom.createRoot(document.getElementById("app"));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
// App.jsx
import React, { lazy, Suspense } from "react";
import { Link, Route, Routes } from "react-router-dom";
// 路由懒加载
const Home = lazy(() => import(/* webpackChunkName: 'home' */ "./pages/Home"));
const About = lazy(() => import(/* webpackChunkName: 'about' */"./pages/About"));
function App() {
return (
-
home
-
about
loading }>
}>
}>
在这里把所有需要的依赖全部安装一下
{
"name": "webpack_react_cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development webpack server --config ./config/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
],
"devDependencies": {
"@babel/core": "^7.21.5",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"babel-loader": "^9.1.2",
"babel-preset-react-app": "^10.0.1",
"copy-webpack-plugin": "^11.0.0",
"cross-env": "^7.0.3",
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^5.0.0",
"eslint": "^8.39.0",
"eslint-config-react-app": "^7.0.1",
"eslint-webpack-plugin": "^4.0.1",
"html-webpack-plugin": "^5.5.1",
"image-minimizer-webpack-plugin": "^3.8.2",
"imagemin-gifsicle": "^7.0.0",
"imagemin-jpegtran": "^7.0.0",
"imagemin-optipng": "^8.0.0",
"imagemin-svgo": "^10.0.1",
"less-loader": "^11.1.0",
"mini-css-extract-plugin": "^2.7.5",
"postcss-loader": "^7.3.0",
"postcss-preset-env": "^8.3.2",
"progress-bar-webpack-plugin": "^2.1.0",
"react-refresh": "^0.14.0",
"sass-loader": "^13.2.2",
"style-loader": "^3.3.2",
"terser-webpack-plugin": "^5.3.7",
"thread-loader": "^4.0.1",
"webpack": "^5.81.0",
"webpack-cli": "^5.0.2",
"webpack-dev-server": "^4.13.3",
"webpackbar": "^5.0.2",
"workbox-webpack-plugin": "^6.5.4"
},
"dependencies": {
"antd": "^5.4.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.0"
},
"resolutions": {
"//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it",
"bin-wrapper": "npm:bin-wrapper-china",
"rollup": "^2.72.0"
}
}
通过entry节点指定打包的入口,通过output节点指定打包的出口,配置模式mode,自动解析文件类型等。
// webpack.dev.js
module.exports = {
entry: "./src/main.js",
// 开发服务器不会输出资源,在内存中编译打包
output: {
filename: "static/js/[name].js",
chunkFilename: "static/js/[name].chunk.js",
assetModuleFilename: "static/media/[hash:10][ext][query]",
},
mode: "development",
devServer: {
open: true, // 是否自动打开浏览器
port: 3000, // 启动服务器端口
host: "localhost", // 启动服务器域名
// 模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
// 保留在完全重新加载页面期间丢失的应用程序状态。
// 只更新变更内容,以节省宝贵的开发时间。
// 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
hot: true,
// 解决开发时刷新404
// 当使用HTML5 History API时,可能必须使用index.html页面来代替任何404响应。通过将devServer.historyPiFallback设置为true来启用它
historyApiFallback: true,
},
devtool: "source-map",
resolve: {
// 自动解析文件扩展名
extensions: [
".web.mjs",".mjs",".web.js",".js",".web.ts",".ts" ,".web.tsx",".tsx",".json",".web.jsx",".jsx"
],
},
}
loader 从右到左(或从下到上)的解析执行,所以写法要谨慎。
// babel.config.js
module.exports = {
// 智能预设,能够编译es6语法
// https://github.com/facebook/create-react-app/blob/main/packages/babel-preset-react-app/create.js
presets: [
"react-app"
],
"compact": true
};
// webpack.dev.js
const path = require("path");
function getStyLoader(pre) {
return [
"style-loader",
{
loader: "css-loader",
options: {
// 开启模块化
modules: {
mode: "local",
// 自动匹配module.css开启模块化
auto: true,
localIdentName: "[path][name]__[local]--[hash:5]",
exportLocalsConvention: "camelCase",
},
},
},
// 处理兼容性,配合package.json中的browserslist使用
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
{
// 其他选项
},
],
],
},
},
},
pre,
].filter(Boolean); // 过滤掉underfind
}
module.exports = {
module: {
rules: [
// 处理css
{
test: /\.css$/i,
use: getStyLoader(),
},
{
test: /\.less$/i,
use: getStyLoader("less-loader"),
},
{
test: /\.s[ac]ss$/i,
use: getStyLoader("sass-loader"),
},
// 处理图片
{
test: /\.(png|jpe?g|gif|webp)$/i,
// asset可以转换base64
type: "asset",
parser: {
dataUrlCondition: {
// 小于4kb转化成base64.减少请求,资源会变大一些
maxSize: 4 * 1024, // 4kb
},
},
generator: {
// :8,hash钱8位
filename: "static/images/[hash:8][ext]",
},
},
{
test: /\.(ttf|mp4)$/i,
// asset/resource 原封不动输出
type: "asset/resource",
// generator: {
// // :8,hash钱8位
// filename: "static/fonts/[hash:8][ext]",
// },
},
// 处理js
{
test: /\.jsx?$/,
// exclude: /(node_modules)/, // 排除的文件
include: [path.resolve(__dirname, "../src")],
use: [
{
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启缓存
cacheCompression: false, // 关闭缓存压缩
plugins: ["react-refresh/babel"], // HMR js
},
},
],
},
],
},
};
// .eslintrc.js
module.exports = {
extends: ["react-app"], // 继承 react 官方规则
parserOptions: {
babelOptions: {
presets: [
// 解决页面报错问题
["babel-preset-react-app", false],
"babel-preset-react-app/prod",
],
},
},
};
// webpack.dev.js
const path = require("path");
// eslint检验
const ESLintPlugin = require("eslint-webpack-plugin");
// html模板
const HtmlWebpackPlugin = require("html-webpack-plugin");
// HMR
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
module.exports = {
// 配置plugins
plugins: [
new ESLintPlugin({
// 检查哪些文件
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true, // 开启缓存
// 缓存目录
cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslint"),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
// HMR js
new ReactRefreshWebpackPlugin(),
],
optimization: {
// 代码分割
splitChunks: {
// include all types of chunks
chunks: "all",
cacheGroups: {
// react react-dom react-router-dom 一起打包成一个js文件
react: {
test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
name: "chunk-react",
priority: 40,
},
// antd 单独打包
antd: {
test: /[\\/]node_modules[\\/]antd[\\/]/,
name: "chunk-antd",
priority: 30,
},
// 剩下node_modules单独打包
libs: {
test: /[\\/]node_modules[\\/]/,
name: "chunk-libs",
priority: 20,
},
},
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`,
},
},
};
// webpack.dev.js
const path = require("path");
// eslint检验
const ESLintPlugin = require("eslint-webpack-plugin");
// html模板
const HtmlWebpackPlugin = require("html-webpack-plugin");
// HMR
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
function getStyLoader(pre) {
return [
"style-loader",
{
loader: "css-loader",
options: {
modules: {
mode: "local",
auto: true,
localIdentName: "[path][name]__[local]--[hash:5]",
exportLocalsConvention: "camelCase",
},
},
},
// 处理兼容性,配合package.json中的browserslist使用
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
{
// 其他选项
},
],
],
},
},
},
pre,
].filter(Boolean); // 过滤掉underfind
}
module.exports = {
entry: "./src/main.js",
// 开发服务器不会输出资源,在内存中编译打包
output: {
filename: "static/js/[name].js",
chunkFilename: "static/js/[name].chunk.js",
assetModuleFilename: "static/media/[hash:10][ext][query]",
},
mode: "development",
devServer: {
open: true, // 是否自动打开浏览器
port: 3000, // 启动服务器端口
host: "localhost", // 启动服务器域名
// 模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
// 保留在完全重新加载页面期间丢失的应用程序状态。
// 只更新变更内容,以节省宝贵的开发时间。
// 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
hot: true,
// 解决开发时刷新404
// 当使用HTML5 History API时,可能必须使用index.html页面来代替任何404响应。通过将devServer.historyPiFallback设置为true来启用它
historyApiFallback: true,
},
devtool: "source-map",
resolve: {
// 自动解析文件扩展名
extensions: [
".web.mjs",
".mjs",
".web.js",
".js",
".web.ts",
".ts",
".web.tsx",
".tsx",
".json",
".web.jsx",
".jsx",
],
},
module: {
rules: [
// 处理css
{
test: /\.css$/i,
use: getStyLoader(),
},
{
test: /\.less$/i,
use: getStyLoader("less-loader"),
},
{
test: /\.s[ac]ss$/i,
use: getStyLoader("sass-loader"),
},
// 处理图片
{
test: /\.(png|jpe?g|gif|webp)$/i,
// asset可以转换base64
type: "asset",
parser: {
dataUrlCondition: {
// 小于4kb转化成base64.减少请求,资源会变大一些
maxSize: 4 * 1024, // 4kb
},
},
generator: {
// :8,hash钱8位
filename: "static/images/[hash:8][ext]",
},
},
{
test: /\.(ttf|mp4)$/i,
// asset/resource 原封不动输出
type: "asset/resource",
// generator: {
// // :8,hash钱8位
// filename: "static/fonts/[hash:8][ext]",
// },
},
// 处理js
{
test: /\.jsx?$/,
// exclude: /(node_modules)/, // 排除的文件
include: [path.resolve(__dirname, "../src")],
use: [
{
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启缓存
cacheCompression: false, // 关闭缓存压缩
plugins: ["react-refresh/babel"], // HMR js
},
},
],
},
],
},
//
plugins: [
new ESLintPlugin({
// 检查哪些文件
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true, // 开启缓存
// 缓存目录
cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslint"),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
// HMR js
new ReactRefreshWebpackPlugin(),
],
optimization: {
// 代码分割
splitChunks: {
// include all types of chunks
chunks: "all",
cacheGroups: {
// react react-dom react-router-dom 一起打包成一个js文件
react: {
test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
name: "chunk-react",
priority: 40,
},
// antd 单独打包
antd: {
test: /[\\/]node_modules[\\/]antd[\\/]/,
name: "chunk-antd",
priority: 30,
},
// 剩下node_modules单独打包
libs: {
test: /[\\/]node_modules[\\/]/,
name: "chunk-libs",
priority: 20,
},
},
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`,
},
},
};