Search code examples
reactjstypescriptasp.net-corewebpackwebpack-hmr

Webpack HMR not reloading imported TypeScript modules


I'm running a ASP.NET Core website with a React frontend written in TypeScript. I have set up HMR in the backend middleware:

app.UseWebpackDevMiddleware(new Microsoft.AspNetCore.SpaServices.Webpack.WebpackDevMiddlewareOptions
{
    HotModuleReplacement = true
});

and my webpack.config.js file like this:

const path = require('path');

module.exports = {
    mode: 'development',
    entry: { main: './scripts/app.tsx' },
    output: {
        path: path.resolve(__dirname, './wwwroot/js/dist'),
        filename: 'bundle.js',
        publicPath: '/dist/'
    },
    resolve: {
        extensions: ['*', '.js', '.jsx', '.tsx']
    },
    module: {
        rules: [
            {
                test: /\.ts|\.tsx$/,  include: /scripts/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {

                            "plugins" : ['react-hot-loader/babel'],
                            "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"]
                        }
                    }
                ]
            }
        ]
    }
};

The webpack is pointing at this file, app.tsx:

import * as React from "react";
import * as ReactDOM from "react-dom";
import { Application } from "./Application";

ReactDOM.render(
    <div><Application /></div>,
    document.getElementById("example")
)
if (module.hot) {
    module.hot.accept()
}

I have the bundle.js file used in a web page. When I run the web server and browse to the page, I get [HMR] connected logged to the console. When I edit and save the app.tsx file, I get the expected output:

client.js:234 [HMR] bundle rebuilding
client.js:242 [HMR] bundle rebuilt in 69ms
process-update.js:39 [HMR] Checking for updates on the server...
process-update.js:112 [HMR] Updated modules:
process-update.js:114 [HMR]  - ./scripts/app.tsx
process-update.js:119 [HMR] App is up to date.

However, when I edit and save application.tsx, (which includes module.hot.accept() at the bottom) I get nothing - neither the webserver nor the browser has any output. I think this may be because the HMR is configured to only look at the app.tsx file:

[0] multi webpack-hot-middleware/client?path=__webpack_hmr&dynamicPublicPath=true ./scripts/app.tsx 40 bytes {main}

Does anyone have any idea about what could be the issue here? It works for the file explicitly declared in webpack config file, but not for the modules which it is importing - shouldn't the other files be included automatically? According to https://webpack.js.org/guides/hot-module-replacement/ I think I have everything set up correctly, but I wasn`t able to use it exactly because the configuration is different using the ASP.NET Core middleware.

Thanks in advance.


Solution

  • The problem was that webpack.config.js was setup incorrectly, and was wasn't using react-hot-reloader properly.

    webpack.config.js

    const webpack = require("webpack");
    const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
    const path = require('path');
    const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
    const CleanWebpackPlugin = require('clean-webpack-plugin');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = (env) => {
        const isDevBuild = !(env && env.prod);
    
        const outputDir = (env && env.publishDir)
            ? env.publishDir
            : __dirname;
    
        return [{
            mode: isDevBuild ? 'development' : 'production',
    
            devtool: 'inline-source-map',
    
            stats: {modules: false},
    
            entry: {
                main: ['./scripts/app.tsx'],
            },
    
            watchOptions: {
                ignored: /node_modules/
            },
    
            output: {
                filename: "dist/bundle.js",
                path: path.join(outputDir, 'wwwroot'),
                publicPath: '/'
            },
    
            resolve: {
                extensions: [".ts", ".tsx", ".js", ".jsx"]
            },
    
            devServer: {
                hot: true
            },
    
            module: {
                rules: [
                    {
                        test: /\.(j|t)sx?$/,
                        exclude: /node_modules/,
                        use: {
                            loader: "babel-loader",
                            options: {
                                cacheDirectory: true,
                                babelrc: false,
                                presets: [
    
                                    "@babel/preset-env",
                                    "@babel/preset-typescript",
                                    "@babel/preset-react"
                                ],
                                plugins: [['@babel/plugin-proposal-decorators', {legacy: true}],
                                    ['@babel/plugin-proposal-class-properties', {loose: true}],
                                    "react-hot-loader/babel",
                                    "@babel/plugin-transform-runtime"
                                ]
                            }
                        }
                    }
                ]
            },
    
            plugins: [
                new CleanWebpackPlugin(path.join(outputDir, 'wwwroot', 'dist')),
                new ForkTsCheckerWebpackPlugin()
            ]
        }];
    };
    
    

    scripts/app.tsx

    import * as React from "react";
    import * as ReactDOM from "react-dom";
    import App from "./Application";
    
    ReactDOM.render(<App/>, document.getElementById("rootComponent"));
    

    Class declaration in scripts/Application.tsx:

    class Application extends React.Component<any,any> {   }
    

    The bottom of scripts/Application.tsx:

    const App: FunctionComponent = () => <Application />;
    export default hot(App);
    

    If anyone else is struggling with this I suggest reading through the example GitHub repositories which exist for using HMR, typescript and ASP.NET Core