Skip to content

Commit

Permalink
fix: better support for webpack 5 (#595)
Browse files Browse the repository at this point in the history
  • Loading branch information
sokra committed Oct 2, 2020
1 parent bb09d75 commit 6e09a51
Show file tree
Hide file tree
Showing 14 changed files with 1,552 additions and 199 deletions.
8 changes: 5 additions & 3 deletions README.md
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
192 changes: 192 additions & 0 deletions src/CssLoadingRuntimeModule.js
@@ -0,0 +1,192 @@
import { RuntimeGlobals, RuntimeModule, Template, util } from 'webpack';

import { MODULE_TYPE } from './utils';

const {
comparators: { compareModulesByIdentifier },
} = util;

const getCssChunkObject = (mainChunk, compilation) => {
const obj = {};
const { chunkGraph } = compilation;

for (const chunk of mainChunk.getAllAsyncChunks()) {
const modules = chunkGraph.getOrderedChunkModulesIterable(
chunk,
compareModulesByIdentifier
);
for (const module of modules) {
if (module.type === MODULE_TYPE) {
obj[chunk.id] = 1;
break;
}
}
}

return obj;
};

module.exports = class CssLoadingRuntimeModule extends RuntimeModule {
constructor(runtimeRequirements) {
super('css loading', 10);
this.runtimeRequirements = runtimeRequirements;
}

generate() {
const { chunk, compilation, runtimeRequirements } = this;
const {
runtimeTemplate,
outputOptions: { crossOriginLoading },
} = compilation;
const chunkMap = getCssChunkObject(chunk, compilation);

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([
`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] = 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',
]);
}
};
25 changes: 24 additions & 1 deletion src/CssModule.js
Expand Up @@ -2,6 +2,12 @@ import webpack from 'webpack';

import { MODULE_TYPE } from './utils';

const TYPES = new Set([MODULE_TYPE]);
const CODE_GENERATION_RESULT = {
sources: new Map(),
runtimeRequirements: new Set(),
};

class CssModule extends webpack.Module {
constructor({
context,
Expand All @@ -20,9 +26,11 @@ class CssModule extends webpack.Module {
this.content = content;
this.media = media;
this.sourceMap = sourceMap;
this.buildInfo = {};
this.buildMeta = {};
}

// no source() so webpack doesn't do add stuff to the bundle
// no source() so webpack 4 doesn't do add stuff to the bundle

size() {
return this.content.length;
Expand All @@ -38,6 +46,16 @@ class CssModule extends webpack.Module {
}`;
}

// eslint-disable-next-line class-methods-use-this
getSourceTypes() {
return TYPES;
}

// eslint-disable-next-line class-methods-use-this
codeGeneration() {
return CODE_GENERATION_RESULT;
}

nameForCondition() {
const resource = this._identifier.split('!').pop();
const idx = resource.indexOf('?');
Expand All @@ -60,6 +78,11 @@ class CssModule extends webpack.Module {
return true;
}

// eslint-disable-next-line class-methods-use-this
needBuild(context, callback) {
callback(null, false);
}

build(options, compilation, resolver, fileSystem, callback) {
this.buildInfo = {};
this.buildMeta = {};
Expand Down

0 comments on commit 6e09a51

Please sign in to comment.