Skip to content

Commit 256ad29

Browse files
committed
feat(hmr): add hmr
remove hmr option for webpack 5 (now automatically) fix/update test cases
1 parent dcdd094 commit 256ad29

File tree

8 files changed

+1231
-129
lines changed

8 files changed

+1231
-129
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

+130-64
Original file line numberDiff line numberDiff line change
@@ -40,87 +40,153 @@ module.exports = class CssLoadingRuntimeModule extends RuntimeModule {
4040
} = compilation;
4141
const chunkMap = getCssChunkObject(chunk, compilation);
4242

43-
if (Object.keys(chunkMap).length === 0) return null;
44-
45-
const withLoading = runtimeRequirements.has(
46-
RuntimeGlobals.ensureChunkHandlers
43+
const withLoading =
44+
runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) &&
45+
Object.keys(chunkMap).length > 0;
46+
const withHmr = runtimeRequirements.has(
47+
RuntimeGlobals.hmrDownloadUpdateHandlers
4748
);
4849

50+
if (!withLoading && !withHmr) return null;
51+
4952
return Template.asString([
50-
'// object to store loaded CSS chunks',
51-
'var installedCssChunks = {',
52-
Template.indent(
53-
chunk.ids.map((id) => `${JSON.stringify(id)}: 0`).join(',\n')
54-
),
55-
'};',
56-
'',
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+
)}`,
57112
withLoading
58113
? 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+
'',
59121
`${
60122
RuntimeGlobals.ensureChunkHandlers
61123
}.miniCss = ${runtimeTemplate.basicFunction('chunkId, promises', [
62124
`var cssChunks = ${JSON.stringify(chunkMap)};`,
63125
'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);',
64126
'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {',
65127
Template.indent([
66-
'promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {',
67-
Template.indent([
68-
`var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`,
69-
`var fullhref = ${RuntimeGlobals.publicPath} + href;`,
70-
'var existingLinkTags = document.getElementsByTagName("link");',
71-
'for(var i = 0; i < existingLinkTags.length; i++) {',
72-
Template.indent([
73-
'var tag = existingLinkTags[i];',
74-
'var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");',
75-
'if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();',
76-
]),
77-
'}',
78-
'var existingStyleTags = document.getElementsByTagName("style");',
79-
'for(var i = 0; i < existingStyleTags.length; i++) {',
80-
Template.indent([
81-
'var tag = existingStyleTags[i];',
82-
'var dataHref = tag.getAttribute("data-href");',
83-
'if(dataHref === href || dataHref === fullhref) return resolve();',
84-
]),
85-
'}',
86-
'var linkTag = document.createElement("link");',
87-
'linkTag.rel = "stylesheet";',
88-
'linkTag.type = "text/css";',
89-
'linkTag.onload = resolve;',
90-
'linkTag.onerror = function(event) {',
91-
Template.indent([
92-
'var request = event && event.target && event.target.src || fullhref;',
93-
'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");',
94-
'err.code = "CSS_CHUNK_LOAD_FAILED";',
95-
'err.request = request;',
96-
'delete installedCssChunks[chunkId]',
97-
'linkTag.parentNode.removeChild(linkTag)',
98-
'reject(err);',
99-
]),
100-
'};',
101-
'linkTag.href = fullhref;',
102-
crossOriginLoading
103-
? Template.asString([
104-
`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`,
105-
Template.indent(
106-
`linkTag.crossOrigin = ${JSON.stringify(
107-
crossOriginLoading
108-
)};`
109-
),
110-
'}',
111-
])
112-
: '',
113-
'var head = document.getElementsByTagName("head")[0];',
114-
'head.appendChild(linkTag);',
115-
]),
116-
'}).then(function() {',
117-
Template.indent(['installedCssChunks[chunkId] = 0;']),
118-
'}));',
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+
])}));`,
119135
]),
120136
'}',
121137
])};`,
122138
])
123139
: '// 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',
124190
]);
125191
}
126192
};

src/index.js

+47-26
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,22 @@ class MiniCssExtractPlugin {
5757
);
5858
}
5959
}
60+
61+
if (!isWebpack4 && 'hmr' in this.options) {
62+
throw new Error(
63+
"The 'hmr' option doesn't exist for the mini-css-extract-plugin when using webpack 5 (it's automatically determined)"
64+
);
65+
}
6066
}
6167

