Html-webpack-plugin: How to watch and reload index.html by using webpack-dev-server together with html-webpack-plugin

Created on 17 Oct 2015  路  29Comments  路  Source: jantimon/html-webpack-plugin

Every time I change the index.html template, I need to re-run the build task now.

Most helpful comment

i think yet a better solution was doing something like this

devServer: {
    contentBase: [ PathToTheFolderWhereYourHTMLsLives ],
    watchContentBase: true
}

this approach doesnt require any "requiring" inside js files...

All 29 comments

Hmm this should work - could you please try the 2.0 version or provide a demo?

Same problem i met.

When i run webpack-dev-server --inline --hot --colors --content-base=./www, it prints webpack: bundle is now INVALID.

plugin version:

"html-webpack-plugin": "^2.1.0",
"webpack-dev-server": "^1.14.0"

Same

var webpack            = require('webpack')
  , path               = require('path')
  , HtmlWebpackPlugin  = require('html-webpack-plugin')
  , CleanWebpackPlugin = require('clean-webpack-plugin')
  , ExtractTextPlugin  = require("extract-text-webpack-plugin")
  , autoprefixer       = require('autoprefixer')

module.exports = {
  devServer: {
    historyApiFallback: true,
    hot: true,
    inline: true,
    progress: true,
    contentBase: './app',
    port: 8080,
  },

  entry: [
    path.resolve(__dirname, 'app/src/'),
  ],

  output: {
    path: path.resolve(__dirname, 'app/build/'),
    filename: './js/bundle?[hash].js'
  },

  module: {
    loaders: [
      {
        test: /\.jade$/,
        loader: 'jade'
      },

      {
        test: /\.js$/,
        include: path.resolve(__dirname, 'app/src'),
        exclude: /node_modules/,
        loader: 'babel'
      },

      {
        test: /\.styl$/,
        include: path.resolve(__dirname, 'app/src'),
        loader: ExtractTextPlugin.extract("style-loader", "css-loader!postcss-loader!stylus-loader")
      },

      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract("style-loader", "css-loader!postcss-loader")
      },
    ]
  },

  postcss: [ autoprefixer ],

  plugins: [
    new ExtractTextPlugin("./css/style?[hash].css"), // separate css

    new CleanWebpackPlugin(['app/build'], {
      verbose: true, 
      dry: false
    }),

    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),

    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './app/src/jade/index.jade',
      inject: 'body',
    }),

    new HtmlWebpackPlugin({
      filename: 'homepage.html',
      template: './app/src/jade/homepage.jade',
      inject: 'body',
    }),
  ]
}

webpack-dev-server --devtool eval --progress --hot --colors --host 0.0.0.0 --content-base app

webpack: bundle is now INVALID.
Hash: 020f485713bee7553bdf  
Version: webpack 1.12.14
Time: 154ms
                               Asset       Size  Chunks             Chunk Names
./css/style?020f485713bee7553bdf.css    1.93 kB       0  [emitted]  main
                          index.html  297 bytes          [emitted]  
chunk    {0} ./js/bundle?020f485713bee7553bdf.js, ./css/style?020f485713bee7553bdf.css (main) 213 kB
     + 79 hidden modules
Child html-webpack-plugin for "homepage.html":
    chunk    {0} homepage.html 6.11 kB
         + 3 hidden modules
Child html-webpack-plugin for "index.html":
    chunk    {0} index.html 6.1 kB [rendered]
        [0] ./~/html-webpack-plugin/lib/loader.js!./app/src/jade/index.jade 394 bytes {0} [built]
         + 2 hidden modules
webpack: bundle is now VALID.
  "devDependencies": {
    "autoprefixer": "^6.3.3",
    "babel-loader": "^6.2.3",
    "babel-preset-es2015": "^6.5.0",
    "babel-preset-stage-0": "^6.5.0",
    "clean-webpack-plugin": "^0.1.8",
    "css-loader": "^0.23.1",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.8.5",
    "html-webpack-plugin": "^2.9.0",
    "jade": "^1.11.0",
    "jade-loader": "^0.8.0",
    "jquery": "^2.2.1",
    "normalize.css": "^3.0.3",
    "postcss-loader": "^0.8.1",
    "precss": "^1.4.0",
    "style-loader": "^0.13.0",
    "stylus-loader": "^1.5.1",
    "webpack-dev-server": "^1.14.1"
  }

