# react_webpack5 **Repository Path**: pear66/react_webpack5 ## Basic Information - **Project Name**: react_webpack5 - **Description**: 不使用脚手架,手动搭建react项目框架 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2025-03-28 - **Last Updated**: 2025-03-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 手动搭建并配置react项目(webpack5) # 介绍 不使用脚手架,手动搭建react项目框架 # 1、项目创建 创建目录 react_wepack # 2、webpack+ react基础架构 ## 2.1 配置 webpack.dev.js [基础配置说明可参考这篇文章](https://blog.csdn.net/yalywq/article/details/146554148?spm=1001.2014.3001.5501) #### 配置 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 新建eslinttrc.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.jsx ```jsx import React from 'react'; const App = ()=>{ return(

Hello World

) } ``` # 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 react- Cli
``` # 5、 报错处理 - 报错一 eslint 版本太高 ``` [eslint] Couldn't find FlatESLint, you might need to set eslintPath to 'eslint/use-at-your-own-risk' ``` 解决方案: 换成低版本的eslint - 报错二 提示有未使用的变量`NODE_ENV` or `BABEL_ENV` Using `babel-preset-react-app` requires that you specify `NODE_ENV` or `BABEL_ENV` 解决方案: 1. 安装cross-env ```bash npm install cross-env --D ``` 2. 修改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
``` - 异常五:页面启动后无报错,页面显示空白 原因: 在高版本中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路由 1. 安装react-router-dom ```bash npm install react-router-dom --S ``` 2. 在main.js中引入react-router-dom ```javascript import { BrowserRouter } from 'react-router-dom' const root = ReactDom.createRoot(document.getElementById("app")); root.render(); ``` 3. 在pages文件夹下创建 About.jsx 、Home.jsx 文件 4. 在App.jsx中配置路由跳转 ```jsx import { Routes, Route, Link } from 'react-router-dom'; import About from './pages/About'; import Home from './pages/Home'; const App = () => { return (
} /> } />
) } export default App; ``` 5. 路由强制刷新出现Cannot GET /about 解决方案: ```javascript module.exports = { devServer: { ... port: 9000, // 端口号 hot: true,// 提供HMR功能, 只更新某个模块,没有替换整个项目 historyApiFallback: true, // 解决前端路由刷新404问题 }, ``` # 9、若想让文件单独打包,可配置路由懒加载 ```jsx 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 (
页面正在加载中...
}> } /> } /> ) } 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 1. css 压缩 ```javascript const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); optimization: { ... minimizer: [ new CssMinimizerPlugin(), ] }, ``` 2. js 压缩 ```javascript const TerserWebpackPlugin = require("terser-webpack-plugin"); ... optimization: { ... minimizer: [ new TerserWebpackPlugin(), ] }, ``` 3. 移除HMR功能 - 移除devServer - ReactRefreshWebpackPlugin移除 - plugins: ["react-refresh/babel",] // 激活js的HMR 4. 对图片进行压缩 - 安装依赖 ```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", }, }, ], }, ], ], }, }, }), ``` 5. 将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 三方组件 1. 安装 ```bash npm install antd --S ``` ```jsx import { Button } from 'antd'; ``` ## webpack 修改某个主题的颜色 1. 页面 ```jsx import { Button } from 'antd'; ``` 2. 在 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, }, }, }, } ```