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

feat: add support for link preload/prefetch #1043

Merged
merged 9 commits into from Apr 16, 2024
Merged
126 changes: 123 additions & 3 deletions src/index.js
Expand Up @@ -15,6 +15,7 @@ const {
compareModulesByIdentifier,
getUndoPath,
BASE_URI,
compileBooleanMatcher,
} = require("./utils");

/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
Expand Down Expand Up @@ -105,6 +106,8 @@ const CODE_GENERATION_RESULT = {
/**
* @typedef {Object} MiniCssExtractPluginCompilationHooks
* @property {import("tapable").SyncWaterfallHook<[string, VarNames], string>} beforeTagInsert
* @property {SyncWaterfallHook<[string, Chunk]>} linkPreload
* @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch
*/

/**
Expand Down Expand Up @@ -526,7 +529,8 @@ class MiniCssExtractPlugin {

/**
* Returns all hooks for the given compilation
* @param {Compilation} compilation
* @param {Compilation} compilation the compilation
* @returns {MiniCssExtractPluginCompilationHooks} hooks
*/
static getCompilationHooks(compilation) {
let hooks = compilationHooksMap.get(compilation);
Expand All @@ -537,6 +541,8 @@ class MiniCssExtractPlugin {
["source", "varNames"],
"string"
),
linkPreload: new SyncWaterfallHook(["source", "chunk"]),
linkPrefetch: new SyncWaterfallHook(["source", "chunk"]),
};
compilationHooksMap.set(compilation, hooks);
}
Expand Down Expand Up @@ -841,6 +847,20 @@ class MiniCssExtractPlugin {
return obj;
};

/**
* @param {Chunk} chunk chunk
* @param {ChunkGraph} chunkGraph chunk graph
* @returns {boolean} true, when the chunk has css
*/
function chunkHasCss(chunk, chunkGraph) {
// this function replace:
// const chunkHasCss = require("webpack/lib/css/CssModulesPlugin").chunkHasCss;
return !!chunkGraph.getChunkModulesIterableBySourceType(
chunk,
"css/mini-extract"
);
}

class CssLoadingRuntimeModule extends RuntimeModule {
/**
* @param {Set<string>} runtimeRequirements
Expand All @@ -854,7 +874,7 @@ class MiniCssExtractPlugin {
}

generate() {
const { chunk, runtimeRequirements } = this;
const { chunkGraph, chunk, runtimeRequirements } = this;
const {
runtimeTemplate,
outputOptions: { crossOriginLoading },
Expand All @@ -863,7 +883,6 @@ class MiniCssExtractPlugin {
/** @type {Chunk} */ (chunk),
/** @type {Compilation} */ (this.compilation)
);

const withLoading =
runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) &&
Object.keys(chunkMap).length > 0;
Expand All @@ -874,6 +893,20 @@ class MiniCssExtractPlugin {
if (!withLoading && !withHmr) {
return "";
}

const conditionMap = /** @type {ChunkGraph} */ (
chunkGraph
).getChunkConditionMap(/** @type {Chunk} */ (chunk), chunkHasCss);
const hasCssMatcher = compileBooleanMatcher(conditionMap);
const withPrefetch = runtimeRequirements.has(
RuntimeGlobals.prefetchChunkHandlers
);
const withPreload = runtimeRequirements.has(
RuntimeGlobals.preloadChunkHandlers
);
const { linkPreload, linkPrefetch } =
MiniCssExtractPlugin.getCompilationHooks(compilation);

return Template.asString([
'if (typeof document === "undefined") return;',
`var createStylesheet = ${runtimeTemplate.basicFunction(
Expand Down Expand Up @@ -1088,6 +1121,87 @@ class MiniCssExtractPlugin {
)}`,
])
: "// no hmr",
"",
withPrefetch && hasCssMatcher !== false
? `${
RuntimeGlobals.prefetchChunkHandlers
}.miniCss = ${runtimeTemplate.basicFunction("chunkId", [
`if((!${
RuntimeGlobals.hasOwnProperty
}(installedCssChunks, chunkId) || installedCssChunks[chunkId] === undefined) && ${
hasCssMatcher === true ? "true" : hasCssMatcher("chunkId")
}) {`,
Template.indent([
"installedCssChunks[chunkId] = null;",
linkPrefetch.call(
Template.asString([
"var link = document.createElement('link');",
crossOriginLoading
? `link.crossOrigin = ${JSON.stringify(
crossOriginLoading
)};`
: "",
`if (${RuntimeGlobals.scriptNonce}) {`,
Template.indent(
`link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
),
"}",
'link.rel = "prefetch";',
'link.as = "style";',
`link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.require}.miniCssF(chunkId);`,
]),
/** @type {Chunk} */ (chunk)
),
"document.head.appendChild(link);",
]),
"}",
])};`
: "// no prefetching",
"",
withPreload && hasCssMatcher !== false
? `${
RuntimeGlobals.preloadChunkHandlers
}.miniCss = ${runtimeTemplate.basicFunction("chunkId", [
`if((!${
RuntimeGlobals.hasOwnProperty
}(installedCssChunks, chunkId) || installedCssChunks[chunkId] === undefined) && ${
hasCssMatcher === true ? "true" : hasCssMatcher("chunkId")
}) {`,
Template.indent([
"installedCssChunks[chunkId] = null;",
linkPreload.call(
Template.asString([
"var link = document.createElement('link');",
"link.charset = 'utf-8';",
`if (${RuntimeGlobals.scriptNonce}) {`,
Template.indent(
`link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
),
"}",
'link.rel = "preload";',
'link.as = "style";',
`link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.require}.miniCssF(chunkId);`,
crossOriginLoading
? crossOriginLoading === "use-credentials"
? 'link.crossOrigin = "use-credentials";'
: Template.asString([
"if (link.href.indexOf(window.location.origin + '/') !== 0) {",
Template.indent(
`link.crossOrigin = ${JSON.stringify(
crossOriginLoading
)};`
),
"}",
])
: "",
]),
/** @type {Chunk} */ (chunk)
),
"document.head.appendChild(link);",
]),
"}",
])};`
: "// no preloaded",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you copy this code from webpack?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, with adjustments to CSS.
Except for the getLinkElements function that I added (b785fd8).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to finish - webpack/webpack#18190, i.e. for built-in CSS support, then I will check code here, regaridng - getLinkElements, it is an intresting question, because I think browsers make different things - webpack/webpack#17497, we need to report about to chrome bug tracker, if it is a bug, they need to fix it, if not - I will improve our code late

]);
}
}
Expand Down Expand Up @@ -1149,6 +1263,12 @@ class MiniCssExtractPlugin {
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.hmrDownloadUpdateHandlers)
.tap(pluginName, handler);
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.prefetchChunkHandlers)
.tap(pluginName, handler);
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.preloadChunkHandlers)
.tap(pluginName, handler);
});
}

Expand Down