手动搭建并配置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,
        },
      },
    },
  }

点击查看项目地址

相关推荐
pe7er5 分钟前
使用CDN、ImportMap增强Vue playground
前端
ze_juejin15 分钟前
Angular的懒加载由浅入深
前端
JSON_L15 分钟前
Vue 详情模块 4
前端·javascript·vue.js
码间舞21 分钟前
什么是Tearing?为什么React的并发渲染可能会有Tearing?
前端·react.js
gnip33 分钟前
做个交通信号灯特效
前端·javascript
小小小小宇34 分钟前
Webpack optimization
前端
尝尝你的优乐美36 分钟前
前端查缺补漏系列(二)JS数组及其扩展
前端·javascript·面试
咕噜签名分发可爱多38 分钟前
苹果iOS应用ipa文件安装之前?为什么需要签名?不签名能用么?
前端
她说人狗殊途1 小时前
Ajax笔记
前端·笔记·ajax
yqcoder1 小时前
33. css 如何实现一条 0.5 像素的线
前端·css