diff --git a/src/index.js b/src/index.js
index 2328922c..2283dfe9 100644
--- a/src/index.js
+++ b/src/index.js
@@ -159,7 +159,9 @@ if (content.locals) {
exported.use = function() {
if (!(refs++)) {
- dispose = api(content, options);
+ var id = ${loaderUtils.stringifyRequest(this, `!!${request}`)};
+
+ dispose = api(id, content, options);
}
return exported;
@@ -248,7 +250,8 @@ var options = ${JSON.stringify(options)};
options.insert = ${insert};
options.singleton = ${isSingleton};
-var update = api(content, options);
+var id = ${loaderUtils.stringifyRequest(this, `!!${request}`)};
+var update = api(id, content, options);
var exported = content.locals ? content.locals : {};
diff --git a/src/runtime/injectStylesIntoStyleTag.js b/src/runtime/injectStylesIntoStyleTag.js
index cbfe2db5..0c8c0e59 100644
--- a/src/runtime/injectStylesIntoStyleTag.js
+++ b/src/runtime/injectStylesIntoStyleTag.js
@@ -46,70 +46,51 @@ const getTarget = (function getTarget() {
};
})();
-function listToStyles(list, options) {
- const styles = [];
- const newStyles = {};
+function addModulesToDom(id, list, options) {
+ id = options.base ? id + options.base : id;
+
+ if (!stylesInDom[id]) {
+ stylesInDom[id] = [];
+ }
for (let i = 0; i < list.length; i++) {
const item = list[i];
- const id = options.base ? item[0] + options.base : item[0];
- const css = item[1];
- const media = item[2];
- const sourceMap = item[3];
- const part = { css, media, sourceMap };
-
- if (!newStyles[id]) {
- styles.push((newStyles[id] = { id, parts: [part] }));
+ const part = { css: item[1], media: item[2], sourceMap: item[3] };
+ const styleInDomById = stylesInDom[id];
+
+ if (styleInDomById[i]) {
+ styleInDomById[i].updater(part);
} else {
- newStyles[id].parts.push(part);
+ styleInDomById.push({ updater: addStyle(part, options) });
}
}
- return styles;
-}
-
-function addStylesToDom(styles, options) {
- for (let i = 0; i < styles.length; i++) {
- const item = styles[i];
- const domStyle = stylesInDom[item.id];
- let j = 0;
-
- if (domStyle) {
- domStyle.refs++;
-
- for (; j < domStyle.parts.length; j++) {
- domStyle.parts[j](item.parts[j]);
- }
+ for (let j = list.length; j < stylesInDom[id].length; j++) {
+ stylesInDom[id][j].updater();
+ }
- for (; j < item.parts.length; j++) {
- domStyle.parts.push(addStyle(item.parts[j], options));
- }
- } else {
- const parts = [];
+ stylesInDom[id].length = list.length;
- for (; j < item.parts.length; j++) {
- parts.push(addStyle(item.parts[j], options));
- }
-
- stylesInDom[item.id] = { id: item.id, refs: 1, parts };
- }
+ if (stylesInDom[id].length === 0) {
+ delete stylesInDom[id];
}
}
function insertStyleElement(options) {
const style = document.createElement('style');
+ const attributes = options.attributes || {};
- if (typeof options.attributes.nonce === 'undefined') {
+ if (typeof attributes.nonce === 'undefined') {
const nonce =
typeof __webpack_nonce__ !== 'undefined' ? __webpack_nonce__ : null;
if (nonce) {
- options.attributes.nonce = nonce;
+ attributes.nonce = nonce;
}
}
- Object.keys(options.attributes).forEach((key) => {
- style.setAttribute(key, options.attributes[key]);
+ Object.keys(attributes).forEach((key) => {
+ style.setAttribute(key, attributes[key]);
});
if (typeof options.insert === 'function') {
@@ -179,6 +160,8 @@ function applyToTag(style, options, obj) {
if (media) {
style.setAttribute('media', media);
+ } else {
+ style.removeAttribute('media');
}
if (sourceMap && btoa) {
@@ -243,51 +226,18 @@ function addStyle(obj, options) {
};
}
-module.exports = (list, options) => {
+module.exports = (id, list, options) => {
options = options || {};
- options.attributes =
- typeof options.attributes === 'object' ? options.attributes : {};
-
// Force single-tag solution on IE6-9, which has a hard limit on the # of
Hello world
"`;
+exports[`addStyle should work with updates #12 1`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #12 2`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #12 3`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #12 4`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #13 1`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #13 2`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #13 3`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #13 4`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #13 5`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #13 6`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #13 7`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #14 1`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #14 2`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #14 3`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #14 4`] = `"TitleHello world
"`;
+
+exports[`addStyle should work with updates #14 5`] = `"TitleHello world
"`;
+
exports[`addStyle should work with updates 1`] = `"TitleHello world
"`;
exports[`addStyle should work with updates 2`] = `"TitleHello world
"`;
diff --git a/test/runtime/injectStylesIntoStyleTag.test.js b/test/runtime/injectStylesIntoStyleTag.test.js
index 50637b9b..d5ea4808 100644
--- a/test/runtime/injectStylesIntoStyleTag.test.js
+++ b/test/runtime/injectStylesIntoStyleTag.test.js
@@ -38,6 +38,14 @@ function insertBeforeAt(element) {
window._lastElementInsertedByStyleLoader = element;
}
+let counter = -1;
+
+function getId() {
+ counter += 1;
+
+ return `!!../../node_modules/css-loader/dist/cjs.js!./style.css${counter}`;
+}
+
describe('addStyle', () => {
beforeEach(() => {
document.head.innerHTML = 'Title';
@@ -47,13 +55,15 @@ describe('addStyle', () => {
// Each query should have be unique because style-loader caching styles in dom
it('should work', () => {
- injectStylesIntoStyleTag([['./style-1.css', '.foo { color: red }', '']]);
+ injectStylesIntoStyleTag(getId(), [
+ ['./style-1.css', '.foo { color: red }', ''],
+ ]);
expect(document.documentElement.innerHTML).toMatchSnapshot();
});
it('should work with multiple styles', () => {
- injectStylesIntoStyleTag([
+ injectStylesIntoStyleTag(getId(), [
['./style-2-1.css', '.foo { color: red }', ''],
['./style-2-2.css', '.bar { color: blue }', ''],
]);
@@ -62,7 +72,7 @@ describe('addStyle', () => {
});
it('should work with same module id in list', () => {
- injectStylesIntoStyleTag([
+ injectStylesIntoStyleTag(getId(), [
['./style-3.css', '.foo { color: red }', ''],
['./style-3.css', '.foo { color: green }', ''],
]);
@@ -71,7 +81,7 @@ describe('addStyle', () => {
});
it('should work with media', () => {
- injectStylesIntoStyleTag([
+ injectStylesIntoStyleTag(getId(), [
['./style-4.css', '.foo { color: red }', 'screen and (min-width:320px)'],
]);
@@ -79,7 +89,7 @@ describe('addStyle', () => {
});
it('should work with source maps', () => {
- injectStylesIntoStyleTag([
+ injectStylesIntoStyleTag(getId(), [
[
'./style-5.css',
'.foo { color: red }',
@@ -102,7 +112,9 @@ describe('addStyle', () => {
// eslint-disable-next-line no-underscore-dangle
window.__webpack_nonce__ = '12345678';
- injectStylesIntoStyleTag([['./style-6.css', '.foo { color: red }', '']]);
+ injectStylesIntoStyleTag(getId(), [
+ ['./style-6.css', '.foo { color: red }', ''],
+ ]);
expect(document.documentElement.innerHTML).toMatchSnapshot();
@@ -114,9 +126,13 @@ describe('addStyle', () => {
// eslint-disable-next-line no-underscore-dangle
window.__webpack_nonce__ = '12345678';
- injectStylesIntoStyleTag([['./style-7.css', '.foo { color: red }', '']], {
- attributes: { nonce: '87654321' },
- });
+ injectStylesIntoStyleTag(
+ getId(),
+ [['./style-7.css', '.foo { color: red }', '']],
+ {
+ attributes: { nonce: '87654321' },
+ }
+ );
expect(document.documentElement.innerHTML).toMatchSnapshot();
@@ -125,23 +141,32 @@ describe('addStyle', () => {
});
it('should work with "base" option', () => {
- injectStylesIntoStyleTag([['./style-8.css', '.foo { color: red }', '']], {
- base: 1000,
- });
+ injectStylesIntoStyleTag(
+ getId(),
+ [['./style-8.css', '.foo { color: red }', '']],
+ {
+ base: 1000,
+ }
+ );
expect(document.documentElement.innerHTML).toMatchSnapshot();
});
it('should work with "attributes" option', () => {
- injectStylesIntoStyleTag([['./style-9.css', '.foo { color: red }', '']], {
- attributes: { foo: 'bar' },
- });
+ injectStylesIntoStyleTag(
+ getId(),
+ [['./style-9.css', '.foo { color: red }', '']],
+ {
+ attributes: { foo: 'bar' },
+ }
+ );
expect(document.documentElement.innerHTML).toMatchSnapshot();
});
it('should work with "attributes" option #2', () => {
injectStylesIntoStyleTag(
+ getId(),
[
['./style-10-1.css', '.foo { color: red }', ''],
['./style-10-2.css', '.bar { color: blue }', ''],
@@ -156,6 +181,7 @@ describe('addStyle', () => {
it('should work with "insert" option', () => {
injectStylesIntoStyleTag(
+ getId(),
[
['./style-11-1.css', '.foo { color: red }', ''],
['./style-11-2.css', '.bar { color: blue }', ''],
@@ -170,6 +196,7 @@ describe('addStyle', () => {
it('should work with "insert" option #2', () => {
injectStylesIntoStyleTag(
+ getId(),
[
['./style-12-1.css', '.foo { color: red }', ''],
['./style-12-2.css', '.bar { color: blue }', ''],
@@ -187,6 +214,7 @@ describe('addStyle', () => {
"Hello world
";
injectStylesIntoStyleTag(
+ getId(),
[
['./style-13-1.css', '.foo { color: red }', ''],
['./style-13-2.css', '.bar { color: blue }', ''],
@@ -205,6 +233,7 @@ describe('addStyle', () => {
it('should work with "insert" option #4', () => {
injectStylesIntoStyleTag(
+ getId(),
[
['./style-14-1.css', '.foo { color: red }', ''],
['./style-14-2.css', '.bar { color: blue }', ''],
@@ -225,6 +254,7 @@ describe('addStyle', () => {
'Title';
injectStylesIntoStyleTag(
+ getId(),
[
['./style-15-1.css', '.foo { color: red }', ''],
['./style-15-2.css', '.bar { color: blue }', ''],
@@ -242,6 +272,7 @@ describe('addStyle', () => {
'Hello world
';
injectStylesIntoStyleTag(
+ getId(),
[
['./style-16-1.css', '.foo { color: red }', ''],
['./style-16-2.css', '.bar { color: blue }', ''],
@@ -257,6 +288,7 @@ describe('addStyle', () => {
it('should throw error with incorrect "insert" option', () => {
expect(() =>
injectStylesIntoStyleTag(
+ getId(),
[['./style-17.css', '.foo { color: red }', '']],
{
insert: 'invalid',
@@ -268,6 +300,7 @@ describe('addStyle', () => {
it('should throw error with invalid "insert" option', () => {
expect(() =>
injectStylesIntoStyleTag(
+ getId(),
[['./style-18.css', '.foo { color: red }', '']],
{
insert: '#test><><><',
@@ -277,7 +310,7 @@ describe('addStyle', () => {
});
it('should work with updates', () => {
- const update = injectStylesIntoStyleTag([
+ const update = injectStylesIntoStyleTag(getId(), [
['./style-19.css', '.foo { color: red }', ''],
]);
@@ -289,7 +322,7 @@ describe('addStyle', () => {
});
it('should work with updates #2', () => {
- const update = injectStylesIntoStyleTag([
+ const update = injectStylesIntoStyleTag(getId(), [
['./style-20-1.css', '.foo { color: red }', ''],
]);
@@ -301,7 +334,7 @@ describe('addStyle', () => {
});
it('should work with updates #3', () => {
- const update = injectStylesIntoStyleTag([
+ const update = injectStylesIntoStyleTag(getId(), [
['./style-21-1.css', '.foo { color: red }', ''],
]);
@@ -316,7 +349,7 @@ describe('addStyle', () => {
});
it('should work with updates #4', () => {
- const update = injectStylesIntoStyleTag([
+ const update = injectStylesIntoStyleTag(getId(), [
['./style-22.css', '.foo { color: red }', ''],
]);
@@ -328,7 +361,7 @@ describe('addStyle', () => {
});
it('should work with updates #5', () => {
- const update = injectStylesIntoStyleTag([
+ const update = injectStylesIntoStyleTag(getId(), [
['./style-23.css', '.foo { color: red }', 'screen and (min-width:320px)'],
]);
@@ -347,6 +380,7 @@ describe('addStyle', () => {
it('should work with updates #6', () => {
const update = injectStylesIntoStyleTag(
+ getId(),
[
['./style-24-1.css', '.foo { color: red }', ''],
['./style-24-2.css', '.bar { color: yellow }', ''],
@@ -365,7 +399,7 @@ describe('addStyle', () => {
});
it('should work with updates #7', () => {
- const update = injectStylesIntoStyleTag([
+ const update = injectStylesIntoStyleTag(getId(), [
['./style-25-1.css', '.foo { color: red }', ''],
['./style-25-2.css', '.bar { color: blue }', ''],
]);
@@ -381,7 +415,7 @@ describe('addStyle', () => {
});
it('should work with updates #8', () => {
- const update = injectStylesIntoStyleTag([
+ const update = injectStylesIntoStyleTag(getId(), [
[
'./style-26-1.css',
'.foo { color: red }',
@@ -457,7 +491,7 @@ describe('addStyle', () => {
});
it('should work with updates #9', () => {
- const update = injectStylesIntoStyleTag([
+ const update = injectStylesIntoStyleTag(getId(), [
['./style-27.css', '.foo { color: red }', ''],
]);
@@ -474,6 +508,7 @@ describe('addStyle', () => {
it('should work with updates #10', () => {
const update = injectStylesIntoStyleTag(
+ getId(),
[
['./style-28-1.css', '.foo { color: red }', ''],
['./style-28-2.css', '.bar { color: blue }', ''],
@@ -501,6 +536,7 @@ describe('addStyle', () => {
'Title';
const update = injectStylesIntoStyleTag(
+ getId(),
[
['./style-29-1.css', '.foo { color: red }', ''],
['./style-29-2.css', '.bar { color: blue }', ''],
@@ -519,4 +555,155 @@ describe('addStyle', () => {
expect(document.documentElement.innerHTML).toMatchSnapshot();
});
+
+ it('should work with updates #12', () => {
+ const update = injectStylesIntoStyleTag(getId(), [
+ ['./order-1.css', '.order { color: red }', ''],
+ [
+ './order.css',
+ '.@import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");',
+ '',
+ ],
+ ['./order-2.css', '.order { color: blue }', ''],
+ [
+ './order.css',
+ '.@import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");',
+ '',
+ ],
+ ['./order-1.css', '.order { color: red }', ''],
+ [
+ './order.css',
+ '.@import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");',
+ '',
+ ],
+ [
+ './order-2.css',
+ '.order { color: blue }',
+ 'screen and (min-width: 2000px)',
+ ],
+ [
+ './order.css',
+ '.@import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");',
+ '',
+ ],
+ ]);
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ update([
+ ['./order-1.css', '.order { color: orange }', ''],
+ [
+ './order.css',
+ '.@import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");',
+ '',
+ ],
+ ['./order-2.css', '.order { color: blue }', ''],
+ [
+ './order.css',
+ '.@import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");',
+ '',
+ ],
+ ['./order-1.css', '.order { color: orange }', ''],
+ [
+ './order.css',
+ '.@import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");',
+ '',
+ ],
+ [
+ './order-2.css',
+ '.order { color: blue }',
+ 'screen and (min-width: 2000px)',
+ ],
+ [
+ './order.css',
+ '.@import url("https://fonts.googleapis.com/css?family=Roboto&display=swap");',
+ '',
+ ],
+ ]);
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+ });
+
+ it('should work with updates #12', () => {
+ const update = injectStylesIntoStyleTag(getId(), [
+ ['./style-30.css', '.foo { color: red }', ''],
+ ['./style-31.css', '.bar { color: blue }', ''],
+ ]);
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ update([]);
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+ });
+
+ it('should work with updates #13', () => {
+ const update1 = injectStylesIntoStyleTag(getId(), [
+ ['./style-32.css', '.red { color: red }', ''],
+ ]);
+ const update2 = injectStylesIntoStyleTag(getId(), [
+ ['./style-33.css', '.green { color: green }', ''],
+ ]);
+ const update3 = injectStylesIntoStyleTag(getId(), [
+ ['./style-34.css', '.blue { color: blue }', ''],
+ ]);
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ update1([['./style-32.css', '.red { color: black }', '']]);
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ update2([['./style-33.css', '.green { color: black }', '']]);
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ update3([['./style-34.css', '.blue { color: black }', '']]);
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ update1();
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ update2();
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ update3();
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+ });
+
+ it('should work with updates #14', () => {
+ const update1 = injectStylesIntoStyleTag(getId(), [
+ ['./style-35.css', '.red { color: red }', ''],
+ ]);
+ const update2 = injectStylesIntoStyleTag(getId(), [
+ ['./style-36.css', '.green { color: green }', ''],
+ ]);
+ const update3 = injectStylesIntoStyleTag(getId(), [
+ ['./style-37.css', '.blue { color: blue }', ''],
+ ]);
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ update2([['./style-36.css', '.green { color: black }', '']]);
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ injectStylesIntoStyleTag(getId(), [
+ ['./style-38.css', '.white { color: white }', ''],
+ ]);
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ update1();
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+
+ update3();
+
+ expect(document.documentElement.innerHTML).toMatchSnapshot();
+ });
});