webpack-livereload-plugin icon indicating copy to clipboard operation
webpack-livereload-plugin copied to clipboard

Live CSS reloading

Open rstacruz opened this issue 8 years ago • 22 comments

I'm using extract-text-webpack-plugin to write .css files and webpack-livereload-plugin reloads the entire page when a CSS file changes.

tiny-lr has support for liveCSS to reload the CSS in place.

Any clues on how I can take advantage of that?

rstacruz avatar Dec 21 '16 03:12 rstacruz

For me the whole page seems to be reloaded no matter if I use inline css or the extract-text plugin. Have you managed to get live reload working properly without extracted css?

0x80 avatar Dec 28 '16 12:12 0x80

For me the whole page seems to be reloaded no matter if I use inline css or the extract-text plugin.

Yes, that's what I'm getting. What I'm expecting to happen, though, is that the CSS reloads in-page without needing a full page reload.

Have you managed to get live reload working properly without extracted css?

Yep, but I haven't tried with CSS! JS works fine though

rstacruz avatar Dec 31 '16 11:12 rstacruz

@rstacruz I was unaware of tiny-lr's liveCSS support. Looks useful for projects using the extract-text plugin.

My guess as to what's causing a full refresh is that livereload-plugin is notifying about all files rather than just those that have changed. include should only be of files with a new hash.

LiveReloadPlugin.prototype.done = function done(stats) {
  var hash = stats.compilation.hash;
  var childHashes = (stats.compilation.children || []).map(child => child.hash);
  var files = Object.keys(stats.compilation.assets);
  var include = files.filter(function(file) {
    return !file.match(this.ignore);
  }, this);

  if (this.isRunning && (hash !== this.lastHash || !arraysEqual(childHashes, this.lastChildHashes)) && include.length > 0) {
    this.lastHash = hash;
    this.lastChildHashes = childHashes;
    setTimeout(function onTimeout() {
      this.server.notifyClients(include);
    }.bind(this));
  }
};

statianzo avatar Jan 01 '17 03:01 statianzo

I was having the same issue as you @rstacruz. What I ended up doing was moving the livereload to a npm script. I've also just discovered you can chain tasks in npm scripts. Example. "scripts": { "build": "webpack --progress --watch", "reload": "livereload dist/ --port 35729", "dev": "npm run -s reload & npm run -s build" },

So npm run dev will start webpack and the livereload.

Apologies as this isn't directly related to the this project. Hopefully might be useful.

defmech avatar Jan 18 '17 22:01 defmech

@defmech May I know how do you compile you SCSS? I am trying your method but the entire page is refreshed even I just changed some SCSS. Also the compile time is long because it attempt to recompile all the JSs.

kayue avatar Jan 24 '17 07:01 kayue

@kayue Webpack by default packs it into the javascript. I'm extracting the CSS using the ExtractTextPlugin. Here's a snippet of my webpack.config.js.

module: {
    loaders: [{
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
      query: {
        presets: [
          ["es2015", {
            "modules": false
          }]
        ]
      }
    }, {
      test: /\.scss$/,
      loader: ExtractTextPlugin.extract('css-loader!postcss-loader!sass-loader')
    }]
  },
  plugins: [
    // css optimize
    new OptimizeCssAssetsPlugin(),
    // sass
    new ExtractTextPlugin({
      filename: '../css/main.css',
      allChunks: true
    }),
    // uglify js
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      },
      output: {
        comments: false
      },
      sourceMap: true
    }),
    // env plugin
    new webpack.DefinePlugin({
      'proccess.env': {
        NODE_ENV: JSON.stringify(nodeEnv)
      }
    })
  ],

defmech avatar Jan 24 '17 09:01 defmech

@defmech I have very similar setup and using ExtractTextPlugin too, but how do you prevent entire page being refreshed?

I have an import statement in my JS (import './bootstrap.scss';), maybe this makes Webpack think I am changing some JS? But if I don't import CSS in my JavaScript, then Webpack won't monitor CSS file for changes at all...

Thank you for your time.

kayue avatar Jan 25 '17 03:01 kayue

@kayue I've uploaded my build system https://github.com/defmech/WebPack-2-Build-System so you see how I've set up my environment. Hopefully that helps.

defmech avatar Jan 25 '17 16:01 defmech

Looks like the current implementation, referenced in @statianzo's https://github.com/statianzo/webpack-livereload-plugin/issues/28#issuecomment-269891061, reports all chunks to LiveReload on each compilation, no matter what has changed.

Replacing this logic with the a changedFiles array from the plugin example docs, I easily was able to make live CSS reloading work. I'll put together a PR next week unless someone jumps on it.

pmowrer avatar May 12 '17 22:05 pmowrer

changedFiles looks like the right source. Thanks for taking on the PR!

statianzo avatar May 14 '17 01:05 statianzo

Sure thing! I'll remove the hash logic as well unless there's a good reason to keep it?

(Referencing:) (hash !== this.lastHash || !arraysEqual(childHashes, this.lastChildHash))

pmowrer avatar May 14 '17 23:05 pmowrer

The logic was there so a reload would only get triggered when there was a change. If your solution keeps that behavior, then feel free to rip out the hash comparison code.

statianzo avatar May 15 '17 01:05 statianzo

I'm afraid I'll have to punt on this for now. Looks like live reloading of <style> tags isn't supported, which makes this a pointless endeavor for us.

pmowrer avatar May 15 '17 16:05 pmowrer

Ahh, yes. It'll only work for <link> or @import based on the href attribute. CSS in JS and raw <style> tags would cause a full refresh. https://github.com/livereload/livereload-js/blob/03b09762e930f6c6c67ee270c208d1c11de0247f/src/reloader.coffee#L149-L179