"html-webpack-plugin": "^2.8.1",
"webpack-dev-server": "^1.14.1"

getting the same error

any updates to this issue?

@usergit I am sorry but I really don't get the problem - every thing works as expected.

You change the html file, webpack sets the state to invalid and recompiles everything.

Only hot-module replacement is not possible.

馃槰 unfortunately and this is the things i met.

When i change the html file, webpack does recompiled but page still have no refresh action.

  "devDependencies": {
    "html-webpack-plugin": "^2.24.1",
    "webpack": "^1.13.2",
    "webpack-dev-server": "^1.16.2"
  }
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'index.html')
    })
  ]

webpack-dev-server --inline --hot

@viko16 there is no auto refresh by now - would be open for pull requests :)

Temporary solutions are always the best 馃槅
Just require template in your entry file (app.js or sth):

import "./index.pug";

And you will have fast bundling time + reload but don't do this on production 馃槈

@pspeter3 wow that's nice - is there any way we could move this into this plugin?

@jantimon I can try. I'll fork and submit a PR

So I think the issue is that the plugin doesn't have access to the hot middleware plugin. It seems like the Vue dev server is doing the best thing.

@19majkel94 Can you elaborate how you did it? i did import in my entry and did not reload the html.

i came a long way defeating one by one.. .ts, .babel, .scss.... and then i got stuck at .html, no hot reload for this on vanilla html.... nor even auto reload.. any way to get this working some other how ?

It looks like that this is really hard to accomplish on compiler level but could be accomplished on the level of the dev-server. I guess it might also be possible to write a custom html-webpack-hmr-plugin which would introduce a new entry point e.g. using https://github.com/jantimon/extra-entry-webpack-plugin which does the hmr - maybe you want to start working on that?

Running it doesn't generate the /dist folder files. Instead, if i just run webpack --watch and lite-server, it works for me. For some reason webpack-dev-server does kick off html plugin, but for some reason the plugin doesn't write to /dist afterwards.

@windmaomao that's completely unrelated to this thread - it's a performance boost by webpack-dev-server and can be solved by https://github.com/jantimon/html-webpack-harddisk-plugin or by setting the output filesystem - for further information please open a stackoverflow question

In case if needed, here's working setup with hot reload of .js and index.html
For hot reload development:
npm run dev

For production:
npm run build

webpack.config.js
```var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {},
devServer: {
historyApiFallback: true,
noInfo: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}

if (process.env.NODE_ENV !== 'production') {
module.exports.plugins = (module.exports.plugins || []).concat([
new HtmlWebpackPlugin({
template: './index.html'
})
])
}

if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
})
])
}

package.json
```{
  "name": "test1",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --inline --hot",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mithril": "^1.0.0"
  },
  "devDependencies": {
    "babel-core": "^6.22.1",
    "babel-loader": "^6.2.10",
    "babel-plugin-transform-react-jsx": "^6.22.0",
    "babel-preset-es2015": "^6.22.0",
    "cross-env": "^3.1.4",
    "file-loader": "^0.10.0",
    "html-webpack-plugin": "^2.28.0",
    "webpack": "^2.2.1",
    "webpack-dev-server": "^2.3.0"
  }
}

/src/main.js

if (process.env.NODE_ENV !== 'production') {
    require('file-loader!../index.html')
}

test1_complete_package2.zip

i think yet a better solution was doing something like this

devServer: {
    contentBase: [ PathToTheFolderWhereYourHTMLsLives ],
    watchContentBase: true
}

this approach doesnt require any "requiring" inside js files...

My approach is a hot reload and not full page reload.
If you change little piece of .js code you don't need to reload whole page.

Again, I just hate the fact that I need to add 'unnecessary' stuff in my js entries.. And somehow remember to remove that.. Or just leave there... Yet the one I showed still has HMR with js but with the html files will do a full page reload yes. Both ways not ideal.. Would rather study a possibility to create a plugin to inject html webpack plug-in outputs as an webpack entry and somehow get it done automatically...

