noe
noe

Reputation: 347

Webpack HMR not reloading HTML file

I have a simple HMR setup for reloading typescript files and postcss files. And they work perfectly well and modules reload without a page refresh. But when I change my HTML files, the website doesn't reload on it's own and the HTML content is not being hot-reloaded in.

This is my webpack config file:

const { resolve } = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
  entry: resolve(__dirname, 'src/main.ts'),
  output: {
    path: resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: 'awesome-typescript-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'style-loader'
          },
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
          {
            loader: 'postcss-loader',
          }
        ]
      }
    ]
  },
  devtool: 'source-map',
  mode: 'development',
  plugins: [
    new HtmlWebpackPlugin({
      template: resolve(__dirname, './src/index.html'),
    }),
    new webpack.HotModuleReplacementPlugin(),
    new CleanWebpackPlugin(resolve(__dirname, 'dist'))
  ],
  devServer: {
    contentBase: resolve(__dirname, 'dist'),
    port: 9000,
    hot: true,
    open: true,
    progress: true,
  }
}

Upvotes: 1

Views: 4820

Answers (3)

alwxkxk
alwxkxk

Reputation: 118

With webpack 5,this setting is work for me:

  devServer: {
    hot:true,
    open:true,
    watchFiles: ['src/**/*']
  },

Upvotes: 5

szegheo
szegheo

Reputation: 4425

Not sure if this is the case, but if you only want to extend your current HMR config to do a browser reload when your outer html/view files changed, then you can do it with a few extra lines using chokidar.

Maybe you already have it, because webpack-dev-server is using chokidar internally, but if not found then install it first with npm or yarn:

npm install chokidar --save
yarn add -D chokidar

Then require it in your webpack config:

const chokidar = require('chokidar');

Then in your devServer config:

devServer: {
  before(app, server) {
    chokidar.watch([
      './src/views/**/*.html'
    ]).on('all', function() {
      server.sockWrite(server.sockets, 'content-changed');
    })
  },

Also check the API for more options.

I'm using it with Webpack4. Don't know if it works with earlier versions...

Hope it helps you or others looking for this situation.

Upvotes: 2

Goran.it
Goran.it

Reputation: 6299

Problem is that html-webpack-plugin doesn't react to change and doesn't trigger the hmr. In order to achieve that you could try something like this:

const { resolve } = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
let devServer; // set below in devserver part

function reloadHtml() {
  this.plugin('compilation',
    thing => thing.plugin('html-webpack-plugin-after-emit', trigger));
  const cache = {};
  function trigger(data, callback) {
    const orig = cache[data.outputName];
    const html = data.html.source();
    if (orig && orig !== html)
      devServer.sockWrite(devServer.sockets, 'content-changed');
    cache[data.outputName] = html;
    callback();
  }
}

module.exports = {
  entry: resolve(__dirname, 'src/main.ts'),
  output: {
    path: resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: 'awesome-typescript-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'style-loader'
          },
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
          {
            loader: 'postcss-loader',
          }
        ]
      }
    ]
  },
  devtool: 'source-map',
  mode: 'development',
  plugins: [
    reloadHtml,
    new HtmlWebpackPlugin({
      template: resolve(__dirname, './src/index.html'),
    }),
    new webpack.HotModuleReplacementPlugin(),
    new CleanWebpackPlugin(resolve(__dirname, 'dist'))
  ],
  devServer: {
    before(app, server) {
      devServer = server;
    },
    contentBase: resolve(__dirname, 'dist'),
    port: 9000,
    hot: true,
    open: true,
    progress: true,
  }
}

If you get :

DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead

You can change reloadHtml to this:

function reloadHtml() {
  const cache = {}
  const plugin = {name: 'CustomHtmlReloadPlugin'}
  this.hooks.compilation.tap(plugin, compilation => {
    compilation.hooks.htmlWebpackPluginAfterEmit.tap(plugin, data => {
      const orig = cache[data.outputName]
      const html = data.html.source()
      if (orig && orig !== html) {
        devServer.sockWrite(devServer.sockets, 'content-changed')
      }
      cache[data.outputName] = html
    })
  })
}

Upvotes: 2

Related Questions