From df2161a844a4473934227fa813cb135a1116cd15 Mon Sep 17 00:00:00 2001 From: Marc Itzenthaler Date: Wed, 28 Apr 2021 02:56:38 +0200 Subject: [PATCH] Bugfix: `useSourceHash` (#28) Generate source hash on emit because afterEmit only has SizeOnlySource Feature: New option `useSourceSize` to compare source sizes --- README.md | 1 + index.js | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++----- test.js | 49 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 99 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 366afec..84cc57e 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Add a script tag to your page pointed at the livereload server ignore nothing. It is also possible to define an array and use multiple [anymatch](https://github.com/micromatch/anymatch) patterns. - `delay` - (Default: `0`) amount of milliseconds by which to delay the live reload (in case build takes longer) - `useSourceHash` - (Default: `false`) create hash for each file source and only notify livereload if hash has changed +- `useSourceSize` - (Default: `false`) check size for each file source and only notify livereload if size has changed (Faster than `useSourceHash` but it has a downside. If file size hasn't changed no reload is triggered. For example if color has changed from `#000000` to `#ffffff` no reload will be triggered!) ## Why? diff --git a/index.js b/index.js index b820806..ecdb64c 100755 --- a/index.js +++ b/index.js @@ -25,6 +25,7 @@ class LiveReloadPlugin { ignore: null, quiet: false, useSourceHash: false, + useSourceSize: false, appendScriptTag: false, delay: 0, }, options); @@ -35,6 +36,7 @@ class LiveReloadPlugin { this.lastChildHashes = []; this.server = null; this.sourceHashs = {}; + this.sourceSizes = {}; this.webpack = null; this.infrastructureLogger = null; this.isWebpack4 = false; @@ -47,7 +49,8 @@ class LiveReloadPlugin { compiler.hooks.compilation.tap(PLUGIN_NAME, this._applyCompilation.bind(this)); compiler.hooks.watchRun.tapAsync(PLUGIN_NAME, this._start.bind(this)); - compiler.hooks.afterEmit.tap(PLUGIN_NAME, this._afterEmit.bind(this)) + compiler.hooks.afterEmit.tap(PLUGIN_NAME, this._afterEmit.bind(this)); + compiler.hooks.emit.tap(PLUGIN_NAME, this._emit.bind(this)); compiler.hooks.failed.tap(PLUGIN_NAME, this._failed.bind(this)); } @@ -145,6 +148,7 @@ class LiveReloadPlugin { const include = Object.entries(compilation.assets) .filter(this._fileIgnoredOrNotEmitted.bind(this)) + .filter(this._fileSizeDoesntMatch.bind(this)) .filter(this._fileHashDoesntMatch.bind(this)) .map((data) => data[0]) ; @@ -162,6 +166,14 @@ class LiveReloadPlugin { } } + /** + * @private + * @param compilation + */ + _emit(compilation) { + Object.entries(compilation.assets).forEach(this._calculateSourceHash.bind(this)); + } + /** * @private */ @@ -169,6 +181,7 @@ class LiveReloadPlugin { this.lastHash = null; this.lastChildHashes = []; this.sourceHashs = {}; + this.sourceSizes = {}; } /** @@ -225,6 +238,25 @@ class LiveReloadPlugin { return !data[0].match(this.options.ignore) && size; } + /** + * Check compiled source size + * + * @param data + * @returns {boolean} + * @private + */ + _fileSizeDoesntMatch(data) { + if (!this.options.useSourceSize) + return true; + + if (this.sourceSizes[data[0]] === data[1].size()) { + return false; + } + + this.sourceSizes[data[0]] = data[1].size(); + return true; + } + /** * Check compiled source hash * @@ -236,16 +268,32 @@ class LiveReloadPlugin { if (!this.options.useSourceHash) return true; - const sourceHash = LiveReloadPlugin.generateHashCode(data[1].source()); - - if (this.sourceHashs[data[0]] === sourceHash) { + if (this.sourceHashs[data[0]]?.hash === this.sourceHashs[data[0]].calculated) { return false; } - this.sourceHashs[data[0]] = sourceHash; + // Update source hash + this.sourceHashs[data[0]].hash = this.sourceHashs[data[0]].calculated; return true; } + /** + * Calculate compiled source hash + * + * @param data + * @returns {void} + * @private + */ + _calculateSourceHash(data) { + if (!this.options.useSourceHash) return; + + // Calculate source hash + this.sourceHashs[data[0]] = { + hash: this.sourceHashs[data[0]]?.hash ?? null, + calculated: LiveReloadPlugin.generateHashCode(data[1].source()) + }; + } + /** * @private */ diff --git a/test.js b/test.js index f829871..e6d6022 100644 --- a/test.js +++ b/test.js @@ -10,6 +10,7 @@ test('default options', function(t) { t.equal(plugin.options.ignore, null); t.equal(plugin.options.quiet, false); t.equal(plugin.options.useSourceHash, false); + t.equal(plugin.options.useSourceSize, false); t.equal(plugin.options.appendScriptTag, false); t.equal(plugin.options.delay, 0); t.equal(plugin._isRunning(), false); @@ -178,6 +179,11 @@ test('filters out hashed files', function(t) { }); const compilation = { assets: { + 'c.js': { + emitted: true, + size: () => 1, + source: () => "asdf", + }, 'b.js': { emitted: true, size: () => 1, @@ -192,12 +198,47 @@ test('filters out hashed files', function(t) { children: [] }; plugin.sourceHashs = { - 'b.js': 'Wrong hash', - 'a.js': hashCode('asdf'), + 'b.js': {hash: 'Wrong hash'}, + 'a.js': {hash: hashCode('asdf')}, }; plugin.server = { notifyClients: function(files) { - t.deepEqual(files.sort(), ['b.js']); + t.deepEqual(files.sort(), ['b.js', 'c.js']); + t.end(); + } + }; + plugin._emit(compilation); + plugin._afterEmit(compilation); +}); + +test('filters out resized files', function(t) { + const plugin = new LiveReloadPlugin({ + useSourceSize: true, + }); + const compilation = { + assets: { + 'c.js': { + emitted: true, + size: () => 10, + }, + 'b.js': { + emitted: true, + size: () => 10, + }, + 'a.js': { + emitted: true, + size: () => 20, + }, + }, + children: [] + }; + plugin.sourceSizes = { + 'b.js': 20, + 'a.js': 20, + }; + plugin.server = { + notifyClients: function(files) { + t.deepEqual(files.sort(), ['b.js', 'c.js']); t.end(); } }; @@ -275,4 +316,4 @@ test('autoloadJs suffixes empty protocol without colon', function(t) { const [,src] = plugin._autoloadJs().match(/src\s+=\s+"(.+)"/) t.assert(src.startsWith('//')); t.end(); -}); \ No newline at end of file +});