statianzo / webpack-livereload-plugin

LiveReload during webpack --watch
ISC License
204 stars 51 forks source link

Live CSS reloading #28

Open rstacruz opened 7 years ago

rstacruz commented 7 years ago

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?

0x80 commented 7 years ago

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?

rstacruz commented 7 years ago

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

statianzo commented 7 years ago

@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));
  }
};
defmech commented 7 years ago

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.

kayue commented 7 years ago

@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.

defmech commented 7 years ago

@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)
      }
    })
  ],
kayue commented 7 years ago

@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.

defmech commented 7 years ago

@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.

pmowrer commented 7 years ago

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.

statianzo commented 7 years ago

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

pmowrer commented 7 years ago

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))

statianzo commented 7 years ago

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.

pmowrer commented 7 years ago

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.

statianzo commented 7 years ago

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

tbranyen commented 7 years ago

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',
        }
      })
    }]
sveint commented 6 years ago

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)
        )
    }
}
claudio-borges commented 5 years ago

Thanks @sveint ! This worked out perfect for me.

veke commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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)

web-mi commented 3 years ago

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.

veke commented 3 years ago

"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