Skip to content

Commit

Permalink
feat(hmr): add hmr
Browse files Browse the repository at this point in the history
remove hmr option for webpack 5 (now automatically)
fix/update test cases
  • Loading branch information
sokra committed Sep 29, 2020
1 parent dcdd094 commit 256ad29
Show file tree
Hide file tree
Showing 8 changed files with 1,231 additions and 129 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ module.exports = {
// you can specify a publicPath here
// by default it uses publicPath in webpackOptions.output
publicPath: '../',
hmr: process.env.NODE_ENV === 'development',
hmr: process.env.NODE_ENV === 'development', // webpack 4 only
},
},
'css-loader',
Expand Down Expand Up @@ -379,7 +379,7 @@ module.exports = {
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV === 'development',
hmr: process.env.NODE_ENV === 'development', // webpack 4 only
},
},
'css-loader',
Expand All @@ -394,6 +394,8 @@ module.exports = {

### Hot Module Reloading (HMR)

Note: HMR is automatically supported in webpack 5. No need to configure it. Skip the following:

The `mini-css-extract-plugin` supports hot reloading of actual css files in development.
Some options are provided to enable HMR of both standard stylesheets and locally scoped CSS or CSS modules.
Below is an example configuration of mini-css for HMR use with CSS modules.
Expand Down Expand Up @@ -424,7 +426,7 @@ module.exports = {
{
loader: MiniCssExtractPlugin.loader,
options: {
// only enable hot in development
// only enable hot in development (webpack 4 only)
hmr: process.env.NODE_ENV === 'development',
// if hmr does not work, this is a forceful method.
reloadAll: true,
Expand Down
194 changes: 130 additions & 64 deletions src/CssLoadingRuntimeModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,87 +40,153 @@ module.exports = class CssLoadingRuntimeModule extends RuntimeModule {
} = compilation;
const chunkMap = getCssChunkObject(chunk, compilation);

if (Object.keys(chunkMap).length === 0) return null;

const withLoading = runtimeRequirements.has(
RuntimeGlobals.ensureChunkHandlers
const withLoading =
runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) &&
Object.keys(chunkMap).length > 0;
const withHmr = runtimeRequirements.has(
RuntimeGlobals.hmrDownloadUpdateHandlers
);

if (!withLoading && !withHmr) return null;

return Template.asString([
'// object to store loaded CSS chunks',
'var installedCssChunks = {',
Template.indent(
chunk.ids.map((id) => `${JSON.stringify(id)}: 0`).join(',\n')
),
'};',
'',
`var createStylesheet = ${runtimeTemplate.basicFunction(
'fullhref, resolve, reject',
[
'var linkTag = document.createElement("link");',
'linkTag.rel = "stylesheet";',
'linkTag.type = "text/css";',
'linkTag.onload = resolve;',
'linkTag.onerror = function(event) {',
Template.indent([
'var request = event && event.target && event.target.src || fullhref;',
'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");',
'err.code = "CSS_CHUNK_LOAD_FAILED";',
'err.request = request;',
'linkTag.parentNode.removeChild(linkTag)',
'reject(err);',
]),
'};',
'linkTag.href = fullhref;',
crossOriginLoading
? Template.asString([
`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`,
Template.indent(
`linkTag.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
),
'}',
])
: '',
'var head = document.getElementsByTagName("head")[0];',
'head.appendChild(linkTag);',
'return linkTag;',
]
)};`,
`var findStylesheet = ${runtimeTemplate.basicFunction('href, fullhref', [
'var existingLinkTags = document.getElementsByTagName("link");',
'for(var i = 0; i < existingLinkTags.length; i++) {',
Template.indent([
'var tag = existingLinkTags[i];',
'var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");',
'if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return tag;',
]),
'}',
'var existingStyleTags = document.getElementsByTagName("style");',
'for(var i = 0; i < existingStyleTags.length; i++) {',
Template.indent([
'var tag = existingStyleTags[i];',
'var dataHref = tag.getAttribute("data-href");',
'if(dataHref === href || dataHref === fullhref) return tag;',
]),
'}',
])};`,
`var loadStylesheet = ${runtimeTemplate.basicFunction(
'chunkId',
`return new Promise(${runtimeTemplate.basicFunction('resolve, reject', [
`var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`,
`var fullhref = ${RuntimeGlobals.publicPath} + href;`,
'if(findStylesheet(href, fullhref)) return resolve();',
'createStylesheet(fullhref, resolve, reject);',
])});`
)}`,
withLoading
? Template.asString([
'// object to store loaded CSS chunks',
'var installedCssChunks = {',
Template.indent(
chunk.ids.map((id) => `${JSON.stringify(id)}: 0`).join(',\n')
),
'};',
'',
`${
RuntimeGlobals.ensureChunkHandlers
}.miniCss = ${runtimeTemplate.basicFunction('chunkId, promises', [
`var cssChunks = ${JSON.stringify(chunkMap)};`,
'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);',
'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {',
Template.indent([
'promises.push(installedCssChunks[chunkId] = new Promise(function(resolve, reject) {',
Template.indent([
`var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`,
`var fullhref = ${RuntimeGlobals.publicPath} + href;`,
'var existingLinkTags = document.getElementsByTagName("link");',
'for(var i = 0; i < existingLinkTags.length; i++) {',
Template.indent([
'var tag = existingLinkTags[i];',
'var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");',
'if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();',
]),
'}',
'var existingStyleTags = document.getElementsByTagName("style");',
'for(var i = 0; i < existingStyleTags.length; i++) {',
Template.indent([
'var tag = existingStyleTags[i];',
'var dataHref = tag.getAttribute("data-href");',
'if(dataHref === href || dataHref === fullhref) return resolve();',
]),
'}',
'var linkTag = document.createElement("link");',
'linkTag.rel = "stylesheet";',
'linkTag.type = "text/css";',
'linkTag.onload = resolve;',
'linkTag.onerror = function(event) {',
Template.indent([
'var request = event && event.target && event.target.src || fullhref;',
'var err = new Error("Loading CSS chunk " + chunkId + " failed.\\n(" + request + ")");',
'err.code = "CSS_CHUNK_LOAD_FAILED";',
'err.request = request;',
'delete installedCssChunks[chunkId]',
'linkTag.parentNode.removeChild(linkTag)',
'reject(err);',
]),
'};',
'linkTag.href = fullhref;',
crossOriginLoading
? Template.asString([
`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`,
Template.indent(
`linkTag.crossOrigin = ${JSON.stringify(
crossOriginLoading
)};`
),
'}',
])
: '',
'var head = document.getElementsByTagName("head")[0];',
'head.appendChild(linkTag);',
]),
'}).then(function() {',
Template.indent(['installedCssChunks[chunkId] = 0;']),
'}));',
`promises.push(installedCssChunks[chunkId] = loadStylesheet(chunkId).then(${runtimeTemplate.basicFunction(
'',
'installedCssChunks[chunkId] = 0;'
)}, ${runtimeTemplate.basicFunction('e', [
'delete installedCssChunks[chunkId];',
'throw e;',
])}));`,
]),
'}',
])};`,
])
: '// no chunk loading',
'',
withHmr
? Template.asString([
'var oldTags = [];',
'var newTags = [];',
`var applyHandler = ${runtimeTemplate.basicFunction('options', [
`return { dispose: ${runtimeTemplate.basicFunction('', [
'for(var i = 0; i < oldTags.length; i++) {',
Template.indent([
'var oldTag = oldTags[i];',
'if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);',
]),
'}',
'oldTags.length = 0;',
])}, apply: ${runtimeTemplate.basicFunction('', [
'for(var i = 0; i < newTags.length; i++) newTags[i].rel = "stylesheet";',
'newTags.length = 0;',
])} };`,
])}`,
`${
RuntimeGlobals.hmrDownloadUpdateHandlers
}.miniCss = ${runtimeTemplate.basicFunction(
'chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList',
[
'applyHandlers.push(applyHandler);',
`chunkIds.forEach(${runtimeTemplate.basicFunction('chunkId', [
`var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`,
`var fullhref = ${RuntimeGlobals.publicPath} + href;`,
'const oldTag = findStylesheet(href, fullhref);',
'if(!oldTag) return;',
`promises.push(new Promise(${runtimeTemplate.basicFunction(
'resolve, reject',
[
`var tag = createStylesheet(fullhref, ${runtimeTemplate.basicFunction(
'',
[
'tag.as = "style";',
'tag.rel = "preload";',
'resolve();',
]
)}, reject);`,
'oldTags.push(oldTag);',
'newTags.push(tag);',
]
)}));`,
])});`,
]
)}`,
])
: '// no hmr',
]);
}
};
73 changes: 47 additions & 26 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,22 @@ class MiniCssExtractPlugin {
);
}
}

if (!isWebpack4 && 'hmr' in this.options) {
throw new Error(
"The 'hmr' option doesn't exist for the mini-css-extract-plugin when using webpack 5 (it's automatically determined)"
);
}
}

/** @param {import("webpack").Compiler} compiler */
apply(compiler) {
const { splitChunks } = compiler.options.optimization;
if (splitChunks) {
if (splitChunks.defaultSizeTypes.includes('...')) {
splitChunks.defaultSizeTypes.push(MODULE_TYPE);
}
}
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
compilation.dependencyFactories.set(
CssDependency,
Expand Down Expand Up @@ -146,6 +158,10 @@ class MiniCssExtractPlugin {
(result, { chunk }) => {
const { chunkGraph } = compilation;

// We don't need hot update chunks for css
// We will use the real asset instead to update
if (chunk instanceof webpack.HotUpdateChunk) return;

const renderedModules = Array.from(
this.getChunkModules(chunk, chunkGraph)
).filter((module) => module.type === MODULE_TYPE);
Expand Down Expand Up @@ -381,32 +397,37 @@ class MiniCssExtractPlugin {
}
);
} else {
// eslint-disable-next-line global-require
const CssLoadingRuntimeModule = require('./CssLoadingRuntimeModule');

compilation.hooks.additionalTreeRuntimeRequirements.tap(
pluginName,
(chunk, set) => {
set.add(webpack.RuntimeGlobals.publicPath);
compilation.addRuntimeModule(
chunk,
new webpack.runtime.GetChunkFilenameRuntimeModule(
MODULE_TYPE,
'mini-css',
`${webpack.RuntimeGlobals.require}.miniCssF`,
(referencedChunk) =>
referencedChunk.canBeInitial()
? ({ chunk: chunkData }) =>
this.options.moduleFilename(chunkData)
: this.options.chunkFilename
)
);
compilation.addRuntimeModule(
chunk,
new CssLoadingRuntimeModule(set)
);
}
);
const enabledChunks = new WeakSet();
const handler = (chunk, set) => {
if (enabledChunks.has(chunk)) return;
enabledChunks.add(chunk);

// eslint-disable-next-line global-require
const CssLoadingRuntimeModule = require('./CssLoadingRuntimeModule');

set.add(webpack.RuntimeGlobals.publicPath);
compilation.addRuntimeModule(
chunk,
new webpack.runtime.GetChunkFilenameRuntimeModule(
MODULE_TYPE,
'mini-css',
`${webpack.RuntimeGlobals.require}.miniCssF`,
(referencedChunk) =>
referencedChunk.canBeInitial()
? ({ chunk: chunkData }) =>
this.options.moduleFilename(chunkData)
: this.options.chunkFilename,
true
)
);
compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set));
};
compilation.hooks.runtimeRequirementInTree
.for(webpack.RuntimeGlobals.ensureChunkHandlers)
.tap(pluginName, handler);
compilation.hooks.runtimeRequirementInTree
.for(webpack.RuntimeGlobals.hmrDownloadUpdateHandlers)
.tap(pluginName, handler);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,7 @@ __webpack_require__.r(__webpack_exports__);
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/compat */
/******/
/******/
/******/ // object to store loaded CSS chunks
/******/ var installedCssChunks = {
/******/ 0: 0
/******/ };/* webpack/runtime/jsonp chunk loading */
/******/ /* webpack/runtime/jsonp chunk loading */
/******/ (() => {
/******/ // no baseURI
/******/
Expand Down
8 changes: 1 addition & 7 deletions test/cases/dependOn/expected/webpack-5/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,7 @@ __webpack_require__.r(__webpack_exports__);
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/compat */
/******/
/******/
/******/ // object to store loaded CSS chunks
/******/ var installedCssChunks = {
/******/ 0: 0
/******/ };/* webpack/runtime/jsonp chunk loading */
/******/ /* webpack/runtime/jsonp chunk loading */
/******/ (() => {
/******/ // no baseURI
/******/
Expand Down
Loading

0 comments on commit 256ad29

Please sign in to comment.