6268
/** @param {import("webpack").Compiler} compiler */
6369
apply(compiler) {
70+
const { splitChunks } = compiler.options.optimization;
71+
if (splitChunks) {
72+
if (splitChunks.defaultSizeTypes.includes('...')) {
73+
splitChunks.defaultSizeTypes.push(MODULE_TYPE);
74+
}
75+
}
6476
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
6577
compilation.dependencyFactories.set(
6678
CssDependency,
@@ -146,6 +158,10 @@ class MiniCssExtractPlugin {
146158
(result, { chunk }) => {
147159
const { chunkGraph } = compilation;
148160

161+
// We don't need hot update chunks for css
162+
// We will use the real asset instead to update
163+
if (chunk instanceof webpack.HotUpdateChunk) return;
164+
149165
const renderedModules = Array.from(
150166
this.getChunkModules(chunk, chunkGraph)
151167
).filter((module) => module.type === MODULE_TYPE);
@@ -381,32 +397,37 @@ class MiniCssExtractPlugin {
381397
}
382398
);
383399
} else {
384-
// eslint-disable-next-line global-require
385-
const CssLoadingRuntimeModule = require('./CssLoadingRuntimeModule');
386-
387-
compilation.hooks.additionalTreeRuntimeRequirements.tap(
388-
pluginName,
389-
(chunk, set) => {
390-
set.add(webpack.RuntimeGlobals.publicPath);
391-
compilation.addRuntimeModule(
392-
chunk,
393-
new webpack.runtime.GetChunkFilenameRuntimeModule(
394-
MODULE_TYPE,
395-
'mini-css',
396-
`${webpack.RuntimeGlobals.require}.miniCssF`,
397-
(referencedChunk) =>
398-
referencedChunk.canBeInitial()
399-
? ({ chunk: chunkData }) =>
400-
this.options.moduleFilename(chunkData)
401-
: this.options.chunkFilename
402-
)
403-
);
404-
compilation.addRuntimeModule(
405-
chunk,
406-
new CssLoadingRuntimeModule(set)
407-
);
408-
}
409-
);
400+
const enabledChunks = new WeakSet();
401+
const handler = (chunk, set) => {
402+
if (enabledChunks.has(chunk)) return;
403+
enabledChunks.add(chunk);
404+
405+
// eslint-disable-next-line global-require
406+
const CssLoadingRuntimeModule = require('./CssLoadingRuntimeModule');
407+
408+
set.add(webpack.RuntimeGlobals.publicPath);
409+
compilation.addRuntimeModule(
410+
chunk,
411+
new webpack.runtime.GetChunkFilenameRuntimeModule(
412+
MODULE_TYPE,
413+
'mini-css',
414+
`${webpack.RuntimeGlobals.require}.miniCssF`,
415+
(referencedChunk) =>
416+
referencedChunk.canBeInitial()
417+
? ({ chunk: chunkData }) =>
418+
this.options.moduleFilename(chunkData)
419+
: this.options.chunkFilename,
420+
true
421+
)
422+
);
423+
compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set));
424+
};
425+
compilation.hooks.runtimeRequirementInTree
426+
.for(webpack.RuntimeGlobals.ensureChunkHandlers)
427+
.tap(pluginName, handler);
428+
compilation.hooks.runtimeRequirementInTree
429+
.for(webpack.RuntimeGlobals.hmrDownloadUpdateHandlers)
430+
.tap(pluginName, handler);
410431
}
411432
});
412433
}

test/cases/dependOn-multiple-files-per-entry/expected/webpack-5/common.js

+1-7
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,7 @@ __webpack_require__.r(__webpack_exports__);
103103
/******/ };
104104
/******/ })();
105105
/******/
106-
/******/ /* webpack/runtime/compat */
107-
/******/
108-
/******/
109-
/******/ // object to store loaded CSS chunks
110-
/******/ var installedCssChunks = {
111-
/******/ 0: 0
112-
/******/ };/* webpack/runtime/jsonp chunk loading */
106+
/******/ /* webpack/runtime/jsonp chunk loading */
113107
/******/ (() => {
114108
/******/ // no baseURI
115109
/******/

test/cases/dependOn/expected/webpack-5/common.js

+1-7
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,7 @@ __webpack_require__.r(__webpack_exports__);
8686
/******/ };
8787
/******/ })();
8888
/******/
89-
/******/ /* webpack/runtime/compat */
90-
/******/
91-
/******/
92-
/******/ // object to store loaded CSS chunks
93-
/******/ var installedCssChunks = {
94-
/******/ 0: 0
95-
/******/ };/* webpack/runtime/jsonp chunk loading */
89+
/******/ /* webpack/runtime/jsonp chunk loading */
9690
/******/ (() => {
9791
/******/ // no baseURI
9892
/******/

0 commit comments

Comments
 (0)