手动搭建并配置react项目(webpack5)
介绍
不使用脚手架,利用webpack,手动搭建react项目框架
1、项目创建
创建目录 react_wepack
2、webpack+ react基础架构
2.1 配置 webpack.dev.js
配置 loader、plugin、eslint【见webpack.dev.js】
javascript
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ESLintPlugin = require('eslint-webpack-plugin');
const { DefinePlugin } = require('webpack');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
// 用来处理获取的样式
function getStyleLoaders(pre) {
return [MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
// plugins: [["autoprefixer"]],
plugins: ['postcss-preset-env'],//能解决大多数兼容性问题
},
},
}, pre].filter(Boolean);
}
module.exports = {
mode: "development", // 开发模式
entry: path.resolve(__dirname, "../src/main.js"), // 入口文件 相对路径
// web server
devtool: 'cheap-module-source-map', //build: slow rebuild: fast
// 在dist中不会输出
devServer: {
static: {
directory: path.resolve(__dirname, "../dist"), // 打包后的文件路径 directory:目录
},
open: true, //自动打开浏览器
compress: true, //启动gzip压缩
port: 9000, // 端口号
hot: true,// 提供HMR功能,只更新某个模块,没有替换整个项目
},
output: {
clean: true, // 清理 /dist 文件夹
// filename: "js/main.js", // 打包后的文件名称
filename: "js/[name].[contenthash:8].js", // 打包后的文件名称
path: undefined,
// 打包输出的其他文件名称
chunkFilename: "js/[name].[contenthash:8].chunk.js",
// 图片 等字体通过type: asset处理资源命名方式
assetModuleFilename: 'media/[name].[contenthash:8][ext][query]',
},
cache: {
type: 'filesystem',
allowCollectingMemory: true,
idleTimeout: 60000,
compression: 'gzip',
},
// 开发环境不用处理
optimization: {
minimize: true, // 强制启用压缩
// 对代码进行分割
splitChunks: {
chunks: 'all',
},
// runtimeChunk: 'single',
minimizer: [
new CssMinimizerPlugin(),
]
},
plugins: [
new HtmlWebpackPlugin({
// 模版:以public/index.html为模板生成打包后的index.html
template: path.resolve(__dirname, "../public/index.html"),
// BASE_URL: process.env.BASE_URL || '/'
}),
new ESLintPlugin({
// 配置哪些目录需要检查
context: path.resolve(__dirname, './src'),
exclude: 'node_modules',// 不写 默认也有
cache: true,// 开启缓存npm
cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'),
// threads,// 开启多进程打包
eslintPath:'eslint',//指定传统
}),
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:8].css"
}),
new DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('development'),
BASE_URL: '"../"' // 定义全局变量BASE_URL
}),
new CssMinimizerPlugin(),
],
// loader 加载器
module: {
rules: [
// 每个文件只有一个loader配置处理
{
oneOf: [
{
test: /\.(gif|png|jpe?g)$/i,
type: "asset",
parser: {
dataUrlCondition: {
// 小于10kb的图片转成base64,减少请求数量
// 缺点:体积会大一点
maxSize: 10 * 1024, // 小于10kb
},
},
generator: {
// 输出图片的名称
filename: "imgs/[name].[contenthash:8][ext]",
},
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
// 对文件原封不动的输出
type: "asset/resource",
// generator: {
// filename: "media/[name].[contenthash:8][ext]",
// },
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
type: "asset/resource",
// generator: {
// filename: "fonts/[name].[contenthash:8][ext]"
// }
},
// 复杂场景用
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-react', { runtime: 'automatic' }]
],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false,// 关闭缓存文件压缩
plugins: ['@babel/plugin-transform-runtime'],
},
},]
},
{
test: /\.css$/,
// MiniCssExtractPlugin.loader 最终会将css提取到单独的文件
use: getStyleLoaders(), // 从右向左解析原则
// use: ["style-loader", "css-loader"]
},
{
test: /\.less$/,
use: getStyleLoaders("less-loader"), // 从右向左解析原则
// use: ["style-loader", "css-loader", "less-loader"]
},
{
test: /\.s[ac]ss$/,
use: getStyleLoaders("sass-loader")
},
{
test: /\.styl$/,
use: getStyleLoaders("stylus-loader")
},]
}
],
},
// webpack解析加载块加载选项
resolve:{
extensions: ['.jsx','.js','.json',],
}
};
2.2 新建eslintrc.js文件
javascript
module.exports = {
extends: ["react-app"], // 继承 react 官方规则
parserOptions: {
babelOptions: {
presets: [
// 解决页面报错问题
["babel-preset-react-app", false],
"babel-preset-react-app/prod",
],
},
},
};
2.3 新建babel.config.js文件
javascript
module.exports = {
persets:["react-app"]
}
2.4 创建 package.json 文件
npm init -y
2.5 创建 react main.js App.jsx
- main.js
javascript
import react from 'react';
import ReactDom from 'react-dom/client';
import App from './App';
const root= ReactDom.createRoot(document.getElementById('app'));
root.render(<App/>);
- App.jsx
jsx
import React from 'react';
const App = ()=>{
return(
<div>
<h1>Hello World</h1>
</div>
)
}
3、安装依赖
bash
npm install webpack webpack-cli webpack-dev-server --D
npm install mini-css-extract-plugin html-webpack-plugin eslint-webpack-plugin --D
npm install style-loader css-loader less-loader sass sass-loader postcss-loader postcss-preset-env stylus-loader --D
npm install babel-loader @babel/core babel-preset-react-app --D
npm install eslit eslint-config-react-app --D
npm install react react-dom --S
4、配置package.json webpack打包入口
json
"scripts": {
"test": "npm run dev",
"dev": "webpack serve --config ./config/webpack.dev.js"
},
5、public index.html 创建一个id为app节点,以便挂载react组件
html
<!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 rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<title>react- Cli</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
5、 报错处理
-
报错一 eslint 版本太高
[eslint] Couldn't find FlatESLint, you might need to set eslintPath to 'eslint/use-at-your-own-risk'
解决方案: 换成低版本的eslint
- 报错二 提示有未使用的变量
NODE_ENV
orBABEL_ENV
Usingbabel-preset-react-app
requires that you specifyNODE_ENV
orBABEL_ENV
解决方案:
- 安装cross-env
bash
npm install cross-env --D
-
修改package.json中的dev命令,定义环境变量
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js"
- 报错三'./App' 未加后缀,代码不认识
ERROR in ./src/main.js 4:0-24
Module not found: Error: Can't resolve './App' in 'E:\study_2025\react-webpack5\src'
解决方案:配置resolve,告诉webpack,先用.jsx解析,再用.js解析,再用.json解析
javascript
// webpack解析加载块加载选项
resolve:{
extensions: ['.jsx','.js','.json',],
}
- 报错四:
Module not found: Error: Can't resolve 'E:\study_2025\react-webpack5\public\index.html
检查了webpack的配置文件,发现没有配置html-webpack-plugin也是正确的
看视频发现没有这一步的配置
自己手动创建了public目录和index.html文件
掉了第五步: 5、创建public目录和 index.html文件, 创建一个id为app节点,以便挂载react组件
html
<body>
<div id="app"></div>
</body>
- 异常五:页面启动后无报错,页面显示空白
原因: 在高版本中import react from 'react' 不用写,打包会报错
解决更新 babel.config.js 或 webpack.config.js:
javascript
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-react',
{
runtime: 'automatic', // 启用自动 JSX 转换
importSource: 'react', // 默认值,可改为 'preact' 等其他库
},
],
],
};
或者
javascript
// webpack.config.js
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-react', { runtime: 'automatic' }]
]
}
}
}
]
}
我用的下面这种
6、最终的目录结构
react-webpack5
├── public/ # 手动创建此文件夹
│ ├── index.html # 主 HTML 模板
│ ├── favicon.ico # 网站图标
│
├── src/ # 源码目录
│ ├── main.js # 入口文件
│ └── App.jsx # 应用根组件
└── config
└──── webpack.dev.js # Webpack 开发配置文件
7、webpack优化
配置热更新
bash
npm install @pmmmwh/react-refresh-webpack-plugin --D
在webpack.dev.js中配置热更新
javascript
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
module.exports = {
module:{
devServer: {
···
hot: true,// 开启HMR
},
rules:[
{
test: /\.jsx?$/,
...
options: {
...
plugins: [
"react-refresh/babel", // 激活js的HMR ,仅用于开发环境
],
},
},
]
}
}
plugins: [
new ReactRefreshWebpackPlugin(), // 激活js的HMR
]
8、配置react路由
- 安装react-router-dom
bash
npm install react-router-dom --S
- 在main.js中引入react-router-dom
javascript
import { BrowserRouter } from 'react-router-dom'
const root = ReactDom.createRoot(document.getElementById("app"));
root.render(<BrowserRouter><App /></BrowserRouter>);
- 在pages文件夹下创建 About.jsx 、Home.jsx 文件
- 在App.jsx中配置路由跳转
js
import { Routes, Route, Link } from 'react-router-dom';
import About from './pages/About';
import Home from './pages/Home';
const App = () => {
return (
<div>
<ul>
<li>
<Link to="/home">首页</Link>
</li>
<li>
<Link to="/about">关于</Link>
</li>
</ul>
<Routes>
<Route path="/home" element={<Home/>} />
<Route path="/about" element={<About/>} />
</Routes>
</div>
)
}
export default App;
- 路由强制刷新出现Cannot GET /about
解决方案:
javascript
module.exports = {
devServer: {
...
port: 9000, // 端口号
hot: true,// 提供HMR功能, 只更新某个模块,没有替换整个项目
historyApiFallback: true, // 解决前端路由刷新404问题
},
9、若想让文件单独打包,可配置路由懒加载
js
import React, { lazy, Suspense } from 'react';
import { Routes, Route, Link } from 'react-router-dom';
const LayAbout = lazy(() => import('./pages/About'));
const LayHome = lazy(() => import('./pages/Home'))
// const Home = lazy(() => import(/* webpackChunkName: 'home' */ "./pages/Home"));
// const About = lazy(() => import(/* webpackChunkName: 'about' */ "./pages/About"));
const App = () => {
return (
<div>
<ul>
<li>
<Link to="/home">首页</Link>
</li>
<li>
<Link to="/about">关于</Link>
</li>
</ul>
<Suspense fallback={<div>页面正在加载中...</div>}>
<Routes >
<Route path="/home" element={<LayHome />} />
<Route path="/about" element={<LayAbout />} />
</Routes>
</Suspense>
</div>
)
}
export default App;
10、webpack.dev 配置文件完整
javascript
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ESLintPlugin = require('eslint-webpack-plugin');
const { DefinePlugin } = require('webpack');
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
// 用来处理获取的样式
function getStyleLoaders(pre) {
return [MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
// plugins: [["autoprefixer"]],
plugins: ['postcss-preset-env'],//能解决大多数兼容性问题
},
},
}, pre].filter(Boolean);
}
module.exports = {
mode: "development", // 开发模式
entry: path.resolve(__dirname, "../src/main.js"), // 入口文件 相对路径
// web server
devtool: 'cheap-module-source-map', //build: slow rebuild: fast
// 在dist中不会输出
devServer: {
static: {
directory: path.resolve(__dirname, "../dist"), // 打包后的文件路径 directory:目录
},
open: true, //自动打开浏览器
compress: true, //启动gzip压缩
port: 9000, // 端口号
hot: true,// 提供HMR功能, 只更新某个模块,没有替换整个项目
},
output: {
clean: true, // 清理 /dist 文件夹
// filename: "js/main.js", // 打包后的文件名称
filename: "js/[name].[contenthash:8].js", // 打包后的文件名称
path: undefined,
// 打包输出的其他文件名称
chunkFilename: "js/[name].[contenthash:8].chunk.js",
// 图片 等字体通过type: asset处理资源命名方式
assetModuleFilename: 'media/[name].[contenthash:8][ext][query]',
},
cache: {
type: 'filesystem',
allowCollectingMemory: true,
idleTimeout: 60000,
compression: 'gzip',
},
// 开发环境不用处理
optimization: {
minimize: true, // 强制启用压缩
// 对代码进行分割
splitChunks: {
chunks: 'all',
},
},
plugins: [
new HtmlWebpackPlugin({
// 模版:以public/index.html为模板生成打包后的index.html
template: path.resolve(__dirname, "../public/index.html"),
// BASE_URL: process.env.BASE_URL || '/'
}),
new ESLintPlugin({
// 配置哪些目录需要检查
context: path.resolve(__dirname, './src'),
exclude: 'node_modules',// 不写 默认也有
cache: true,// 开启缓存npm
cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'),
// threads,// 开启多进程打包
eslintPath: 'eslint',//指定传统
}),
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:8].css"
}),
new DefinePlugin({
// window.ENV = 'production'
ENV: JSON.stringify('development'),
BASE_URL: '"../"' // 定义全局变量BASE_URL
}),
new CssMinimizerPlugin(),
new ReactRefreshWebpackPlugin(), // 激活js的HMR
],
// loader 加载器
module: {
rules: [
// 每个文件只有一个loader配置处理
{
oneOf: [
{
test: /\.(gif|png|jpe?g)$/i,
type: "asset",
parser: {
dataUrlCondition: {
// 小于10kb的图片转成base64,减少请求数量
// 缺点:体积会大一点
maxSize: 10 * 1024, // 小于10kb
},
},
generator: {
// 输出图片的名称
filename: "imgs/[name].[contenthash:8][ext]",
},
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
// 对文件原封不动的输出
type: "asset/resource",
// generator: {
// filename: "media/[name].[contenthash:8][ext]",
// },
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
type: "asset/resource",
// generator: {
// filename: "fonts/[name].[contenthash:8][ext]"
// }
},
// 复杂场景用
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-react', { runtime: 'automatic' }]
],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false,// 关闭缓存文件压缩
plugins: ['@babel/plugin-transform-runtime',
"react-refresh/babel", // 激活js的HMR
],
},
},]
},
{
test: /\.css$/,
// MiniCssExtractPlugin.loader 最终会将css提取到单独的文件
use: getStyleLoaders(), // 从右向左解析原则
// use: ["style-loader", "css-loader"]
},
{
test: /\.less$/,
use: getStyleLoaders("less-loader"), // 从右向左解析原则
// use: ["style-loader", "css-loader", "less-loader"]
},
{
test: /\.s[ac]ss$/,
use: getStyleLoaders("sass-loader")
},
{
test: /\.styl$/,
use: getStyleLoaders("stylus-loader")
},]
}
],
},
// webpack解析加载块加载选项
resolve: {
extensions: ['.jsx', '.js', '.json',],
}
};
生产环境配置
- 复制一份webpack.dev.js文件,并改名为webpack.config.prod.js
- 修改项
javascript
mode: "production", // 生产模式
devtool: "source-map",
- 添加插件css-minimizer-webpack-plugin、mini-css-extract-plugin
- css 压缩
javascript
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
optimization: {
...
minimizer: [
new CssMinimizerPlugin(),
]
},
- js 压缩
javascript
const TerserWebpackPlugin = require("terser-webpack-plugin");
...
optimization: {
...
minimizer: [
new TerserWebpackPlugin(),
]
},
-
移除HMR功能
- 移除devServer
- ReactRefreshWebpackPlugin移除
- plugins: ["react-refresh/babel",] // 激活js的HMR
-
对图片进行压缩
- 安装依赖
bash
npm i image-minimizer-webpack-plugin --D
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --D
包不好下
javascript
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",
},
},
],
},
],
],
},
},
}),
- 将public目录下的静态资源复制到dist目录下
javascript
// const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"),
to: path.resolve(__dirname, "../dist"),
globOptions: {
// 忽略index.html文件
ignore: ["**/index.html"],
},
},
],
}),
webpack 公共
- 引入antd 三方组件
- 安装
bash
npm install antd --S
js
import { Button } from 'antd';
<Button type="primary">按钮</Button>
webpack 修改某个主题的颜色
- 页面
js
import { Button } from 'antd';
<Button type="primary">按钮</Button>
- 在 webpack.prod.js 文件中添加以下代码
js
const getStyleLoaders = (pre) => {
return [
...
pre && {
loader: pre,
options:
pre === "less-loader"
? {
// antd自定义主题配置
// 主题色文档:https://ant.design/docs/react/customize-theme-cn#Ant-Design-%E7%9A%84%E6%A0%B7%E5%BC%8F%E5%8F%98%E9%87%8F
lessOptions: {
modifyVars: { "@primary-color": "red" },
javascriptEnabled: true,
},
}
: {},
},
].filter(Boolean);
};
- webpack 打包时部分包比较大,这个时候可以使用 webpack-bundle-analyzer 来分析打包后的包大小,然后进行优化。
javascript
optimization: {
splitChunks: {
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,
},
},
},
}