手动搭建并配置react项目(webpack5)

手动搭建并配置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 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
  1. 修改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路由

  1. 安装react-router-dom
bash 复制代码
npm install react-router-dom --S
  1. 在main.js中引入react-router-dom
javascript 复制代码
import { BrowserRouter } from 'react-router-dom'

const root = ReactDom.createRoot(document.getElementById("app"));
root.render(<BrowserRouter><App /></BrowserRouter>);
  1. 在pages文件夹下创建 About.jsx 、Home.jsx 文件
  2. 在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;
  1. 路由强制刷新出现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
  1. css 压缩
javascript 复制代码
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
  optimization: {
   ...
    minimizer: [
      new CssMinimizerPlugin(),
    ]
  },
  1. js 压缩
javascript 复制代码
 const TerserWebpackPlugin = require("terser-webpack-plugin");
 ...
optimization: {
 ...
  minimizer: [
    new TerserWebpackPlugin(),
  ]
},
  1. 移除HMR功能

    • 移除devServer
    • ReactRefreshWebpackPlugin移除
    • plugins: ["react-refresh/babel",] // 激活js的HMR
  2. 对图片进行压缩

  • 安装依赖
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",
                      },
                    },
                  ],
                },
              ],
            ],
          },
        },
      }),
  1. 将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
js 复制代码
import { Button } from 'antd';
 <Button type="primary">按钮</Button>

webpack 修改某个主题的颜色

  1. 页面
js 复制代码
import { Button } from 'antd';
 <Button type="primary">按钮</Button>
  1. 在 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,
        },
      },
    },
  }

点击查看项目地址

相关推荐
__不想说话__1 分钟前
面试官问我React Router原理,我掏出了平底锅…
前端·javascript·react.js
DevinJohw5 分钟前
为什么我选择[email protected]
react.js·vite
头发秃头小宝贝8 分钟前
JavaScript 高级之手写Promise
前端·javascript·面试
Aiolimp8 分钟前
Web Worker 基本使用
前端
还是鼠鼠15 分钟前
Node.js Express 处理静态资源
前端·javascript·vscode·node.js·json·express
智能编织者26 分钟前
用 Pinia 点燃 Vue 3 应用:状态管理革新之旅
前端·javascript·vue.js
微臣愚钝32 分钟前
【12】Ajax的原理和解析
前端·javascript·ajax
徐小夕@趣谈前端1 小时前
从零到一开发电子病历编辑器(源码+教程)
前端·javascript·vue.js·编辑器·ecmascript
货拉拉技术1 小时前
LLM 驱动前端创新:AI 赋能营销合规实践
前端·程序员·llm
MariaH1 小时前
深入了解vertical-align
前端