You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为非Create React App的React类组件项目配置热重载?

问题

刚加入采用React类组件+TypeScript技术栈的公司,当前项目无热重载功能:修改CSS、状态或代码后,Webpack全量编译耗时约25秒,需手动刷新页面,开发效率极低。团队计划逐步迁移至Create React App,但代码量庞大,迁移周期长达数年,现需在过渡阶段解决热重载问题。项目非Create React App初始化搭建,本人有1.5年行业经验,对配置操作较陌生,此次为自主学习并优化团队开发体验。

项目现有配置文件

Package.json

{
    "name": "web-front-end",
    "version": "1.22.19",
    "description": "HTML5 implementation of the <our company name>.  Written in Typescript, compiled to JavaScript, bundled with Webpack, executing on NodeJS, using React, adopting FLUX patterns.",
    "private": true,
    "scripts": {
        "babel": "babel built/client/js/client.js -o built/client/js/client.js",
        "clean": "rm -rf node_modules && rm -rf built && rm -rf coverage && echo 'clean complete'",
        "reset": "yarn clean && yarn cache clean && yarn install --frozen-lockfile",
        "compile": "yarn compile-client && yarn compile-server && yarn babel",
        "debug-client": "NODE_ENV=debug webpack --config webpack.dev.js && echo 'client compile complete'",
        "compile-client": "NODE_ENV=production webpack --config webpack.prod.js && echo 'production client compile complete'",
        "compile-client-dev": "NODE_ENV=development webpack --config webpack.dev.js && echo 'development client compile complete'",
        "compile-client-dev-watch": "NODE_ENV=development webpack --config webpack.dev-watch.js",
        "compile-server": "tsc -p src/server && echo 'server compile complete'",
        "compile-client-watch": "NODE_ENV=development webpack --config webpack.dev-watch.js",
        "compile-server-watch": "tsc -p src/server --watch",
        "validate": "yarn --ignore-engines && tslint './src/**/*.{ts,tsx}' './test/**/*.{ts,tsx}' && echo 'validate complete'",
        "start-server": "node ./built/server/ServerApp.js",
        "test": "tslint --project test && tslint --project src/app && tsc -p test --jsx react && node ./node_modules/jest/bin/jest.js --config=jest.test.config.json --coverage --verbose",
        "lint-fix": "tslint --fix --project src && tslint --fix --project test",
        "prepare": "patch-package"
    },
    "dependencies": {
        "@emotion/react": "^11.9.3",
        "@emotion/styled": "^11.9.3",
        "@mui/icons-material": "^5.8.4",
        "@mui/material": "^5.8.4",
        "@mui/styles": "^5.8.4",
        "@stripe/stripe-js": "1.11.0",
        "assert": "^2.0.0",
        "axios": "^0.27.2",
        "blob-stream": "0.1.3",
        "buffer": "^6.0.3",
        "compression": "1.7.3",
        "compression-webpack-plugin": "^10.0.0",
        "connected-react-router": "^6.9.2",
        "cors": "2.8.4",
        "device-uuid": "1.0.4",
        "dotenv": "6.0.0",
        "express": "4.16.3",
        "file-saver": "^2.0.5",
        "html-to-image": "^1.10.8",
        "https-browserify": "^1.0.0",
        "js-cookie": "2.2.0",
        "node-zip": "1.1.1",
        "path-to-regexp": "2.4.0",
        "pug": "2.0.3",
        "query-string": "6.1.0",
        "react": "17.0.0",
        "react-copy-to-clipboard": "5.0.1",
        "react-dom": "^17.0.2",
        "react-form-validator-core": "0.4.4",
        "react-hook-form": "^7.36.0",
        "react-inlinesvg": "2.3.0",
        "react-jss": "8.5.1",
        "react-lazy-load": "3.0.13",
        "react-markdown": "4.0.4",
        "react-material-ui-form-validator": "^3.0.1",
        "react-player": "1.14.2",
        "react-redux": "^7.2.6",
        "react-router": "5.2.1",
        "react-router-dom": "5.3.0",
        "react-share": "2.4.0",
        "react-speech-recognition": "^3.10.0",
        "react-stripe-elements": "2.0.1",
        "react-svgmt": "^1.2.0",
        "react-virtualized": "9.22.3",
        "react-youtube": "7.9.0",
        "recompose": "0.27.1",
        "rect": "1.2.1",
        "redux": "4.0.0",
        "redux-thunk": "2.3.0",
        "regenerator-runtime": "^0.13.9",
        "shortid": "2.2.15",
        "stream-browserify": "^3.0.0",
        "stream-http": "^3.2.0",
        "svg-to-pdfkit": "0.1.7",
        "url": "^0.11.0",
        "utf8": "3.0.0"
    },
    "devDependencies": {
        "@babel/cli": "^7.17.6",
        "@babel/core": "^7.17.8",
        "@babel/types": "^7.17.0",
        "@types/blob-stream": "0.1.29",
        "@types/code": "4.0.4",
        "@types/detect-browser": "3.0.0",
        "@types/jest": "25.2.3",
        "@types/js-cookie": "2.2.1",
        "@types/lab": "11.1.0",
        "@types/react": "17.0.14",
        "@types/react-dom": "17.0.14",
        "@types/react-inlinesvg": "1.0.0",
        "@types/react-redux": "^7.1.20",
        "@types/react-router": "4.4.5",
        "@types/react-virtualized": "9.21.18",
        "@types/shortid": "0.0.29",
        "@types/utf8": "2.1.6",
        "awesome-typescript-loader": "3.4.1",
        "copy-webpack-plugin": "4.5.1",
        "detect-browser": "3.0.1",
        "fork-ts-checker-webpack-plugin": "^7.2.13",
        "jest": "^27.5.1",
        "patch-package": "6.1.2",
        "postinstall-postinstall": "2.0.0",
        "process": "^0.11.10",
        "source-map-loader": "0.2.3",
        "source-map-support": "0.4.18",
        "ts-jest": "27.1.2",
        "ts-loader": "^9.3.1",
        "ts-mockito": "2.3.0",
        "ts-node": "^10.8.1",
        "tslint": "^6.0.1",
        "tslint-eslint-rules": "5.4.0",
        "tslint-react": "^5.0.0",
        "typescript": "4.1.5",
        "webpack": "^5.73.0",
        "webpack-bundle-analyzer": "^3.0.3",
        "webpack-cli": "^4.10.0",
        "webpack-merge": "^5.8.0"
    },
    "resolutions": {
        "@types/prop-types": "15.7.5",
        "@types/unist": "2.0.0",
        "@types/react-transition-group": "2.0.8",
        "@types/react": "17.0.14",
        "@types/react-dom": "17.0.14"
    },
    "repository": {
        "type": "git",
        "url": "our repo link"
    },
    "author": "our company name",
    "license": "MIT"
}

