From dbe6a5e0874e41a461690694107324308905ef68 Mon Sep 17 00:00:00 2001 From: Tim Branyen Date: Fri, 2 Jun 2017 16:20:27 -0700 Subject: [PATCH] LiveReload always sends all assets A problem with this plugin is that regardless of what has changed, the same output assets are always emitted. I've taken a stable at filtering down, but to be honest, I made it a total hack job. For some reason the webpack plugin API doesn't not 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()` at least 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. --- index.js | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 7ede0cc..c584426 100755 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ /* jshint node:true */ +var statSync = require('fs').statSync; var lr = require('tiny-lr'); var servers = {}; @@ -13,6 +14,10 @@ function LiveReloadPlugin(options) { this.protocol = this.options.protocol || 'http'; this.hostname = this.options.hostname || 'localhost'; this.server = null; + + this.startTime = new Date(); + this.startTime.setSeconds(-process.uptime()); + this.prevTimestamps = {}; } function arraysEqual(a1, a2){ @@ -49,18 +54,52 @@ LiveReloadPlugin.prototype.start = function start(watching, cb) { }; LiveReloadPlugin.prototype.done = function done(stats) { + var timestamps = stats.compilation ? stats.compilation.fileTimestamps : {}; + var assets = stats.compilation.assets; + var include = []; + + // If no timestamp information is available, use all assets. + if (!timestamps) { + include.push.apply(include, Object.keys(assets)); + } + else { + this.changedFiles = Object.keys(timestamps).filter(function(watchfile) { + return this.startTime < Math.ceil(statSync(watchfile).mtime); + }.bind(this)).forEach(function(changedFile) { + Object.keys(assets).forEach(function(assetName) { + const asset = Object.assign({}, assets[assetName]); + const sources = []; + + if (asset.emitted && asset.existsAt.split('.').slice(-1)[0] !== 'css') { + include.push(assetName); + } + + (asset.children || []).forEach(function(child) { + if (child && child._sourceMap && child._sourceMap.sources) { + sources.push.apply(sources, child._sourceMap.sources); + } + }); + + if (sources.includes(changedFile)) { + include.push(assetName); + } + }, this); + }, this); + } + + this.startTime = Date.now(); + 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) { + var updated = include.filter(function(file) { return !file.match(this.ignore); }, this); - if (this.isRunning && (hash !== this.lastHash || !arraysEqual(childHashes, this.lastChildHashes)) && include.length > 0) { + if (this.isRunning && (hash !== this.lastHash || !arraysEqual(childHashes, this.lastChildHashes)) && updated.length > 0) { this.lastHash = hash; this.lastChildHashes = childHashes; setTimeout(function onTimeout() { - this.server.notifyClients(include); + this.server.notifyClients(updated); }.bind(this)); } };