基础工程搭建

在前一章中我们介绍了使用CRA创建React工程,实际上CRA屏蔽了很多工程配置的细节,一些工程配置修改起来可能不如我们自己手动搭建的方便使用,这里我们再手动搭建一个最基础的React工程,集成Webpack、Webpack DevServer、Babel、各种类型Loader、构建分析等必备的常用功能,以加深对相关工具链和依赖库的理解。

本篇笔记中的例子工程我们基于最新版本的React18、Webpack5来搭建。

工程目录结构

下面是这个基础功能的目录结构,其中包括Webpack配置文件、构建输出目录、公共目录、源码目录以及工程描述文件,这些文件共同组成了一个最基础可用的React前端工程。

|_node_modules // npm包目录
|_config // 配置文件目录
  |_webpack.js // 基础Webpack配置文件
  |_webpack.development.js // 开发环境配置文件
  |_webpack.test.js // 测试环境配置文件
  |_webpack.production.js // 生产环境配置文件
  |_webpack.analyze.js // 构建分析配置文件
|_dist // 构建输出
|_public // 入口HTML和其直接引用的静态文件
  |_index.html // 入口HTML
  |_global.css // 入口HTML直接引用的一个全局CSS文件
|_src // 源代码路径
  |_index.jsx // 入口JavaScript文件
  |_App.jsx // 根组件
|_package.json // npm工程描述文件

基础工程源码和介绍

这里我们给出所有配置文件的源码以供参考。

package.json

{
  "name": "demoreact",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "dev": "cross-env NODE_ENV=development APP_ENV=development webpack-dev-server --config config/webpack.development.js",
    "build:test": "cross-env NODE_ENV=production APP_ENV=test webpack --config config/webpack.test.js",
    "build:analyze": "cross-env NODE_ENV=production APP_ENV=production webpack --config config/webpack.analyze.js",
    "build:prod": "cross-env NODE_ENV=production APP_ENV=production webpack --config config/webpack.production.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "webpack": "^5.88.2",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1",
    "webpack-merge": "^5.9.0",
    "babel-loader": "^9.1.3",
    "@babel/core": "^7.22.9",
    "@babel/preset-env": "^7.22.9",
    "@babel/preset-react": "^7.22.5",
    "style-loader": "^3.3.3",
    "css-loader": "^6.8.1",
    "less": "^4.1.3",
    "less-loader": "^11.1.3",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
    "react-refresh": "^0.14.0",
    "html-webpack-plugin": "^5.5.3",
    "copy-webpack-plugin": "^11.0.0",
    "css-minimizer-webpack-plugin": "^5.0.1",
    "speed-measure-webpack-plugin": "^1.5.0",
    "webpack-bundle-analyzer": "^4.9.0",
    "cross-env": "^7.0.3"
  }
}

工程描述文件package.json中,我们主要关注scripts构建命令和依赖包。构建命令我们编写了4条:

  • npm run dev 本地通过devServer启动工程
  • npm run build:test 以测试环境配置构建工程(配置基本同生产,后面不在重复介绍)
  • npm run build:prod 以生产环境配置构建工程
  • npm run build:analyze 执行构建分析插件

依赖包这里我们详细介绍一下:

  • reactreact-dom:不必多说,这些是React框架的本体
  • webpackwebpack-cliwebpack-dev-server:包括Webpack构建工具和开发环境用的调试服务器
  • webpack-merge:用于合并Webpack配置,是多环境开发拆分配置文件必备的工具
  • babel-loader@babel/core@babel/preset-env@babel/preset-react:Babel用于将高版本JavaScript语法和JSX语法转义到浏览器能够执行的JavaScript语法,这些工具通过babel-loader集成到Webpack处理构建中的资源
  • style-loadercss-loaderlessless-loader:用于加载CSS和Less样式资源
  • @pmmmwh/react-refresh-webpack-pluginreact-refresh:这两个包是对Webpack原版HMR的补充,用于实现修改JSX后不刷新页面直接更新渲染结果,且保留组件状态,对开发效率十分有帮助
  • html-webpack-plugin:该插件用于将public/index.html作为模板向其中插入Webpack打包后的资源,并将index.html拷贝到构建输出路径dist
  • copy-webpack-plugin:该插件用于将public中的资源拷贝到构建输出路径dist中,注意HtmlWebpackPlugin默认会拷贝index.html,该插件则用于拷贝index.html直接引用的其它样式表、媒体资源、字体、第三方库等
  • css-minimizer-webpack-plugin:该插件用于压缩CSS文件
  • speed-measure-webpack-plugin:该插件用于分析构建速度,只在npm run build:analyze命令中执行
  • webpack-bundle-analyzer:该插件用于分析构建结果,只在npm run build:analyze命令中执行
  • cross-env:用于配置Node环境变量,这个大家都很熟悉,就不多介绍了

config/webpack.js