Webpack.common.js

const path                          = require("path");
const webpack                       = require('webpack');
const ForkTsCheckerWebpackPlugin    = require('fork-ts-checker-webpack-plugin');

module.exports = {
    entry: "./src/app/Main.ts",
    cache: false,
    optimization: {
        providedExports: true,
        usedExports: true,
        removeAvailableModules: true,
        removeEmptyChunks: true,
        mergeDuplicateChunks: true,
        chunkIds: 'named',
    },
    plugins: [
        new webpack.ProvidePlugin({process: "process/browser"}),
        new ForkTsCheckerWebpackPlugin(
            {
                typescript: {
                    configFile: './src/app/tsconfig.json'
                }
            }),
    ],
    module: {
        rules: [
            {
                test: /\.tsx?$/i,
                loader: "ts-loader",
            },
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                type: "asset/resource",
                generator: {
                    filename: 'assets/[hash][ext]',
                },
            },
        ],
    },
    resolve: {
        alias: {
            core: path.join(__dirname, "src", "core"),
            client: path.join(__dirname, "src", "client"),
            server: path.join(__dirname, "src", "server"),
            '<our company name>-typescript-common': path.resolve(__dirname, 'node_modules/<our company name>-typescript-common/'),
        },
        extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js", ".jsx"],
        fallback: {
            "util": require.resolve('util'),
            "os": false,
            "fs": false,
            "path": false,
            "zlib": false, 
            "buffer": require.resolve('buffer'),
            "http": require.resolve('stream-http'),
            "https": require.resolve('https-browserify'),
            "url": require.resolve('url'),
            "stream": require.resolve('stream-browserify'),
        },
    },
    output: {
        path: path.resolve(__dirname, "built"),
        filename: "client/js/client.js",
        clean: true,
    },
};

入口文件(Main.ts)

import { render } from 'react-dom';
import { AppWrapper } from './view/App/AppWrapper';

declare var window: any;

class CompanyName {
    constructor() {
        window.dataLayer = window.dataLayer || [];
        window._hsq = window._hsq || [];
        const rootHtmlElement = document.body.appendChild(document.createElement('div'));
        render(new AppWrapper({
            lang: window.appConfig.LANGUAGE,
            appConfig: window.appConfig,
            startupTimestamp: window.sts}).render(), rootHtmlElement);
    }
}

