Skip to content

Commit

Permalink
fix: reemit assets from loaders (#1811)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait authored Jun 21, 2023
1 parent 0db3c2b commit a214736
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 26 deletions.
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

0 comments on commit a214736

Please sign in to comment.