const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  entry: path.join(__dirname, "../src/index.jsx"),
  output: {
    filename: "[name].[chunkhash:8].js",
    path: path.join(__dirname, "../dist"),
    clean: true,
    publicPath: "/",
  },
  resolve: {
    alias: {
      "@": path.join(__dirname, "../src"),
    },
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: "/node_modules/",
        use: {
          loader: "babel-loader",
          options: {
            presets: [
              ["@babel/preset-react", { runtime: "automatic" }],
              "@babel/preset-env",
            ],
            plugins: [
              process.env === "development" &&
                require.resolve("react-refresh/babel"),
            ].filter(Boolean),
          },
        },
      },
      {
        test: /\.(css|less)$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /.(png|jpg|jpeg|gif|svg)$/,
        type: "asset",
        parser: {
          dataUrlCondition: { maxSize: 10 * 1024 },
        },
        generator: {
          filename: "static/images/[name][ext]",
        },
      },
      {
        test: /.(woff2?|eot|ttf|otf)$/,
        type: "asset",
        generator: {
          filename: "static/fonts/[name][ext]",
        },
      },
      {
        test: /.(mp4|webm|ogg|mp3|wav|flac|aac)$/,
        type: "asset",
        generator: {
          filename: "static/media/[name][ext]",
        },
      },
    ],
  },
  plugins: [
    new webpack.DefinePlugin({
      "process.env.APP_ENV": JSON.stringify(process.env.APP_ENV),
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
      inject: true,
    }),
  ],
  optimization: {
    minimize: true,
    minimizer: [
      new TerserWebpackPlugin({ extractComments: false }),
      new CssMinimizerWebpackPlugin(),
    ],
    splitChunks: {
      cacheGroups: {
        vendors: {
          test: /node_modules/,
          name: "vendors",
          minChunks: 1,
          chunks: "initial",
          minSize: 0,
          priority: 1,
        },
        commons: {
          name: "commons",
          minChunks: 2,
          chunks: "initial",
          minSize: 0,
        },
      },
    },
  },
  cache: {
    type: "filesystem",
  },
};

webpack.js是我们的基础Webpack配置,其它环境的Webpack配置都是通过merge的方式合并到基础配置上的。

config/webpack.development.js

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

module.exports = merge(baseConfig, {
  mode: "development",
  devtool: "source-map",
  watch: true,
  devServer: {
    host: "localhost",
    port: 3000,
    open: true,
    compress: false,
    hot: true,
    historyApiFallback: true,
    static: {
      directory: path.join(__dirname, "../public"),
    },
  },
  plugins: [new ReactRefreshWebpackPlugin()],
});

webpack.development.js是开发环境的Webpack配置,其中主要对devServer进行了配置。

config/webpack.production.js

const path = require("path");
const { merge } = require("webpack-merge");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const baseConfig = require("./webpack.js");

module.exports = merge(baseConfig, {
  mode: "production",
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, "../public"),
          to: path.resolve(__dirname, "../dist"),
          filter: (resourcePath) => {
            return !resourcePath.includes("index.html");
          },
        },
      ],
    }),
  ],
});

webpack.production.js是生产环境的Webpack配置,主要对构建进行了配置。config/webpack.test.js内容基本和config/webpack.production.js相同。

config/webpack.analyze.js

const { merge } = require("webpack-merge");
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const productionConfig = require("./webpack.production.js");

const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap(
  merge(productionConfig, {
    plugins: [new BundleAnalyzerPlugin()],
  })
);

webpack.analyze.js是用于构建分析的Webpack配置,这里主要用到了两个插件:speed-measure-webpack-pluginwebpack-bundle-analyzer,分别用于分析构建速度和构建结果。

public/index.html

<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link href="global.css" type="text/css" rel="stylesheet" />
  </head>

  <body>
    <div id="root"></div>
  </body>
</html>

index.html是入口HTML文件,注意其中包含的<div id="root"></div>是我们的React组件挂载根节点。

public/global.css

body {
  margin: 0;
  padding: 0;
}

ul {
  margin: 0;
}

* {
  box-sizing: border-box;
}

global.css是我们自定义的样式表文件,在这里我们添加这样一个文件主要用于演示公共文件构建拷贝和CSS文件压缩的效果。

src/index.jsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "@/App.jsx";

const render = () => {
  const root = ReactDOM.createRoot(document.getElementById("root"));
  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
};

if (process.env.NODE_ENV === "development" && module.hot) {
  module.hot.accept("@/App.jsx", render);
}

render();

index.jsx是我们的入口源码文件,其中包含了挂载React组件和集成开发环境HMR功能的代码。

src/App.jsx

import Demo from "@/Demo/index.jsx";

const App = () => {
  return (
    <div>
      <Demo />
    </div>
  );
};

export default App;

App.jsx是我们自定义的React组件。

更多关于工程搭建的细节

上面我们搭建的只是最简单最基础的React工程,真正开发中情况会更加的复杂。我们可能需要添加更多功能,比如react-routerredux、组件库等;我们面向的平台可能是Electron,我们渲染的方式可能是SSR等等。

实际上,我们只要弄懂Webpack等工具的基本使用方法,参考文档和各种教程去解决特定的问题即可,随着各种框架和工具版本的迭代配置项也可能发生变化,我们没必一上来就沉迷工程搭建,而最终反倒一行真正有用的代码都没写出来,那样就本末倒置了。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap