From cc6da6562dc1f601a96498a0ff91b057950789c4 Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Wed, 22 Apr 2020 17:25:38 +0300 Subject: [PATCH 1/3] feat: hot module replacement for css modules --- src/index.js | 24 ++++++++++++--- src/runtime/isEqualLocals.js | 23 +++++++++++++++ test/runtime/isEqualLocals.test.js | 47 ++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 src/runtime/isEqualLocals.js create mode 100644 test/runtime/isEqualLocals.test.js diff --git a/src/index.js b/src/index.js index 5907d16f..533f7dc6 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,8 @@ import path from 'path'; import loaderUtils from 'loader-utils'; import validateOptions from 'schema-utils'; +import isEqualLocals from './runtime/isEqualLocals'; + import schema from './options.json'; const loaderApi = () => {}; @@ -187,13 +189,22 @@ ${esModule ? 'export default' : 'module.exports ='} exported;`; const hmrCode = this.hot ? ` if (module.hot) { - if (!content.locals) { + if (!content.locals || module.hot.invalidate) { + var isEqualLocals = ${isEqualLocals.toString()}; + var oldLocals = content.locals; + module.hot.accept( ${loaderUtils.stringifyRequest(this, `!!${request}`)}, function () { ${ esModule - ? `update(content);` + ? `if (!isEqualLocals(oldLocals, content.locals)) { + module.hot.invalidate(); + } + + oldLocals = content.locals; + + update(content);` : `var newContent = require(${loaderUtils.stringifyRequest( this, `!!${request}` @@ -205,6 +216,12 @@ if (module.hot) { newContent = [[module.id, newContent, '']]; } + if (!isEqualLocals(oldLocals, newContent.locals)) { + module.hot.invalidate(); + } + + oldLocals = newContent.locals; + update(newContent);` } } @@ -226,8 +243,7 @@ if (module.hot) { import content from ${loaderUtils.stringifyRequest( this, `!!${request}` - )}; - var clonedContent = content;` + )};` : `var api = require(${loaderUtils.stringifyRequest( this, `!${path.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}` diff --git a/src/runtime/isEqualLocals.js b/src/runtime/isEqualLocals.js new file mode 100644 index 00000000..1002ff1f --- /dev/null +++ b/src/runtime/isEqualLocals.js @@ -0,0 +1,23 @@ +function isEqualLocals(a, b) { + if ((!a && b) || (a && !b)) { + return false; + } + + let p; + + for (p in a) { + if (a[p] !== b[p]) { + return false; + } + } + + for (p in b) { + if (!a[p]) { + return false; + } + } + + return true; +} + +module.exports = isEqualLocals; diff --git a/test/runtime/isEqualLocals.test.js b/test/runtime/isEqualLocals.test.js new file mode 100644 index 00000000..0a5c458b --- /dev/null +++ b/test/runtime/isEqualLocals.test.js @@ -0,0 +1,47 @@ +/* eslint-env browser */ + +import isEqualLocals from '../../src/runtime/isEqualLocals'; + +describe('isEqualLocals', () => { + it('should work', () => { + expect(isEqualLocals()).toBe(true); + expect(isEqualLocals({}, {})).toBe(true); + // eslint-disable-next-line no-undefined + expect(isEqualLocals(undefined, undefined)).toBe(true); + expect(isEqualLocals({ foo: 'bar' }, { foo: 'bar' })).toBe(true); + expect( + isEqualLocals({ foo: 'bar', bar: 'baz' }, { foo: 'bar', bar: 'baz' }) + ).toBe(true); + expect( + isEqualLocals({ foo: 'bar', bar: 'baz' }, { bar: 'baz', foo: 'bar' }) + ).toBe(true); + expect( + isEqualLocals({ bar: 'baz', foo: 'bar' }, { foo: 'bar', bar: 'baz' }) + ).toBe(true); + + // eslint-disable-next-line no-undefined + expect(isEqualLocals(undefined, { foo: 'bar' })).toBe(false); + // eslint-disable-next-line no-undefined + expect(isEqualLocals({ foo: 'bar' }, undefined)).toBe(false); + + expect(isEqualLocals({ foo: 'bar' }, { foo: 'baz' })).toBe(false); + + expect(isEqualLocals({ foo: 'bar' }, { bar: 'bar' })).toBe(false); + expect(isEqualLocals({ bar: 'bar' }, { foo: 'bar' })).toBe(false); + + expect(isEqualLocals({ foo: 'bar' }, { foo: 'bar', bar: 'baz' })).toBe( + false + ); + expect(isEqualLocals({ foo: 'bar', bar: 'baz' }, { foo: 'bar' })).toBe( + false + ); + + // Should never happen, but let's test it + expect(isEqualLocals({ foo: 'bar' }, { foo: true })).toBe(false); + expect(isEqualLocals({ foo: true }, { foo: 'bar' })).toBe(false); + // eslint-disable-next-line no-undefined + expect(isEqualLocals({ foo: 'bar' }, { foo: undefined })).toBe(false); + expect(isEqualLocals({ foo: undefined }, { foo: 'bar' })).toBe(false); + expect(isEqualLocals({ foo: { foo: 'bar' } }, { foo: 'bar' })).toBe(false); + }); +}); From 4b59b688a829c2683b35199f0a9c9b97fb1fec58 Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Wed, 22 Apr 2020 19:42:40 +0300 Subject: [PATCH 2/3] feat: hot module replacement for css modules --- src/index.js | 75 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/src/index.js b/src/index.js index 533f7dc6..5101db95 100644 --- a/src/index.js +++ b/src/index.js @@ -98,25 +98,60 @@ ${esModule ? `export default {}` : ''}`; const hmrCode = this.hot ? ` if (module.hot) { - var lastRefs = module.hot.data && module.hot.data.refs || 0; + if (!content.locals || module.hot.invalidate) { + var isEqualLocals = ${isEqualLocals.toString()}; + var oldLocals = content.locals; - if (lastRefs) { - exported.use(); + module.hot.accept( + ${loaderUtils.stringifyRequest(this, `!!${request}`)}, + function () { + ${ + esModule + ? `if (refs <= 0) { + return; + } - if (!content.locals) { - refs = lastRefs; - } - } + if (!isEqualLocals(oldLocals, content.locals)) { + module.hot.invalidate(); - if (!content.locals) { - module.hot.accept(); - } + return; + } + + oldLocals = content.locals; - module.hot.dispose(function(data) { - data.refs = content.locals ? 0 : refs; + if (update) { + update(content); + }` + : `if (refs <= 0) { + return; + } - if (dispose) { - dispose(); + var newContent = require(${loaderUtils.stringifyRequest( + this, + `!!${request}` + )}); + + newContent = newContent.__esModule ? newContent.default : newContent; + + if (!isEqualLocals(oldLocals, newContent.locals)) { + module.hot.invalidate(); + + return; + } + + oldLocals = newContent.locals; + + if (update) { + update(newContent); + }` + } + } + ) + } + + module.hot.dispose(function() { + if (update) { + update(); } }); }` @@ -149,7 +184,7 @@ if (module.hot) { } var refs = 0; -var dispose; +var update; var options = ${JSON.stringify(options)}; options.insert = ${insert}; @@ -163,7 +198,7 @@ if (content.locals) { exported.use = function() { if (!(refs++)) { - dispose = api(content, options); + update = api(content, options); } return exported; @@ -171,8 +206,8 @@ exported.use = function() { exported.unuse = function() { if (refs > 0 && !--refs) { - dispose(); - dispose = null; + update(); + update = null; } }; @@ -200,6 +235,8 @@ if (module.hot) { esModule ? `if (!isEqualLocals(oldLocals, content.locals)) { module.hot.invalidate(); + + return; } oldLocals = content.locals; @@ -218,6 +255,8 @@ if (module.hot) { if (!isEqualLocals(oldLocals, newContent.locals)) { module.hot.invalidate(); + + return; } oldLocals = newContent.locals; From 70ff3ac2603b4d5da167377585e7f15d000de528 Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Thu, 23 Apr 2020 14:19:45 +0300 Subject: [PATCH 3/3] feat: hot module replacement for css modules --- src/index.js | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/index.js b/src/index.js index 5101db95..41e25cd8 100644 --- a/src/index.js +++ b/src/index.js @@ -88,7 +88,7 @@ var update = api(content, options); ${hmrCode} -${esModule ? `export default {}` : ''}`; +${esModule ? 'export default {}' : ''}`; } case 'lazyStyleTag': @@ -107,11 +107,7 @@ if (module.hot) { function () { ${ esModule - ? `if (refs <= 0) { - return; - } - - if (!isEqualLocals(oldLocals, content.locals)) { + ? `if (!isEqualLocals(oldLocals, content.locals)) { module.hot.invalidate(); return; @@ -119,14 +115,10 @@ if (module.hot) { oldLocals = content.locals; - if (update) { + if (update && refs > 0) { update(content); }` - : `if (refs <= 0) { - return; - } - - var newContent = require(${loaderUtils.stringifyRequest( + : `var newContent = require(${loaderUtils.stringifyRequest( this, `!!${request}` )}); @@ -141,7 +133,7 @@ if (module.hot) { oldLocals = newContent.locals; - if (update) { + if (update && refs > 0) { update(newContent); }` } @@ -192,10 +184,7 @@ options.singleton = ${isSingleton}; var exported = {}; -if (content.locals) { - exported.locals = content.locals; -} - +exported.locals = content.locals || {}; exported.use = function() { if (!(refs++)) { update = api(content, options); @@ -203,7 +192,6 @@ exported.use = function() { return exported; }; - exported.unuse = function() { if (refs > 0 && !--refs) { update(); @@ -306,11 +294,9 @@ options.singleton = ${isSingleton}; var update = api(content, options); -var exported = content.locals ? content.locals : {}; - ${hmrCode} -${esModule ? 'export default' : 'module.exports ='} exported;`; +${esModule ? 'export default' : 'module.exports ='} content.locals || {};`; } } };