Using reload-html-webpack-plugin is done !!

When you change the templates, the html-webpack-plugin does recompiling them, but it just not to re-write the new generated html files to the disk. Given this, even if the devServer reloading the pages, the result will not change. If you are in this case, try this plugin:

https://github.com/jantimon/html-webpack-harddisk-plugin

No need to write to disk or open more sockets: just use the html-webpack-plugin-after-emit event to trigger a content-changed message from the devserver::

let devServer; // set below
module.exports = {
  plugins: [reloadHtml],
  devServer: {
    before(app, server) {
      devServer = server;
    }
  }
}
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();
    // plugin seems to emit on any unrelated change?
    if (orig && orig !== html)
      devServer.sockWrite(devServer.sockets, 'content-changed');
    cache[data.outputName] = html;
    callback();
  }
}

I'm sure someone can make a nice plugin out of this with more style, but this works for me.

reload-html-webpack-plugin doesn't seem to work with Webpack 4.

Thanks @avdd for the quick work-around, which worked for me (even though I wish this somehow worked out of the box between html-webpack-plugin and webpack-dev-server).

If anyone else runs into Webpack 4's deprecation warning:

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()
      // plugin seems to emit on any unrelated change?
      if (orig && orig !== html) {
        devServer.sockWrite(devServer.sockets, 'content-changed')
      }
      cache[data.outputName] = html
    })
  })
}

@sebastianseilund not able to get this working compilation.hooks.htmlWebpackPluginAfterEmit.tap(..) produces:

TypeError: Cannot read property 'tap' of undefined

and indeed Object.keys( compilation.hooks ) is:

[ 'buildModule',
  'rebuildModule',
  'failedModule',
  'succeedModule',
  'finishModules',
  'finishRebuildingModule',
  'unseal',
  'seal',
  'optimizeDependenciesBasic',
  'optimizeDependencies',
  'optimizeDependenciesAdvanced',
  'afterOptimizeDependencies',
  'optimize',
  'optimizeModulesBasic',
  'optimizeModules',
  'optimizeModulesAdvanced',
  'afterOptimizeModules',
  'optimizeChunksBasic',
  'optimizeChunks',
  'optimizeChunksAdvanced',
  'afterOptimizeChunks',
  'optimizeTree',
  'afterOptimizeTree',
  'optimizeChunkModulesBasic',
  'optimizeChunkModules',
  'optimizeChunkModulesAdvanced',
  'afterOptimizeChunkModules',
  'shouldRecord',
  'reviveModules',
  'optimizeModuleOrder',
  'advancedOptimizeModuleOrder',
  'beforeModuleIds',
  'moduleIds',
  'optimizeModuleIds',
  'afterOptimizeModuleIds',
  'reviveChunks',
  'optimizeChunkOrder',
  'beforeChunkIds',
  'optimizeChunkIds',
  'afterOptimizeChunkIds',
  'recordModules',
  'recordChunks',
  'beforeHash',
  'contentHash',
  'afterHash',
  'recordHash',
  'record',
  'beforeModuleAssets',
  'shouldGenerateChunkAssets',
  'beforeChunkAssets',
  'additionalChunkAssets',
  'additionalAssets',
  'optimizeChunkAssets',
  'afterOptimizeChunkAssets',
  'optimizeAssets',
  'afterOptimizeAssets',
  'needAdditionalSeal',
  'afterSeal',
  'chunkHash',
  'moduleAsset',
  'chunkAsset',
  'assetPath',
  'needAdditionalPass',
  'childCompiler',
  'normalModuleLoader',
  'optimizeExtractedChunksBasic',
  'optimizeExtractedChunks',
  'optimizeExtractedChunksAdvanced',
  'afterOptimizeExtractedChunks' ]

webpack 4.8
html-webpack-plugin 3.20

NVM, it was a result of bad ordering in webpack-merge, where our custom reloadHtml plugin was coming before HtmlWebpackPlugin

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings