Skip to content

Commit 6e09a51

Browse files
authored
fix: better support for webpack 5 (#595)
1 parent bb09d75 commit 6e09a51

File tree

14 files changed

+1552
-199
lines changed

14 files changed

+1552
-199
lines changed

README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ module.exports = {
297297
// you can specify a publicPath here
298298
// by default it uses publicPath in webpackOptions.output
299299
publicPath: '../',
300-
hmr: process.env.NODE_ENV === 'development',
300+
hmr: process.env.NODE_ENV === 'development', // webpack 4 only
301301
},
302302
},
303303
'css-loader',
@@ -379,7 +379,7 @@ module.exports = {
379379
{
380380
loader: MiniCssExtractPlugin.loader,
381381
options: {
382-
hmr: process.env.NODE_ENV === 'development',
382+
hmr: process.env.NODE_ENV === 'development', // webpack 4 only
383383
},
384384
},
385385
'css-loader',
@@ -394,6 +394,8 @@ module.exports = {
394394

395395
### Hot Module Reloading (HMR)
396396

397+
Note: HMR is automatically supported in webpack 5. No need to configure it. Skip the following:
398+
397399
The `mini-css-extract-plugin` supports hot reloading of actual css files in development.
398400
Some options are provided to enable HMR of both standard stylesheets and locally scoped CSS or CSS modules.
399401
Below is an example configuration of mini-css for HMR use with CSS modules.
@@ -424,7 +426,7 @@ module.exports = {
424426
{
425427
loader: MiniCssExtractPlugin.loader,
426428
options: {
427-
// only enable hot in development
429+
// only enable hot in development (webpack 4 only)
428430
hmr: process.env.NODE_ENV === 'development',
429431
// if hmr does not work, this is a forceful method.
430432
reloadAll: true,

src/CssLoadingRuntimeModule.js

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { RuntimeGlobals, RuntimeModule, Template, util } from 'webpack';
2+
3+
import { MODULE_TYPE } from './utils';
4+
5+
const {
6+
comparators: { compareModulesByIdentifier },
7+
} = util;
8+
9+
const getCssChunkObject = (mainChunk, compilation) => {
10+
const obj = {};
11+
const { chunkGraph } = compilation;
12+
13+
for (const chunk of mainChunk.getAllAsyncChunks()) {
14+
const modules = chunkGraph.getOrderedChunkModulesIterable(
15+
chunk,
16+
compareModulesByIdentifier
17+
);
18+
for (const module of modules) {
19+
if (module.type === MODULE_TYPE) {
20+
obj[chunk.id] = 1;
21+
break;
22+
}
23+
}
24+
}
25+
26+
return obj;
27+
};
28+
29+
module.exports = class CssLoadingRuntimeModule extends RuntimeModule {
30+
constructor(runtimeRequirements) {
31+
super('css loading', 10);
32+
this.runtimeRequirements = runtimeRequirements;
33+
}
34+
35+
generate() {
36+
const { chunk, compilation, runtimeRequirements } = this;
37+
const {
38+
runtimeTemplate,
39+
outputOptions: { crossOriginLoading },
40+
} = compilation;
41+
const chunkMap = getCssChunkObject(chunk, compilation);
42+
43+
const withLoading =
44+
runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) &&
45+
Object.keys(chunkMap).length > 0;
46+
const withHmr = runtimeRequirements.has(
47+
RuntimeGlobals.hmrDownloadUpdateHandlers
48+
);
49+
50+
if (!withLoading && !withHmr) return null;
51+
52+
return Template.asString([
53+
`var createStylesheet = ${runtimeTemplate.basicFunction(
54+
'fullhref, resolve, reject',
55+
[
56+
'var linkTag = document.createElement("link");',
57+
'linkTag.rel = "stylesheet";',
58+
'linkTag.type = "text/css";',
59+
'linkTag.onload = resolve;',
60+
'linkTag.onerror = function(event) {',
61+
Template.indent([
62+
'var request = event && event.target && event.target.src || fullhref;',
63+
'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");',
64+
'err.code = "CSS_CHUNK_LOAD_FAILED";',
65+
'err.request = request;',
66+
'linkTag.parentNode.removeChild(linkTag)',
67+
'reject(err);',
68+
]),
69+
'};',
70+
'linkTag.href = fullhref;',
71+
crossOriginLoading
72+
? Template.asString([
73+
`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`,
74+
Template.indent(
75+
`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
76+
),
77+
'}',
78+
])
79+
: '',
80+
'var head = document.getElementsByTagName("head")[0];',
81+
'head.appendChild(linkTag);',
82+
'return linkTag;',
83+
]
84+
)};`,
85+
`var findStylesheet = ${runtimeTemplate.basicFunction('href, fullhref', [
86+
'var existingLinkTags = document.getElementsByTagName("link");',
87+
'for(var i = 0; i < existingLinkTags.length; i++) {',
88+
Template.indent([
89+
'var tag = existingLinkTags[i];',
90+
'var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");',
91+
'if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return tag;',
92+
]),
93+
'}',
94+
'var existingStyleTags = document.getElementsByTagName("style");',
95+
'for(var i = 0; i < existingStyleTags.length; i++) {',
96+
Template.indent([
97+
'var tag = existingStyleTags[i];',
98+
'var dataHref = tag.getAttribute("data-href");',
99+
'if(dataHref === href || dataHref === fullhref) return tag;',
100+
]),
101+
'}',
102+
])};`,
103+
`var loadStylesheet = ${runtimeTemplate.basicFunction(
104+
'chunkId',
105+
`return new Promise(${runtimeTemplate.basicFunction('resolve, reject', [
106+
`var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`,
107+
`var fullhref = ${RuntimeGlobals.publicPath} + href;`,
108+
'if(findStylesheet(href, fullhref)) return resolve();',
109+
'createStylesheet(fullhref, resolve, reject);',
110+
])});`
111+
)}`,
112+
withLoading
113+
? Template.asString([
114+
'// object to store loaded CSS chunks',
115+
'var installedCssChunks = {',
116+
Template.indent(
117+
chunk.ids.map((id) => `${JSON.stringify(id)}: 0`).join(',\n')
118+
),
119+
'};',
120+
'',
121+
`${
122+
RuntimeGlobals.ensureChunkHandlers
123+
}.miniCss = ${runtimeTemplate.basicFunction('chunkId, promises', [
124+
`var cssChunks = ${JSON.stringify(chunkMap)};`,
125+
'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);',
126+
'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {',
127+
Template.indent([
128+
`promises.push(installedCssChunks[chunkId] = loadStylesheet(chunkId).then(${runtimeTemplate.basicFunction(
129+
'',
130+
'installedCssChunks[chunkId] = 0;'
131+
)}, ${runtimeTemplate.basicFunction('e', [
132+
'delete installedCssChunks[chunkId];',
133+
'throw e;',
134+
])}));`,
135+
]),
136+
'}',
137+
])};`,
138+
])
139+
: '// no chunk loading',
140+
'',
141+
withHmr
142+
? Template.asString([
143+
'var oldTags = [];',
144+
'var newTags = [];',
145+
`var applyHandler = ${runtimeTemplate.basicFunction('options', [
146+
`return { dispose: ${runtimeTemplate.basicFunction('', [
147+
'for(var i = 0; i < oldTags.length; i++) {',
148+
Template.indent([
149+
'var oldTag = oldTags[i];',
150+
'if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);',
151+
]),
152+
'}',
153+
'oldTags.length = 0;',
154+
])}, apply: ${runtimeTemplate.basicFunction('', [
155+
'for(var i = 0; i < newTags.length; i++) newTags[i].rel = "stylesheet";',
156+
'newTags.length = 0;',
157+
])} };`,
158+
])}`,
159+
`${
160+
RuntimeGlobals.hmrDownloadUpdateHandlers
161+
}.miniCss = ${runtimeTemplate.basicFunction(
162+
'chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList',
163+
[
164+
'applyHandlers.push(applyHandler);',
165+
`chunkIds.forEach(${runtimeTemplate.basicFunction('chunkId', [
166+
`var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`,
167+
`var fullhref = ${RuntimeGlobals.publicPath} + href;`,
168+
'const oldTag = findStylesheet(href, fullhref);',
169+
'if(!oldTag) return;',
170+
`promises.push(new Promise(${runtimeTemplate.basicFunction(
171+
'resolve, reject',
172+
[
173+
`var tag = createStylesheet(fullhref, ${runtimeTemplate.basicFunction(
174+
'',
175+
[
176+
'tag.as = "style";',
177+
'tag.rel = "preload";',
178+
'resolve();',
179+
]
180+
)}, reject);`,
181+
'oldTags.push(oldTag);',
182+
'newTags.push(tag);',
183+
]
184+
)}));`,
185+
])});`,
186+
]
187+
)}`,
188+
])
189+
: '// no hmr',
190+
]);
191+
}
192+
};

src/CssModule.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import webpack from 'webpack';
22

33
import { MODULE_TYPE } from './utils';
44

5+
const TYPES = new Set([MODULE_TYPE]);
6+
const CODE_GENERATION_RESULT = {
7+
sources: new Map(),
8+
runtimeRequirements: new Set(),
9+
};
10+
511
class CssModule extends webpack.Module {
612
constructor({
713
context,
@@ -20,9 +26,11 @@ class CssModule extends webpack.Module {
2026
this.content = content;
2127
this.media = media;
2228
this.sourceMap = sourceMap;
29+
this.buildInfo = {};
30+
this.buildMeta = {};
2331
}
2432

25-
// no source() so webpack doesn't do add stuff to the bundle
33+
// no source() so webpack 4 doesn't do add stuff to the bundle
2634

2735
size() {
2836
return this.content.length;
@@ -38,6 +46,16 @@ class CssModule extends webpack.Module {
3846
}`;
3947
}
4048

49+
// eslint-disable-next-line class-methods-use-this
50+
getSourceTypes() {
51+
return TYPES;
52+
}
53+
54+
// eslint-disable-next-line class-methods-use-this
55+
codeGeneration() {
56+
return CODE_GENERATION_RESULT;
57+
}
58+
4159
nameForCondition() {
4260
const resource = this._identifier.split('!').pop();
4361
const idx = resource.indexOf('?');
@@ -60,6 +78,11 @@ class CssModule extends webpack.Module {
6078
return true;
6179
}
6280

81+
// eslint-disable-next-line class-methods-use-this
82+
needBuild(context, callback) {
83+
callback(null, false);
84+
}
85+
6386
build(options, compilation, resolver, fileSystem, callback) {
6487
this.buildInfo = {};
6588
this.buildMeta = {};

0 commit comments

Comments
 (0)