Skip to content

React 脚手架

目录结构

text
react_cli
    ├─ node_modules
    ├─ config
    │  ├─ webpack.config.base.js  #公共配置
    │  ├─ webpack.dev.js #开发配置
    │  └─ webpack.prod.js #生产配置
    ├─ public
    │  ├─ favicon.ico
    │  └─ index.html
    ├─ src
    │  ├─ pages
    │  ├─ main.js
    │  └─ App.jsx
    ├─ .eslintrc.js
    ├─ babel.config.js
    └─ package.json

共用配置

js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const EslintWebpackPlugin = require("eslint-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const isProduction = process.env.NODE_ENV === "production";
// 样式文件处理函数
function getStyleLoaders() {
  return [
    isProduction ? MiniCssExtractPlugin.loader : "style-loader",
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: ["postcss-preset-env"], //处理绝大多数样式兼容
        },
      },
    },
  ];
}
module.exports = {
  // 入口
  entry: "./src/main.js",
  // 解析器
  module: {
    rules: [
      {
        oneOf: [
          {
            test: /\.css$/,
            use: [...getStyleLoaders()],
          },
          {
            test: /\.less$/,
            use: [...getStyleLoaders(), "less-loader"],
          },
          {
            test: /\.s[ac]ss$/,
            use: [...getStyleLoaders(), "sass-loader"],
          },
          {
            test: /\.(jpe?g|png|gif|svg)$/,
            type: "asset",
            parser: {
              dataUrlCondition: {
                maxSzie: 10 * 1024, //小于10kb的图片会被转换为base64格式
              },
            },
          },
          {
            test: /\.(tff|woff2?)$/,
            type: "asset/resource", //字体文件会被直接输出
          },
          {
            test: /\.jsx?$/,
            exclude: /node_modules/,
            loader: "babel-loader",
            options: {
              cacheDirectory: true, //开启缓存
              cacheCompression: false, //缓存不压缩
            },
          },
        ],
      },
    ],
  },
  // 插件
  plugins: [
    new EslintWebpackPlugin({
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules",
      cache: true, //开启缓存
      cacheLocation: path.resolve(__dirname, "../node_modules/.cahe/.eslintcahe"), //缓存位置
    }),
    new HtmlWebpackPlugin({
      favicon: path.resolve(__dirname, "../public/favicon.ico"),
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    // 抽离css样式文件
    new MiniCssExtractPlugin({
      filename: "static/css/[contenthash:8].css",
      chunkFilename: "static/css/[contenthash:8].chunk.css",
    }),
  ],
  // 优化
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all",
      //提取模块的方案
      cacheGroups: {
        antd: {
          name: "chunk-antd",
          test: /[\\/]node_modules[\\/]antd(.*)/,
          priority: 30,
        },
        // 将react相关的库单独打包,减少node_modules的chunk体积。
        react: {
          name: "react",
          test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
          chunks: "initial",
          priority: 20,
        },
        libs: {
          name: "chunk-vendors",
          test: /[\\/]node_modules[\\/]/,
          priority: 10, // 权重最低,优先考虑前面内容
          chunks: "initial",
        },
      },
    },
  },
  resolve: {
    extensions: [".jsx", ".js", ".json"], // 自动补全文件扩展名,让jsx可以使用
  },
};

开发模式配置

js
const path = require("path");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const { merge } = require("webpack-merge");
const baseConfig = require("./webpack.config.base.js");

