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

fix: reemit assets from loaders #1811

Merged
merged 1 commit into from
Jun 21, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 19 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const getHtmlWebpackPluginHooks = require('./lib/hooks.js').getHtmlWebpackPlugin
/** @typedef {import("webpack").Compiler} Compiler */
/** @typedef {ReturnType<Compiler["getInfrastructureLogger"]>} Logger */
/** @typedef {import("webpack/lib/Compilation.js")} Compilation */
/** @typedef {Array<{ source: import('webpack').sources.Source, name: string }>} PreviousEmittedAssets */
/** @typedef {Array<{ name: string, source: import('webpack').sources.Source, info?: import('webpack').AssetInfo }>} PreviousEmittedAssets */
/** @typedef {{ publicPath: string, js: Array<string>, css: Array<string>, manifest?: string, favicon?: string }} AssetsInformationByGroups */

class HtmlWebpackPlugin {
Expand Down Expand Up @@ -1023,8 +1023,8 @@ class HtmlWebpackPlugin {
const newAssetJson = JSON.stringify(this.getAssetFiles(assetsInformationByGroups));

if (isCompilationCached && this.options.cache && assetJson.value === newAssetJson) {
previousEmittedAssets.forEach(({ name, source }) => {
compilation.emitAsset(name, source);
previousEmittedAssets.forEach(({ name, source, info }) => {
compilation.emitAsset(name, source, info);
});
return callback();
} else {
Expand Down Expand Up @@ -1084,15 +1084,26 @@ class HtmlWebpackPlugin {
if ('error' in templateResult) {
return this.options.showErrors ? prettyError(templateResult.error, compiler.context).toHtml() : 'ERROR';
}

// Allow to use a custom function / string instead
if (this.options.templateContent !== false) {
return this.options.templateContent;
}
// Once everything is compiled evaluate the html factory
// and replace it with its content
return ('compiledEntry' in templateResult)
? this.evaluateCompilationResult(templateResult.compiledEntry.content, assetsInformationByGroups.publicPath, this.options.template)
: Promise.reject(new Error('Child compilation contained no compiledEntry'));

// Once everything is compiled evaluate the html factory and replace it with its content
if ('compiledEntry' in templateResult) {
const compiledEntry = templateResult.compiledEntry;
const assets = compiledEntry.assets;

// Store assets from child compiler to reemit them later
for (const name in assets) {
previousEmittedAssets.push({ name, source: assets[name].source, info: assets[name].info });
}

return this.evaluateCompilationResult(compiledEntry.content, assetsInformationByGroups.publicPath, this.options.template);
}

return Promise.reject(new Error('Child compilation contained no compiledEntry'));
});
const templateExectutionPromise = Promise.all([assetsPromise, assetTagGroupsPromise, templateEvaluationPromise])
// Execute the template
Expand Down
6 changes: 3 additions & 3 deletions lib/cached-child-compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
/** @typedef {import("webpack").Compiler} Compiler */
/** @typedef {import("webpack").Compilation} Compilation */
/** @typedef {import("webpack/lib/FileSystemInfo").Snapshot} Snapshot */
/** @typedef {{hash: string, entry: any, content: string }} ChildCompilationResultEntry */
/** @typedef {import("./child-compiler").ChildCompilationTemplateResult} ChildCompilationTemplateResult */
/** @typedef {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} FileDependencies */
/** @typedef {{
dependencies: FileDependencies,
compiledEntries: {[entryName: string]: ChildCompilationResultEntry}
compiledEntries: {[entryName: string]: ChildCompilationTemplateResult}
} | {
dependencies: FileDependencies,
error: Error
Expand Down Expand Up @@ -96,7 +96,7 @@ class CachedChildCompilation {
* @param {string} entry
* @returns {
| { mainCompilationHash: string, error: Error }
| { mainCompilationHash: string, compiledEntry: ChildCompilationResultEntry }
| { mainCompilationHash: string, compiledEntry: ChildCompilationTemplateResult }
}
*/
getCompilationEntryResult (entry) {
Expand Down
52 changes: 37 additions & 15 deletions lib/child-compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

/** @typedef {import("webpack").Chunk} Chunk */
/** @typedef {import("webpack").sources.Source} Source */
/** @typedef {{hash: string, entry: Chunk, content: string, assets: {[name: string]: { source: Source, info: import("webpack").AssetInfo }}}} ChildCompilationTemplateResult */

let instanceId = 0;
/**
Expand All @@ -30,17 +31,11 @@ class HtmlWebpackChildCompiler {
* The template array will allow us to keep track which input generated which output
*/
this.templates = templates;
/**
* @type {Promise<{[templatePath: string]: { content: string, hash: string, entry: Chunk }}>}
*/
/** @type {Promise<{[templatePath: string]: ChildCompilationTemplateResult}>} */
this.compilationPromise; // eslint-disable-line
/**
* @type {number}
*/
/** @type {number | undefined} */
this.compilationStartedTimestamp; // eslint-disable-line
/**
* @type {number}
*/
/** @type {number | undefined} */
this.compilationEndedTimestamp; // eslint-disable-line
/**
* All file dependencies of the child compiler
Expand All @@ -51,6 +46,7 @@ class HtmlWebpackChildCompiler {

/**
* Returns true if the childCompiler is currently compiling
*
* @returns {boolean}
*/
isCompiling () {
Expand All @@ -59,6 +55,8 @@ class HtmlWebpackChildCompiler {

/**
* Returns true if the childCompiler is done compiling
*
* @returns {boolean}
*/
didCompile () {
return this.compilationEndedTimestamp !== undefined;
Expand All @@ -69,7 +67,7 @@ class HtmlWebpackChildCompiler {
* once it is started no more templates can be added
*
* @param {import('webpack').Compilation} mainCompilation
* @returns {Promise<{[templatePath: string]: { content: string, hash: string, entry: Chunk }}>}
* @returns {Promise<{[templatePath: string]: ChildCompilationTemplateResult}>}
*/
compileTemplates (mainCompilation) {
const webpack = mainCompilation.compiler.webpack;
Expand Down Expand Up @@ -125,13 +123,17 @@ class HtmlWebpackChildCompiler {
// The following config enables relative URL support for the child compiler
childCompiler.options.module = { ...childCompiler.options.module };
childCompiler.options.module.parser = { ...childCompiler.options.module.parser };
childCompiler.options.module.parser.javascript = { ...childCompiler.options.module.parser.javascript,
url: 'relative' };
childCompiler.options.module.parser.javascript = {
...childCompiler.options.module.parser.javascript,
url: 'relative'
};

this.compilationStartedTimestamp = new Date().getTime();
/** @type {Promise<{[templatePath: string]: ChildCompilationTemplateResult}>} */
this.compilationPromise = new Promise((resolve, reject) => {
/** @type {Source[]} */
const extractedAssets = [];

childCompiler.hooks.thisCompilation.tap('HtmlWebpackPlugin', (compilation) => {
compilation.hooks.processAssets.tap(
{
Expand All @@ -142,6 +144,7 @@ class HtmlWebpackChildCompiler {
temporaryTemplateNames.forEach((temporaryTemplateName) => {
if (assets[temporaryTemplateName]) {
extractedAssets.push(assets[temporaryTemplateName]);

compilation.deleteAsset(temporaryTemplateName);
}
});
Expand All @@ -151,13 +154,16 @@ class HtmlWebpackChildCompiler {

childCompiler.runAsChild((err, entries, childCompilation) => {
// Extract templates
// TODO fine a better way to store entries and results, to avoid duplicate chunks and assets
const compiledTemplates = entries
? extractedAssets.map((asset) => asset.source())
: [];

// Extract file dependencies
if (entries && childCompilation) {
this.fileDependencies = { fileDependencies: Array.from(childCompilation.fileDependencies), contextDependencies: Array.from(childCompilation.contextDependencies), missingDependencies: Array.from(childCompilation.missingDependencies) };
}

// Reject the promise if the childCompilation contains error
if (childCompilation && childCompilation.errors && childCompilation.errors.length) {
const errorDetails = childCompilation.errors.map(error => {
Expand All @@ -167,34 +173,50 @@ class HtmlWebpackChildCompiler {
}
return message;
}).join('\n');

reject(new Error('Child compilation failed:\n' + errorDetails));

return;
}

// Reject if the error object contains errors
if (err) {
reject(err);
return;
}

if (!childCompilation || !entries) {
reject(new Error('Empty child compilation'));
return;
}

/**
* @type {{[templatePath: string]: { content: string, hash: string, entry: Chunk }}}
* @type {{[templatePath: string]: ChildCompilationTemplateResult}}
*/
const result = {};

/** @type {{[name: string]: { source: Source, info: import("webpack").AssetInfo }}} */
const assets = {};

for (const asset of childCompilation.getAssets()) {
assets[asset.name] = { source: asset.source, info: asset.info };
}

compiledTemplates.forEach((templateSource, entryIndex) => {
// The compiledTemplates are generated from the entries added in
// the addTemplate function.
// Therefore the array index of this.templates should be the as entryIndex.
// Therefore, the array index of this.templates should be the as entryIndex.
result[this.templates[entryIndex]] = {
// TODO, can we have Buffer here?
content: /** @type {string} */ (templateSource),
hash: childCompilation.hash || 'XXXX',
entry: entries[entryIndex]
entry: entries[entryIndex],
assets
};
});

this.compilationEndedTimestamp = new Date().getTime();

resolve(result);
});
});
Expand Down