statianzo / webpack-livereload-plugin

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

LiveReload always sends all assets #33

Open tbranyen opened 7 years ago

tbranyen commented 7 years ago

A problem with this plugin is that regardless of what has changed, the same output assets are always emitted. I've taken a stab at filtering down, but to be honest, I made it a total hack job. For some reason the webpack plugin API doesn't make it easy (even after reading docs) to figure out what files changed and what assets they belong to.

That aside, the goal of this PR is to allow CSS to LiveReload without doing a full page refresh. It does work. However, it relies on adjusting the startTime (https://github.com/webpack/docs/wiki/how-to-write-a-plugin#monitoring-the-watch-graph) by offseting process.uptime() for the first call, otherwise the modified timestamps aren't accurate. It also relies on enabling sourceMaps for css-loader to get access to the css asset children.

I'd love a review and tips on how to improve this, but for now this should greatly assist with reloading ExtractTextPlugin-based webpack configs, without requiring the webpack-dev-server.

tbranyen commented 7 years ago

I published this to make it easier for folks to try in their own apps:

npm i webpack-livereload-plugin-css@0.11.0-1

I'll deprecate the package after this gets updated and merged.

rstacruz commented 7 years ago

Nice work!

@tbranyen if I may suggest, when publishing your own forks, you can name them as @tbranyen/webpack-livereload-plugin :)

kayue commented 7 years ago

@tbranyen There is a failed test case, does it need to be fixed?

@statianzo what do you think about this feature?

tbranyen commented 6 years ago

@statianzo I've updated and rebased this PR. Let me know if you need anything else to merge.

tbranyen commented 6 years ago

I noticed this PR falls short when using glob patterns within other files. So for instance, I updated somefile.css that is embedded in myapp.css through a glob pattern. This code I added is unable to reconcile nested changes.

Not sure if you have any thoughts on handling this case.

Kagami commented 6 years ago

This doesn't work for me in WebPack 4.

And it's hard to make right because you need to find output name by source name which was changed. There're ways to do that, e.g. changed chunks example seems to provide required info.

But this won't help much: if you required .css in JS module and use extract-text-webpack-plugin to extract it to separate file, technically you depend on CSS in JS module and so JS also needs to be refreshed when you update CSS, so full page should be reloaded.

I don't know any good way to solve this in general case because some may use result of require() in JS module so we should also update all dependency tree. So I made this quick hack instead:

diff --git a/index.js b/index.js
index 89a4ff2..642616f 100755
--- a/index.js
+++ b/index.js
@@ -17,6 +17,7 @@ function LiveReloadPlugin(options) {
   this.protocol = this.options.protocol ? this.options.protocol + ':' : '';
   this.hostname = this.options.hostname || '" + location.hostname + "';
   this.server = null;
+  this.lastUpdate = Date.now();
 }

 function arraysEqual(a1, a2){
@@ -56,6 +57,23 @@ 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);
+  const timestamps = stats.compilation.fileTimestamps;
+  if (timestamps.size) {
+    let fastUpdate = true;
+    const fastUpdateRe = /\.css$/;
+    for (const [name, timestamp] of timestamps) {
+      if (timestamp > this.lastUpdate) {
+        if (!fastUpdateRe.test(name)) {
+          fastUpdate = false;
+          break;
+        }
+      }
+    }
+    if (fastUpdate) {
+      files = files.filter(name => fastUpdateRe.test(name));
+    }
+  }
+  this.lastUpdate = Date.now();
   var include = files.filter(function(file) {
     return !file.match(this.ignore);
   }, this);

It only updates CSS when nothing else was changed. It's wrong, but works for me in simple webpack setup.