statianzo avatar May 15 '17 22:05 statianzo

Posted a PR fix here: https://github.com/statianzo/webpack-livereload-plugin/pull/33 it works perfectly with this configuration:

    }, {
      test: /\.css$/,
      use: ExtractTextPlugin.extract({
        fallback: 'style-loader',
        use: {
          loader: 'css-loader',
          options: 'url=false&sourceMap',
        }
      })
    }]

tbranyen avatar Jun 02 '17 23:06 tbranyen

Couldn't get @statianzo snippet to work (it always included all files). To simplify everything I just check the md5 sum from disk instead. Can probably be improved to not recalculate hashes, but didn't want to spend too long on it and this works fine for my setup.

const LivereloadWebpackPlugin = require('webpack-livereload-plugin')
const md5File = require('md5-file')

LivereloadWebpackPlugin.prototype.done = function done(stats) {
    this.fileHashes = this.fileHashes || {}

    const fileHashes = {}
    for (let file of Object.keys(stats.compilation.assets)) {
        fileHashes[file] = md5File.sync(stats.compilation.assets[file].existsAt)
    }

    const toInclude = Object.keys(fileHashes).filter((file) => {
        if (this.ignore && file.match(this.ignore)) {
            return false
        }
        return !(file in this.fileHashes) || this.fileHashes[file] !== fileHashes[file]
    })

    if (this.isRunning && toInclude.length) {
        this.fileHashes = fileHashes
        console.log('Live Reload: Reloading ' + toInclude.join(', '))
        setTimeout(
            function onTimeout() {
                this.server.notifyClients(toInclude)
            }.bind(this)
        )
    }
}

sveint avatar Aug 09 '18 12:08 sveint

Thanks @sveint ! This worked out perfect for me.

claudio-borges avatar Sep 04 '18 12:09 claudio-borges

There is still issues with the CSS reloading when using mini-css-extract-plugin. I can see that styles changes without full reload but after short time page does full reload. @sveint snippet does not work with plugin version 3 anymore because the plugin code is changed. If using the useSourceHash option to true I get error

[webpack-cli] Error: Content and Map of this Source is not available (only size() is supported

Webpack 5 Livereload plugin 3.0.1

veke avatar Apr 20 '21 08:04 veke

Well, I ended up to modify the existing @sveint "hack" to work with version 3 of this plugin. In case someone else is needing this.

const md5File = require('md5-file');
const LivereloadWebpackPlugin = require('webpack-livereload-plugin');
const liveReload = new LivereloadWebpackPlugin({ your configs });
const outputPath = path.resolve(__dirname, 'your_output_path'); // probably can be resolved also from the hook

liveReload._afterEmit = (compilation) => {
    const self = liveReload;
    const fileHashes = {};
    self.fileHashes = self.fileHashes || {};

    compilation.getAssets().forEach(asset => {
        fileHashes[asset.name] = md5File.sync(`${outputPath}/${asset.name}`);
    });

    const include = Object.keys(fileHashes).filter(file => {
        if (self.options.ignore && file.match(self.options.ignore)) {
            return false;
        }
        return !(file in self.fileHashes) || self.fileHashes[file] !== fileHashes[file];
    });

    if (self._isRunning() && include.length) {
        self.fileHashes = fileHashes;
        self.logger.info('Reloading ' + include.join(', '));
        setTimeout(() => {
            self.server.notifyClients(include);
        }, self.options.delay);
    }
}
...
module.exports = {
  output: {
    path: outputPath
  },
  plugins: [liveReload]
};

veke avatar Apr 26 '21 17:04 veke

Hi, I tried the bugfix branch (useSourceSize = true). Here is my test results: background-color is changed => no full page reload and the color is changed. OK new property added => no full page reload, changes are applied. OK edit property integer value, for example add height: 200px, then change it to 100px and back to for example 500px => no full page reload, no visible changes at all. Not OK. (useSourceHash did not have impact, at least it is not giving error anymore)

veke avatar May 03 '21 08:05 veke

Hi Veke, as i mentioned in the readme useSourceSize will not reload if you change only the signs but you didn't remove or add any character because filesize stays the same by changing "100px" to "200px" or "500px".

And i know why useSourceHash did not have any impact. Maybe you enabled useSourceHash AND useSourceSize. If you have enabled both useSourceSize will filter out the files because the size haven't changed and they will not handelt by useSourceHash anymore.

Could you please try only enabling useSourceHash and disable or remove useSourceSize from config and get back to me if that works. Then i will add a notice to the readme that useSourceHash and useSourceSize won't work together.

web-mi avatar May 04 '21 16:05 web-mi

"as i mentioned in the readme useSourceSize will not reload if you change only the signs but you didn't remove or add any character because filesize stays the same by changing "100px" to "200px" or "500px".

ah, ok. Yes I understand. My point only was to point out that when using this new option, full page reload is fixed but these kind of CSS changes are not applied at all so the "hack" https://github.com/statianzo/webpack-livereload-plugin/issues/28#issuecomment-411733411 mentioned earlier in this issue is still the only working solution.

And i know why useSourceHash did not have any impact. Maybe you enabled useSourceHash AND useSourceSize. If you have enabled both useSourceSize will filter out the files because the size haven't changed and they will not handelt by useSourceHash anymore.

Ok, my intention was to report that this branch did fix the error I got earlier when using the useSourceHash (with or without the useSourceSize) and it did not have impact to the CSS full page reload issue.

Thanks for the contribution

veke avatar May 04 '21 17:05 veke