// tslint:disable-next-line:no-unused-expression
new CompanyName();

webpack.dev-watch.js

const { merge }             = require("webpack-merge");
const common                = require("./webpack.dev.js");
const path                  = require('path');

module.exports = merge(common, {
    watch: true,
    optimization: {
        minimize: false,
        mangleExports: false,
        mangleWasmImports: false
    },
    watchOptions: {
        ignored: [
            path.posix.resolve(__dirname, './node_modules'),
            path.posix.resolve(__dirname, './rfg'),
            path.posix.resolve(__dirname, './patches'),
            path.posix.resolve(__dirname, './built'),
        ],
        poll: 1500,
        aggregateTimeout: 1000,
    },
});
解决方案

针对React类组件+TypeScript+Webpack5的项目,配置热重载需以下步骤:

1. 安装必要依赖

执行命令安装Webpack热重载相关包:

yarn add --dev webpack-dev-server react-refresh @pmmmwh/react-refresh-webpack-plugin react-refresh-typescript
  • webpack-dev-server:提供开发服务器,支持热重载和实时编译
  • @pmmmwh/react-refresh-webpack-plugin:React官方推荐的热重载插件,兼容类组件
  • react-refresh:插件依赖的核心包
  • react-refresh-typescript:适配TypeScript的转换器

2. 修改Webpack开发配置(webpack.dev.js)

假设webpack.dev.js基于webpack.common.js合并,添加以下配置:

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

module.exports = merge(common, {
    mode: 'development',
    devtool: 'cheap-module-source-map', // 生成清晰的source map,便于调试
    plugins: [
        new webpack.HotModuleReplacementPlugin(), // 启用模块热替换
        new ReactRefreshWebpackPlugin(), // 启用React热重载
    ],
    module: {
        rules: [
            {
                test: /\.tsx?$/i,
                loader: "ts-loader",
                options: {
                    transpileOnly: true, // 只转译不做类型检查,提升编译速度(类型检查由fork-ts-checker-plugin处理)
                    getCustomTransformers: () => ({
                        before: [require('react-refresh-typescript')()], // 添加React Refresh的TypeScript转换器
                    }),
                },
                exclude: /node_modules/,
            },
        ],
    },
});

3. 更新webpack.dev-watch.js

替换原有watch配置为webpack-dev-server配置,利用其内置的热重载能力:

const { merge } = require("webpack-merge");
const devConfig = require("./webpack.dev.js");

module.exports = merge(devConfig, {
    devServer: {
        static: {
            directory: './built', // 指向编译后的静态文件目录
        },
        hot: true, // 启用热重载
        open: false, // 是否自动打开浏览器
        port: 3000, // 开发服务器端口
        client: {
            overlay: {
                errors: true, // 编译错误时显示全屏覆盖层
                warnings: false, // 警告不显示覆盖层
            },
        },
        historyApiFallback: true, // 支持React Router的SPA路由
    },
});

4. 修改入口文件(Main.ts)

调整React渲染逻辑,适配热重载:

import { render } from 'react-dom';
import { AppWrapper } from './view/App/AppWrapper';

declare var window: any;

// 创建根节点(优先使用已有root节点,避免重复创建)
const rootHtmlElement = document.getElementById('root') || (() => {
    const div = document.createElement('div');
    div.id = 'root';
    document.body.appendChild(div);
    return div;
})();

function renderApp() {
    window.dataLayer = window.dataLayer || [];
    window._hsq = window._hsq || [];
    // 类组件使用JSX语法渲染,确保React正确管理生命周期
    render(<AppWrapper 
        lang={window.appConfig.LANGUAGE}
        appConfig={window.appConfig}
        startupTimestamp={window.sts}
    />, rootHtmlElement);
}

renderApp();

// 热重载逻辑:模块更新时重新渲染
if (module.hot) {
    module.hot.accept('./view/App/AppWrapper', () => {
        renderApp();
    });
}

5. 更新package.json的启动脚本

添加开发服务器启动命令:

"scripts": {
    // ... 原有脚本
    "start-dev": "NODE_ENV=development webpack serve --config webpack.dev-watch.js",
}

6. 优化编译速度(可选)

  • 在webpack.common.js中设置cache: true,开启Webpack缓存,避免重复编译未修改的模块
  • 保持fork-ts-checker-webpack-plugin配置,让类型检查与Webpack编译并行,不阻塞构建

学习

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 07:45:59