Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

better support for webpack 5 #595

Merged
merged 5 commits into from
Oct 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
192 changes: 192 additions & 0 deletions src/CssLoadingRuntimeModule.js
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Loading