Search code examples
reactjswebpackwebpack-hmrhmr

Webpack dev server is deleting bundle


I am trying to use the webpack dev-server with hot enabled in a React library - the main goal is to enable HMR. However, whenever the serve completes, it seems to delete the bundle file.

$ clear && npx webpack serve --mode=development

<i> [webpack-dev-server] Project is running at: "/tmp/webpack-dev-server.sock"
<i> [webpack-dev-server] Content not from webpack is served from '/usr/local/src/foo/View/public' directory
asset bundle_ff16b3001589ab53ae37.js 6.39 MiB [emitted] [immutable] (name: main)
orphan modules 1010 KiB [orphan] 233 modules
runtime modules 28.5 KiB 14 modules
cacheable modules 5.65 MiB
  modules by path ./node_modules/ 5.26 MiB 219 modules
  modules by path ./src/ 394 KiB
    modules by path ./src/components/ 389 KiB 90 modules
    modules by path ./src/lib/ 2.42 KiB
      modules by path ./src/lib/util/*.ts 778 bytes 2 modules
      + 2 modules
    modules by path ./src/*.js 2.72 KiB
      ./src/export.js 1.06 KiB [built] [code generated]
      ./src/constants.js 808 bytes [built] [code generated]
      ./src/helpers.js 899 bytes [built] [code generated]
external "React" 42 bytes [built] [code generated]
external "ReactDOM" 42 bytes [built] [code generated]
webpack 5.90.3 compiled successfully in 8966 ms

...

$ ll public
total 0

webpack.config.js

const webpack = require('webpack');
const path = require('path');
const fs = require('fs');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

/**
 * Path resolver - automatically resolve alias from @folder into ./src/folder
 * @type {string}
 */
const srcDir = path.resolve(__dirname, 'src');
const aliases = fs.readdirSync(srcDir)
    .filter((item) => fs.statSync(path.join(srcDir, item)).isDirectory())
    .reduce((acc, item) => {
        acc[`@${item}`] = path.join(srcDir, item);
        return acc;
    }, {});

let config = {
    entry: path.join(__dirname, 'src/export.js'),
    watchOptions: {
        aggregateTimeout: 200,
        poll: 1000,
    },
    module: {
        rules: [
            {
                test: [/\.js$/, /\.jsx$/],
                exclude: [/node_modules/],
                loader: 'babel-loader'
            },
            {
                test: [/\.tsx?$/, /\.ts?$/],
                exclude: [/node_modules/],
                use: 'ts-loader'
            },
            {
                test: /\.css$/,
                exclude: [/node_modules/],
                loader: 'css-loader'
            },
            {
                test: /\.scss$/,
                exclude: [/node_modules/],
                use: ['style-loader', 'css-loader', 'sass-loader']
            },
            {
                test: /\.(png|jpe?g|gif)$/i,
                exclude: [/node_modules/],
                type: 'asset/resource'
            },
            {
                test: /\.svg$/,
                type: 'asset',
                use: ['@svgr/webpack'],
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/i,
                type: 'asset/inline',
                loader: 'url-loader',
            }
        ]
    },
    plugins: [
        new webpack.LoaderOptionsPlugin({
            options: {
                globalObject: '(typeof self !== \'undefined\' ? self : this)'
            }
        }),
        new webpack.ProvidePlugin({
            process: 'process/browser',
        }),
    ],
    resolve: {
        fallback: {
            'process/browser': require.resolve('process/browser'),
        },
        alias: {
            ...aliases,
            '@scss': path.resolve(__dirname, 'src/static/scss'),
        },
        extensions: ['', '.js', '.jsx', '.ts', '.tsx']
    },
    output: {
        path: path.join(__dirname, '/public'),
        library: {
            name: 'foo',
            type: 'umd'
        },
        filename: 'bundle_[contenthash].js',
        clean: true
    },
    externals: {
        'react': 'React',
        'react-dom': 'ReactDOM'
    },
};

module.exports = (env, argv) => {
    if (argv.mode === 'production') {
        config.optimization = {
            ...config.optimization,
            minimizer: [new TerserPlugin({
                extractComments: false,
            })],
            minimize: true,
            plugins: [
                ...config.plugins,
                new CleanWebpackPlugin()
            ]
        };
    } else {
        config = {
            ...config,
            // development by default
            mode: 'development',
            devServer: {
                hot: true,
                ipc: true,
                watchFiles: ['./src/**/**'],
            },
        };
    }

    return config;
};


Solution

  • Webpack dev-server keeps the bundle in memory and not statically, as reproduced from the above example. To preserve the bundle in the disk, the webpack-dev-middleware has to be used with the writeToDisk option.

    devServer: {
        devMiddleware: {
            writeToDisk: true,
        },
        hot: true
    }