Search code examples
vuejs2webpack-dev-servervue-loaderhot-module-replacement

Hot reload on vue-loader only works with structural changes to the template


I've setup vue + vue-loader + HMR on an existing project.

It works mostly well, vue components are loaded and rendered correctly.

The hot module reload part is configured and loading.

However, it doesn't seem to apply updates when the change is only a text node of the component.

For example, if I have a component like this:

<template lang="html">
  <div>
    <h1>I'm a Component</h1>
  </div>
</template>

<script>
export default {
}
</script>

And I change it to this:

<template lang="html">
  <div>
    <h1>I'm a Component updated</h1>
  </div>
</template>

<script>
export default {
}
</script>

Then I can see the HMR updates in the browser console.

console output

But the component doesn't update, it still says "I'm a Component".

However, if I slightly alter the html structure of the component like this:

<template lang="html">
  <div>
    <h1>I'm a Component updated</h1>
    <p>do it</p>
  </div>
</template>

<script>
export default {
}
</script>

Then the console shows the HMR log but this time the component update.

The behaviour is consistently the same, text change = no update.

The loader doesn't have anything particular in its config.

{
test: /\.vue$/,
loader: 'vue-loader',
options: {
    loaders: {
    }

}

The dev server is launch via gulp with this task:

// Start a webpack-dev-server
const hot_webpack_config = cloneDeep(webpack_config)

hot_webpack_config.output.filename = 'frontend.hot.js'
hot_webpack_config.output.publicPath = PUBLIC_DEV_SERVER
hot_webpack_config.entry.unshift("webpack-dev-server/client?"+PUBLIC_DEV_SERVER, "webpack/hot/dev-server");
hot_webpack_config.plugins.push(new webpack.HotModuleReplacementPlugin())

var compiler = webpack(hot_webpack_config)

var WebpackDevServer = require("webpack-dev-server")

new WebpackDevServer(compiler, {
    //noInfo: true,
    hot: true,
    stats: {
      assets: false,
      colors: true,
      version: false,
      timings: false,
      chunks: false,
      chunkModules: false
    },
    inline: true,
    publicPath: hot_webpack_config.output.publicPath,
    headers: { "Access-Control-Allow-Origin": "*" }

}).listen(4000, "localhost", function(err) {
    if(err) throw new gutil.PluginError("webpack-dev-server", err)
    // Server listening
    gutil.log(chalk.blue("Hot server listening at http://0.0.0.0:4000"))

})

Not sure where else to look to fix this. As mentioned, it kinda works, just not for text node updates.

I've looked at the template generated by the vue-cli webpack-simple sample and the code is somewhat similar (except dev server is launch from node command line rather than manually building it), theirs does update text node, mine doesn't :(

Any clues?

Update: versions of relevant dependencies

vue 2.3.4
vue-loader 13.0.0 
vue-template-compiler 2.3.4 
webpack 2.6.1 
webpack-dev-server 2.5.0 

Update 2: applying any modification to the <script> part of the component does cause the text nodes to refresh.

Update 3:

// webpack_config.js
/* jshint node: true */
var webpack = require('webpack'),
    path = require('path'),
    package = require('./package.json'),
    gutil = require('gulp-util'),
    chalk = require('chalk');

const PUBLIC_DEV_SERVER = package.config.build.PUBLIC_DEV_SERVER
const ENTRY = package.config.build.ENTRY

var PROD = process.env.NODE_ENV == 'production';

let config = {
    entry: [
        ENTRY
    ],
    output: {
        path: path.join(__dirname, 'resources', 'js'),
        filename: 'frontend.min.js'
    },
    module: {

        rules: [{
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    loaders: {
                    }

                }
            }, {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
                query: {
                    presets: ['es2015', 'stage-0'],
                }
            }
        ]
    },
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.esm.js'
        }
    }

};

if (process.env.NODE_ENV === 'production') {
    gutil.log(chalk.red("Build for production"));
    config.devtool = '#source-map'
    config.entry = [
        ENTRY
    ];
    // http://vue-loader.vuejs.org/en/workflow/production.html
    config.plugins = (config.plugins || []).concat([
        new webpack.DefinePlugin({
          'process.env': {
            NODE_ENV: '"production"'
          }
        }),
        new webpack.optimize.UglifyJsPlugin({
          sourceMap: true,
          compress: {
            warnings: false
          }
        }),
        new webpack.LoaderOptionsPlugin({
          minimize: true
        })
      ])
} else {
    gutil.log(chalk.red("Build for development"));
    config.devtool = '#eval-source-map' //"cheap-module-eval-source-map"
    config.plugins = [
    ]
}

module.exports = config

PUBLIC_DEV_SERVER is set to "http://localhost:4000/"

ENTRY is set to "./src/js/frontend.js"


Solution

  • I've tried multiple things to fix this, I thought some settings fixed it but reverting back to previous versions suddenly started working too.

    At the end, i think the fix was simply:

    rm -rf node_modules/
    npm i
    

    But I don't know exactly which part of it made it fall apart.