Skip to content

Commit 86adece

Browse files
authored
Merge pull request #55 from webpack-contrib/feature/no-dup
avoid adding styles multiple times
2 parents e062cd2 + a534432 commit 86adece

17 files changed

+1905
-72
lines changed

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/dist
33
/test/cases
44
/test/js
5+
/test/manual

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ module.exports = {
6666
}
6767
```
6868

69+
### Features
70+
71+
#### Using preloaded or inlined CSS
72+
73+
The runtime code detects already added CSS via `<link>` or `<style>` tag.
74+
This can be useful when injecting CSS on server-side for Server-Side-Rendering.
75+
The `href` of the `<link>` tag has to match the URL that will be used for loading the CSS chunk.
76+
The `data-href` attribute can be used for `<link>` and `<style>` too.
77+
When inlining CSS `data-href` must be used.
78+
6979
<h2 align="center">Maintainers</h2>
7080

7181
<table>

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"test": "jest",
2323
"test:watch": "jest --watch",
2424
"test:coverage": "jest --collectCoverageFrom='src/**/*.js' --coverage",
25+
"test:manual": "webpack-dev-server test/manual/src/index.js --open --config test/manual/webpack.config.js",
2526
"travis:lint": "npm run lint && npm run security",
2627
"travis:test": "npm run test -- --runInBand",
2728
"travis:coverage": "npm run test:coverage -- --runInBand"
@@ -44,7 +45,9 @@
4445
"pre-commit": "^1.2.2",
4546
"standard-version": "^4.3.0",
4647
"webpack": "^4.1.0",
47-
"webpack-defaults": "^1.6.0"
48+
"webpack-cli": "^2.0.13",
49+
"webpack-defaults": "^1.6.0",
50+
"webpack-dev-server": "^3.1.1"
4851
},
4952
"files": [
5053
"dist"

src/index.js

+32-2
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,43 @@ class MiniCssExtractPlugin {
197197
Template.indent([
198198
'promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {',
199199
Template.indent([
200+
201+
`var href = ${linkHrefPath};`,
202+
`var fullhref = ${mainTemplate.requireFn}.p + href;`,
203+
'var existingLinkTags = document.getElementsByTagName("link");',
204+
'for(var i = 0; i < existingLinkTags.length; i++) {',
205+
Template.indent([
206+
'var tag = existingLinkTags[i];',
207+
'var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");',
208+
'if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();',
209+
]),
210+
'}',
211+
'var existingStyleTags = document.getElementsByTagName("style");',
212+
'for(var i = 0; i < existingStyleTags.length; i++) {',
213+
Template.indent([
214+
'var tag = existingStyleTags[i];',
215+
'var dataHref = tag.getAttribute("data-href");',
216+
'if(dataHref === href || dataHref === fullhref) return resolve();',
217+
]),
218+
'}',
200219
'var linkTag = document.createElement("link");',
201220
'linkTag.rel = "stylesheet";',
221+
'linkTag.type = "text/css";',
202222
'linkTag.onload = resolve;',
203-
'linkTag.onerror = reject;',
204-
`linkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`,
223+
'linkTag.onerror = function(event) {',
224+
Template.indent([
225+
'var request = event && event.target && event.target.src || fullhref;',
226+
'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");',
227+
'err.request = request;',
228+
'reject(err);',
229+
]),
230+
'};',
231+
'linkTag.href = fullhref;',
205232
'var head = document.getElementsByTagName("head")[0];',
206233
'head.appendChild(linkTag);',
234+
]),
235+
'}).then(function() {',
236+
Template.indent([
207237
'installedCssChunks[chunkId] = 0;',
208238
]),
209239
'}));',

test/manual/README.md

Whitespace-only changes.

test/manual/index.html

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<title>mini-css-extract-plugin testcase</title>
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
<link rel="stylesheet" type="text/css" href="/dist/preloaded1.css" />
9+
<style>
10+
.test {
11+
background: lightcoral;
12+
}
13+
.errors {
14+
font-weight: bold;
15+
color: darkred;
16+
}
17+
.preloaded-css1 {
18+
background: lightgreen;
19+
}
20+
</style>
21+
<style data-href="preloaded2.css">
22+
.preloaded-css2 {
23+
background: lightgreen;
24+
}
25+
</style>
26+
<link rel="stylesheet" type="text/css" href="/dist/main.css" />
27+
</head>
28+
<body>
29+
<div class="test initial-css">
30+
Initial CSS: Must be green
31+
</div>
32+
<div class="test lazy-css">
33+
<p>Lazy CSS: Must be red, but turn green when <button class="lazy-button">pressing this button</button>.</p>
34+
<p>But turn orange, when <button class="lazy-button2">pressing this button</button>. Additional clicks have no effect.</p>
35+
<p>Refresh and press buttons in reverse order: This should turn green instead.</p>
36+
</div>
37+
<div class="test preloaded-css1">
38+
<p>Preloaded CSS: Must be green.</p>
39+
<p><button class="preloaded-button1">Pressing this button</button> displays an alert but has no styling effect.</p>
40+
</div>
41+
<div class="test preloaded-css2">
42+
<p>Preloaded inlined CSS: Must be green.</p>
43+
<p><button class="preloaded-button2">Pressing this button</button> displays an alert but has no styling effect.</p>
44+
</div>
45+
<div class="errors"></div>
46+
<script async defer src="/dist/main.js"></script>
47+
</body>
48+
</html>

test/manual/src/index.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import './initial.css';
2+
3+
const handleError = (err) => {
4+
document.querySelector(".errors").textContent += `\n${err.toString()}`;
5+
console.error(err);
6+
}
7+
8+
const makeButton = (className, fn) => {
9+
const button = document.querySelector(className);
10+
button.addEventListener("click", () => {
11+
button.disabled = true;
12+
fn().then(() => {
13+
button.disabled = false;
14+
}).catch(handleError);
15+
});
16+
}
17+
18+
makeButton(".lazy-button", () => import('./lazy.js'));
19+
makeButton(".lazy-button2", () => import('./lazy2.css'));
20+
21+
makeButton(".preloaded-button1", () => import(/* webpackChunkName: "preloaded1" */ './preloaded1'));
22+
makeButton(".preloaded-button2", () => import(/* webpackChunkName: "preloaded2" */ './preloaded2'));

test/manual/src/initial.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.initial-css {
2+
background: lightgreen;
3+
}

test/manual/src/lazy.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.lazy-css {
2+
background: lightgreen;
3+
}

test/manual/src/lazy.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './lazy.css';

test/manual/src/lazy2.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.lazy-css {
2+
background: peru;
3+
}

test/manual/src/preloaded1.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.preloaded-css1 {
2+
background: red;
3+
}

test/manual/src/preloaded1.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import './preloaded1.css';
2+
3+
alert('Ok');

test/manual/src/preloaded2.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.preloaded-css2 {
2+
background: red;
3+
}

test/manual/src/preloaded2.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import './preloaded2.css';
2+
3+
alert('Ok');

test/manual/webpack.config.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const Self = require('../../');
2+
3+
module.exports = {
4+
mode: 'development',
5+
output: {
6+
publicPath: '/dist/',
7+
},
8+
module: {
9+
rules: [
10+
{
11+
test: /\.css$/,
12+
use: [
13+
Self.loader,
14+
'css-loader',
15+
],
16+
},
17+
],
18+
},
19+
plugins: [
20+
new Self({
21+
filename: '[name].css',
22+
}),
23+
],
24+
devServer: {
25+
contentBase: __dirname,
26+
},
27+
};

0 commit comments

Comments
 (0)