const devConfig = {
  // 输出
  output: {
    path: undefined, //开发模式 缓存在内存
    filename: "static/js/[name].[chunkhash:8].js",
    chunkFilename: "static/js/[name].[chunkhash:8].chunk.js",
  },
  // 解析器
  module: {
    rules: [
      {
        oneOf: [
          {
            test: /\.(jsx|js)$/,
            include: path.resolve(__dirname, "../src"),
            loader: "babel-loader",
            options: {
              cacheDirectory: true,
              cacheCompression: false,
              plugins: [
                // "@babel/plugin-transform-runtime", // presets中包含了
                "react-refresh/babel", // 开启js的HMR功能
              ],
            },
          },
        ],
      },
    ],
  },
  // 插件
  plugins: [
    // js热更新HMR
    new ReactRefreshWebpackPlugin(),
  ],
  // 优化
  optimization: {},
  resolve: {},
  devServer: {
    host: "localhost",
    port: 3000,
    open: false,
    historyApiFallback: true, // 解决react-router刷新404问题
  },
  mode: "development",
  devtool: "cheap-module-source-map", //代码错误提示形式
};
// 将公共配置合并
module.exports = merge(baseConfig, devConfig);

生产模式配置

js
const path = require("path");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");

const { merge } = require("webpack-merge");
const baseConfig = require("./webpack.config.base.js");
const prodConfig = {
  // 输出
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "static/js/[name].[chunkhash:8].js",
    chunkFilename: "static/js/[name].[chunkhash:8].chunk.js",
    // 清除原打包文件
    clean: true,
  },
  // 解析器
  module: {},
  // 插件
  plugins: [],
  // 优化
  optimization: {
    // 文件压缩
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserWebpackPlugin({
        extractComments: false, //不将注释提取到单独文件
      }),
    ],
  },
  resolve: {},
  mode: "production",
  devtool: "source-map", //代码错误提示形式
  performance: false, // 关闭性能分析,提升打包速度
};
// 将公共配置合并
module.exports = merge([baseConfig, prodConfig]);

其他配置

package.json

json
{
  "name": "react-cli",
  "version": "1.0.0",
  "description": "",
  "main": ".eslintrc.js",
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.20.2",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
    "babel-loader": "^9.1.0",
    "babel-preset-react-app": "^10.0.1",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.2",
    "css-minimizer-webpack-plugin": "^4.2.2",
    "eslint-config-react-app": "^7.0.1",
    "eslint-plugin-import": "^2.26.0",
    "eslint-webpack-plugin": "^3.2.0",
    "html-webpack-plugin": "^5.5.0",
    "less-loader": "^11.1.0",
    "mini-css-extract-plugin": "^2.7.0",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.3",
    "react-refresh": "^0.14.0",
    "sass": "^1.56.1",
    "sass-loader": "^13.2.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.0",
    "webpack-dev-server": "^4.11.1",
    "webpack-merge": "^5.8.0"
  },
  "browserslist": ["last 2 version", "> 1%", "not dead"],
  "dependencies": {
    "antd": "^5.0.3",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.4.3"
  }
}

.eslintrc.js

js
module.exports = {
  extends: ["react-app"], // 继承 react 官方规则
  parserOptions: {
    babelOptions: {
      presets: [
        // 解决页面报错问题
        ["babel-preset-react-app", false],
        "babel-preset-react-app/prod",
      ],
    },
  },
};

babel.config.js

js
module.exports = {
  // 使用react官方规则
  presets: ["react-app"],
};

react 配置

App.jsx

jsx
import React, { Suspense, lazy } from "react";
import { Routes, Route, Link } from "react-router-dom";
import { ConfigProvider } from "antd";

const About = lazy(() => import(/* webpackChunkName:'about' */ "./pages/about"));
const Home = lazy(() => import(/* webpackChunkName:'home' */ "./pages/home"));

export default function App() {
  return (
    <div>
      <h1>APP</h1>
      <ul>
        <li>
          <Link to="/home">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
      </ul>

      <ConfigProvider
        theme={{
          token: {
            colorPrimary: "#00b96b",
          },
        }}
      >
        <Suspense fallback={<p>loading...</p>}>
          <Routes>
            <Route path="/home" element={<Home />}></Route>
            <Route path="/about" element={<About />}></Route>
          </Routes>
        </Suspense>
      </ConfigProvider>
    </div>
  );
}

main.js

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("root"));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);