Skip to content

Commit 236b243

Browse files
fix: injection algorithm (#456)
1 parent 36bd8f1 commit 236b243

15 files changed

+1279
-1078
lines changed

package-lock.json

Lines changed: 1029 additions & 943 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,42 +45,42 @@
4545
},
4646
"dependencies": {
4747
"loader-utils": "^1.2.3",
48-
"schema-utils": "^2.0.1"
48+
"schema-utils": "^2.6.4"
4949
},
5050
"devDependencies": {
51-
"@babel/cli": "^7.7.4",
52-
"@babel/core": "^7.7.4",
53-
"@babel/preset-env": "^7.7.4",
54-
"@commitlint/cli": "^8.2.0",
55-
"@commitlint/config-conventional": "^8.2.0",
51+
"@babel/cli": "^7.8.3",
52+
"@babel/core": "^7.8.3",
53+
"@babel/preset-env": "^7.8.3",
54+
"@commitlint/cli": "^8.3.5",
55+
"@commitlint/config-conventional": "^8.3.4",
5656
"@webpack-contrib/defaults": "^6.3.0",
5757
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
5858
"babel-jest": "^24.9.0",
59-
"commitlint-azure-pipelines-cli": "^1.0.2",
59+
"commitlint-azure-pipelines-cli": "^1.0.3",
6060
"cross-env": "^6.0.3",
61-
"css-loader": "^3.4.0",
61+
"css-loader": "^3.4.2",
6262
"del": "^5.1.0",
6363
"del-cli": "^3.0.0",
6464
"es-check": "^5.1.0",
65-
"eslint": "^6.7.1",
66-
"eslint-config-prettier": "^6.7.0",
67-
"eslint-plugin-import": "^2.18.2",
65+
"eslint": "^6.8.0",
66+
"eslint-config-prettier": "^6.9.0",
67+
"eslint-plugin-import": "^2.20.0",
6868
"file-loader": "^5.0.2",
6969
"husky": "^3.1.0",
7070
"jest": "^24.9.0",
7171
"jest-junit": "^10.0.0",
7272
"jsdom": "^15.2.1",
7373
"lint-staged": "^9.5.0",
74-
"memfs": "^3.0.1",
74+
"memfs": "^3.0.4",
7575
"npm-run-all": "^4.1.5",
7676
"prettier": "^1.19.1",
77-
"sass": "^1.23.7",
78-
"sass-loader": "^8.0.0",
79-
"semver": "^7.1.0",
77+
"sass": "^1.24.5",
78+
"sass-loader": "^8.0.2",
79+
"semver": "^7.1.1",
8080
"standard-version": "^7.0.1",
81-
"webpack": "^4.41.3",
81+
"webpack": "^4.41.5",
8282
"webpack-cli": "^3.3.10",
83-
"webpack-dev-server": "^3.9.0"
83+
"webpack-dev-server": "^3.10.1"
8484
},
8585
"keywords": [
8686
"webpack"

src/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ if (content.locals) {
161161
162162
exported.use = function() {
163163
if (!(refs++)) {
164-
dispose = api(module.id, content, options);
164+
dispose = api(content, options);
165165
}
166166
167167
return exported;
@@ -249,7 +249,7 @@ var options = ${JSON.stringify(options)};
249249
options.insert = ${insert};
250250
options.singleton = ${isSingleton};
251251
252-
var update = api(module.id, content, options);
252+
var update = api(content, options);
253253
254254
var exported = content.locals ? content.locals : {};
255255

src/runtime/injectStylesIntoStyleTag.js

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,55 @@ const getTarget = (function getTarget() {
4444
};
4545
})();
4646

47-
const stylesInDom = {};
47+
const stylesInDom = [];
48+
49+
function getIndexByIdentifier(identifier) {
50+
let result = -1;
51+
52+
for (let i = 0; i < stylesInDom.length; i++) {
53+
if (stylesInDom[i].identifier === identifier) {
54+
result = i;
55+
break;
56+
}
57+
}
58+
59+
return result;
60+
}
61+
62+
function modulesToDom(list, options) {
63+
const idCountMap = {};
64+
const identifiers = [];
4865

49-
function modulesToDom(moduleId, list, options) {
5066
for (let i = 0; i < list.length; i++) {
51-
const part = {
52-
css: list[i][1],
53-
media: list[i][2],
54-
sourceMap: list[i][3],
67+
const item = list[i];
68+
const id = options.base ? item[0] + options.base : item[0];
69+
const count = idCountMap[id] || 0;
70+
const identifier = `${id} ${count}`;
71+
72+
idCountMap[id] = count + 1;
73+
74+
const index = getIndexByIdentifier(identifier);
75+
const obj = {
76+
css: item[1],
77+
media: item[2],
78+
sourceMap: item[3],
5579
};
5680

57-
if (stylesInDom[moduleId][i]) {
58-
stylesInDom[moduleId][i](part);
81+
if (index !== -1) {
82+
stylesInDom[index].references++;
83+
stylesInDom[index].updater(obj);
5984
} else {
60-
stylesInDom[moduleId].push(addStyle(part, options));
85+
stylesInDom.push({
86+
identifier,
87+
updater: addStyle(obj, options),
88+
references: 1,
89+
});
6190
}
91+
92+
identifiers.push(identifier);
6293
}
94+
95+
return identifiers;
6396
}
6497

6598
function insertStyleElement(options) {
@@ -117,7 +150,11 @@ const replaceText = (function replaceText() {
117150
})();
118151

119152
function applyToSingletonTag(style, index, remove, obj) {
120-
const css = remove ? '' : obj.css;
153+
const css = remove
154+
? ''
155+
: obj.media
156+
? `@media ${obj.media} {${obj.css}}`
157+
: obj.css;
121158

122159
// For old IE
123160
/* istanbul ignore if */
@@ -212,7 +249,7 @@ function addStyle(obj, options) {
212249
};
213250
}
214251

215-
module.exports = (moduleId, list, options) => {
252+
module.exports = (list, options) => {
216253
options = options || {};
217254

218255
// Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
@@ -221,15 +258,9 @@ module.exports = (moduleId, list, options) => {
221258
options.singleton = isOldIE();
222259
}
223260

224-
moduleId = options.base ? moduleId + options.base : moduleId;
225-
226261
list = list || [];
227262

228-
if (!stylesInDom[moduleId]) {
229-
stylesInDom[moduleId] = [];
230-
}
231-
232-
modulesToDom(moduleId, list, options);
263+
let lastIdentifiers = modulesToDom(list, options);
233264

234265
return function update(newList) {
235266
newList = newList || [];
@@ -238,20 +269,25 @@ module.exports = (moduleId, list, options) => {
238269
return;
239270
}
240271

241-
if (!stylesInDom[moduleId]) {
242-
stylesInDom[moduleId] = [];
243-
}
244-
245-
modulesToDom(moduleId, newList, options);
272+
for (let i = 0; i < lastIdentifiers.length; i++) {
273+
const identifier = lastIdentifiers[i];
274+
const index = getIndexByIdentifier(identifier);
246275

247-
for (let j = newList.length; j < stylesInDom[moduleId].length; j++) {
248-
stylesInDom[moduleId][j]();
276+
stylesInDom[index].references--;
249277
}
250278

251-
stylesInDom[moduleId].length = newList.length;
279+
const newLastIdentifiers = modulesToDom(newList, options);
280+
281+
for (let i = 0; i < lastIdentifiers.length; i++) {
282+
const identifier = lastIdentifiers[i];
283+
const index = getIndexByIdentifier(identifier);
252284

253-
if (stylesInDom[moduleId].length === 0) {
254-
delete stylesInDom[moduleId];
285+
if (stylesInDom[index].references === 0) {
286+
stylesInDom[index].updater();
287+
stylesInDom.splice(index, 1);
288+
}
255289
}
290+
291+
lastIdentifiers = newLastIdentifiers;
256292
};
257293
};
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`validate options should throw an error on the "attributes" option with "true" value 1`] = `
4-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
4+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
55
- options.attributes should be an object:
66
object {}
77
-> Adds custom attributes to tag (https://github.com/webpack-contrib/style-loader#attributes)."
88
`;
99

1010
exports[`validate options should throw an error on the "esModule" option with "true" value 1`] = `
11-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
11+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
1212
- options.esModule should be a boolean.
1313
-> Use the ES modules syntax (https://github.com/webpack-contrib/css-loader#esmodule)."
1414
`;
1515

1616
exports[`validate options should throw an error on the "injectType" option with "unknown" value 1`] = `
17-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
17+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
1818
- options.injectType should be one of these:
1919
\\"styleTag\\" | \\"singletonStyleTag\\" | \\"lazyStyleTag\\" | \\"lazySingletonStyleTag\\" | \\"linkTag\\"
2020
-> Allows to setup how styles will be injected into DOM (https://github.com/webpack-contrib/style-loader#injecttype)."
2121
`;
2222

2323
exports[`validate options should throw an error on the "insert" option with "true" value 1`] = `
24-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
24+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
2525
- options.insert should be one of these:
2626
string | function
2727
-> Inserts \`<style>\`/\`<link>\` at the given position (https://github.com/webpack-contrib/style-loader#insert).
@@ -31,49 +31,49 @@ exports[`validate options should throw an error on the "insert" option with "tru
3131
`;
3232
3333
exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = `
34-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
34+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
3535
- options has an unknown property 'unknown'. These properties are valid:
3636
object { injectType?, attributes?, insert?, base?, esModule? }"
3737
`;
3838
3939
exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = `
40-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
40+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
4141
- options has an unknown property 'unknown'. These properties are valid:
4242
object { injectType?, attributes?, insert?, base?, esModule? }"
4343
`;
4444
4545
exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = `
46-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
46+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
4747
- options has an unknown property 'unknown'. These properties are valid:
4848
object { injectType?, attributes?, insert?, base?, esModule? }"
4949
`;
5050
5151
exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = `
52-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
52+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
5353
- options has an unknown property 'unknown'. These properties are valid:
5454
object { injectType?, attributes?, insert?, base?, esModule? }"
5555
`;
5656
5757
exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = `
58-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
58+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
5959
- options has an unknown property 'unknown'. These properties are valid:
6060
object { injectType?, attributes?, insert?, base?, esModule? }"
6161
`;
6262
6363
exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = `
64-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
64+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
6565
- options has an unknown property 'unknown'. These properties are valid:
6666
object { injectType?, attributes?, insert?, base?, esModule? }"
6767
`;
6868
6969
exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = `
70-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
70+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
7171
- options has an unknown property 'unknown'. These properties are valid:
7272
object { injectType?, attributes?, insert?, base?, esModule? }"
7373
`;
7474
7575
exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = `
76-
"Invalid options object. Style Loader has been initialised using an options object that does not match the API schema.
76+
"Invalid options object. Style Loader has been initialized using an options object that does not match the API schema.
7777
- options has an unknown property 'unknown'. These properties are valid:
7878
object { injectType?, attributes?, insert?, base?, esModule? }"
7979
`;

test/manual/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ <h2>Duplicate</h2>
7575
<div class="duplicate">SHOULD BE BLUE</div>
7676
</section>
7777

78+
<section>
79+
<h2>Modules</h2>
80+
<div class="selector1">BACKGROUND SHOULD BE RED</div>
81+
<div class="selector2">BACKGROUND SHOULD BE NAVY</div>
82+
83+
<div class="toolbar"><div class="common">BACKGROUND SHOULD BE ORANGE</div></div>
84+
85+
<div class="page-btn">BACKGROUND SHOULD BE CRIMSON</div>
86+
</section>
87+
7888
<section>
7989
<h2>Custom element</h2>
8090
<custom-square l="100" c="red"></custom-square>

test/manual/src/index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import './order.css';
1212
import './nested.css';
1313
import './nested/style.css';
1414
import './custom-square';
15+
import one from './modules/one.module.css';
16+
import two from './modules/two.module.css';
17+
import toolbar from './modules/toolbar.module.css';
18+
import page from './modules/page.module.css';
1519

1620
console.log('___LOCALS___');
1721
console.log(component);
@@ -99,3 +103,14 @@ const api = useUnse.use();
99103
setTimeout(() => {
100104
api.unuse();
101105
}, 6000);
106+
107+
const selector1 = document.querySelector('.selector1');
108+
selector1.className = one.selector1;
109+
const selector2 = document.querySelector('.selector2');
110+
selector2.className = two.selector2;
111+
const toolbar1 = document.querySelector('.toolbar');
112+
toolbar1.className = toolbar.toolbar;
113+
const common1 = document.querySelector('.common');
114+
common1.className = toolbar.common;
115+
const pageBtn = document.querySelector('.page-btn');
116+
pageBtn.className = page['page-btn'];
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.common {
2+
background-color: yellow;
3+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.selector1 {
2+
composes: common from './common.module.css';
3+
background-color: red;
4+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.page-btn {
2+
composes: selector1 from './one.module.css';
3+
4+
background: crimson;
5+
}

0 commit comments

Comments
 (0)