first
This commit is contained in:
379
node_modules/html-webpack-plugin/lib/cached-child-compiler.js
generated
vendored
Normal file
379
node_modules/html-webpack-plugin/lib/cached-child-compiler.js
generated
vendored
Normal file
@ -0,0 +1,379 @@
|
||||
// @ts-check
|
||||
/**
|
||||
* @file
|
||||
* Helper plugin manages the cached state of the child compilation
|
||||
*
|
||||
* To optimize performance the child compilation is running asyncronously.
|
||||
* Therefore it needs to be started in the compiler.make phase and ends after
|
||||
* the compilation.afterCompile phase.
|
||||
*
|
||||
* To prevent bugs from blocked hooks there is no promise or event based api
|
||||
* for this plugin.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```js
|
||||
const childCompilerPlugin = new PersistentChildCompilerPlugin();
|
||||
childCompilerPlugin.addEntry('./src/index.js');
|
||||
compiler.hooks.afterCompile.tapAsync('MyPlugin', (compilation, callback) => {
|
||||
console.log(childCompilerPlugin.getCompilationResult()['./src/index.js']));
|
||||
return true;
|
||||
});
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Import types
|
||||
/** @typedef {import("webpack/lib/Compiler.js")} WebpackCompiler */
|
||||
/** @typedef {import("webpack/lib/Compilation.js")} WebpackCompilation */
|
||||
/** @typedef {{hash: string, entry: any, content: string }} ChildCompilationResultEntry */
|
||||
/** @typedef {import("./file-watcher-api").Snapshot} Snapshot */
|
||||
/** @typedef {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} FileDependencies */
|
||||
/** @typedef {{
|
||||
dependencies: FileDependencies,
|
||||
compiledEntries: {[entryName: string]: ChildCompilationResultEntry}
|
||||
} | {
|
||||
dependencies: FileDependencies,
|
||||
error: Error
|
||||
}} ChildCompilationResult */
|
||||
'use strict';
|
||||
|
||||
const { HtmlWebpackChildCompiler } = require('./child-compiler');
|
||||
const fileWatcherApi = require('./file-watcher-api');
|
||||
|
||||
/**
|
||||
* This plugin is a singleton for performance reasons.
|
||||
* To keep track if a plugin does already exist for the compiler they are cached
|
||||
* in this map
|
||||
* @type {WeakMap<WebpackCompiler, PersistentChildCompilerSingletonPlugin>}}
|
||||
*/
|
||||
const compilerMap = new WeakMap();
|
||||
|
||||
class CachedChildCompilation {
|
||||
/**
|
||||
* @param {WebpackCompiler} compiler
|
||||
*/
|
||||
constructor (compiler) {
|
||||
/**
|
||||
* @private
|
||||
* @type {WebpackCompiler}
|
||||
*/
|
||||
this.compiler = compiler;
|
||||
// Create a singleton instance for the compiler
|
||||
// if there is none
|
||||
if (compilerMap.has(compiler)) {
|
||||
return;
|
||||
}
|
||||
const persistentChildCompilerSingletonPlugin = new PersistentChildCompilerSingletonPlugin();
|
||||
compilerMap.set(compiler, persistentChildCompilerSingletonPlugin);
|
||||
persistentChildCompilerSingletonPlugin.apply(compiler);
|
||||
}
|
||||
|
||||
/**
|
||||
* apply is called by the webpack main compiler during the start phase
|
||||
* @param {string} entry
|
||||
*/
|
||||
addEntry (entry) {
|
||||
const persistentChildCompilerSingletonPlugin = compilerMap.get(this.compiler);
|
||||
if (!persistentChildCompilerSingletonPlugin) {
|
||||
throw new Error(
|
||||
'PersistentChildCompilerSingletonPlugin instance not found.'
|
||||
);
|
||||
}
|
||||
persistentChildCompilerSingletonPlugin.addEntry(entry);
|
||||
}
|
||||
|
||||
getCompilationResult () {
|
||||
const persistentChildCompilerSingletonPlugin = compilerMap.get(this.compiler);
|
||||
if (!persistentChildCompilerSingletonPlugin) {
|
||||
throw new Error(
|
||||
'PersistentChildCompilerSingletonPlugin instance not found.'
|
||||
);
|
||||
}
|
||||
return persistentChildCompilerSingletonPlugin.getLatestResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result for the given entry
|
||||
* @param {string} entry
|
||||
* @returns {
|
||||
| { mainCompilationHash: string, error: Error }
|
||||
| { mainCompilationHash: string, compiledEntry: ChildCompilationResultEntry }
|
||||
}
|
||||
*/
|
||||
getCompilationEntryResult (entry) {
|
||||
const latestResult = this.getCompilationResult();
|
||||
const compilationResult = latestResult.compilationResult;
|
||||
return 'error' in compilationResult ? {
|
||||
mainCompilationHash: latestResult.mainCompilationHash,
|
||||
error: compilationResult.error
|
||||
} : {
|
||||
mainCompilationHash: latestResult.mainCompilationHash,
|
||||
compiledEntry: compilationResult.compiledEntries[entry]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class PersistentChildCompilerSingletonPlugin {
|
||||
constructor () {
|
||||
/**
|
||||
* @private
|
||||
* @type {
|
||||
| {
|
||||
isCompiling: false,
|
||||
isVerifyingCache: false,
|
||||
entries: string[],
|
||||
compiledEntries: string[],
|
||||
mainCompilationHash: string,
|
||||
compilationResult: ChildCompilationResult
|
||||
}
|
||||
| Readonly<{
|
||||
isCompiling: false,
|
||||
isVerifyingCache: true,
|
||||
entries: string[],
|
||||
previousEntries: string[],
|
||||
previousResult: ChildCompilationResult
|
||||
}>
|
||||
| Readonly <{
|
||||
isVerifyingCache: false,
|
||||
isCompiling: true,
|
||||
entries: string[],
|
||||
}>
|
||||
} the internal compilation state */
|
||||
this.compilationState = {
|
||||
isCompiling: false,
|
||||
isVerifyingCache: false,
|
||||
entries: [],
|
||||
compiledEntries: [],
|
||||
mainCompilationHash: 'initial',
|
||||
compilationResult: {
|
||||
dependencies: {
|
||||
fileDependencies: [],
|
||||
contextDependencies: [],
|
||||
missingDependencies: []
|
||||
},
|
||||
compiledEntries: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* apply is called by the webpack main compiler during the start phase
|
||||
* @param {WebpackCompiler} compiler
|
||||
*/
|
||||
apply (compiler) {
|
||||
/** @type Promise<ChildCompilationResult> */
|
||||
let childCompilationResultPromise = Promise.resolve({
|
||||
dependencies: {
|
||||
fileDependencies: [],
|
||||
contextDependencies: [],
|
||||
missingDependencies: []
|
||||
},
|
||||
compiledEntries: {}
|
||||
});
|
||||
/**
|
||||
* The main compilation hash which will only be updated
|
||||
* if the childCompiler changes
|
||||
*/
|
||||
let mainCompilationHashOfLastChildRecompile = '';
|
||||
/** @typedef{Snapshot|undefined} */
|
||||
let previousFileSystemSnapshot;
|
||||
let compilationStartTime = new Date().getTime();
|
||||
|
||||
compiler.hooks.make.tapAsync(
|
||||
'PersistentChildCompilerSingletonPlugin',
|
||||
(mainCompilation, callback) => {
|
||||
if (this.compilationState.isCompiling || this.compilationState.isVerifyingCache) {
|
||||
return callback(new Error('Child compilation has already started'));
|
||||
}
|
||||
|
||||
// Update the time to the current compile start time
|
||||
compilationStartTime = new Date().getTime();
|
||||
|
||||
// The compilation starts - adding new templates is now not possible anymore
|
||||
this.compilationState = {
|
||||
isCompiling: false,
|
||||
isVerifyingCache: true,
|
||||
previousEntries: this.compilationState.compiledEntries,
|
||||
previousResult: this.compilationState.compilationResult,
|
||||
entries: this.compilationState.entries
|
||||
};
|
||||
|
||||
// Validate cache:
|
||||
const isCacheValidPromise = this.isCacheValid(previousFileSystemSnapshot, mainCompilation);
|
||||
|
||||
let cachedResult = childCompilationResultPromise;
|
||||
childCompilationResultPromise = isCacheValidPromise.then((isCacheValid) => {
|
||||
// Reuse cache
|
||||
if (isCacheValid) {
|
||||
return cachedResult;
|
||||
}
|
||||
// Start the compilation
|
||||
const compiledEntriesPromise = this.compileEntries(
|
||||
mainCompilation,
|
||||
this.compilationState.entries
|
||||
);
|
||||
// Update snapshot as soon as we know the filedependencies
|
||||
// this might possibly cause bugs if files were changed inbetween
|
||||
// compilation start and snapshot creation
|
||||
compiledEntriesPromise.then((childCompilationResult) => {
|
||||
return fileWatcherApi.createSnapshot(childCompilationResult.dependencies, mainCompilation, compilationStartTime);
|
||||
}).then((snapshot) => {
|
||||
previousFileSystemSnapshot = snapshot;
|
||||
});
|
||||
return compiledEntriesPromise;
|
||||
});
|
||||
|
||||
// Add files to compilation which needs to be watched:
|
||||
mainCompilation.hooks.optimizeTree.tapAsync(
|
||||
'PersistentChildCompilerSingletonPlugin',
|
||||
(chunks, modules, callback) => {
|
||||
const handleCompilationDonePromise = childCompilationResultPromise.then(
|
||||
childCompilationResult => {
|
||||
this.watchFiles(
|
||||
mainCompilation,
|
||||
childCompilationResult.dependencies
|
||||
);
|
||||
});
|
||||
handleCompilationDonePromise.then(() => callback(null, chunks, modules), callback);
|
||||
}
|
||||
);
|
||||
|
||||
// Store the final compilation once the main compilation hash is known
|
||||
mainCompilation.hooks.additionalAssets.tapAsync(
|
||||
'PersistentChildCompilerSingletonPlugin',
|
||||
(callback) => {
|
||||
const didRecompilePromise = Promise.all([childCompilationResultPromise, cachedResult]).then(
|
||||
([childCompilationResult, cachedResult]) => {
|
||||
// Update if childCompilation changed
|
||||
return (cachedResult !== childCompilationResult);
|
||||
}
|
||||
);
|
||||
|
||||
const handleCompilationDonePromise = Promise.all([childCompilationResultPromise, didRecompilePromise]).then(
|
||||
([childCompilationResult, didRecompile]) => {
|
||||
// Update hash and snapshot if childCompilation changed
|
||||
if (didRecompile) {
|
||||
mainCompilationHashOfLastChildRecompile = mainCompilation.hash;
|
||||
}
|
||||
this.compilationState = {
|
||||
isCompiling: false,
|
||||
isVerifyingCache: false,
|
||||
entries: this.compilationState.entries,
|
||||
compiledEntries: this.compilationState.entries,
|
||||
compilationResult: childCompilationResult,
|
||||
mainCompilationHash: mainCompilationHashOfLastChildRecompile
|
||||
};
|
||||
});
|
||||
handleCompilationDonePromise.then(() => callback(null), callback);
|
||||
}
|
||||
);
|
||||
|
||||
// Continue compilation:
|
||||
callback(null);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new entry to the next compile run
|
||||
* @param {string} entry
|
||||
*/
|
||||
addEntry (entry) {
|
||||
if (this.compilationState.isCompiling || this.compilationState.isVerifyingCache) {
|
||||
throw new Error(
|
||||
'The child compiler has already started to compile. ' +
|
||||
"Please add entries before the main compiler 'make' phase has started or " +
|
||||
'after the compilation is done.'
|
||||
);
|
||||
}
|
||||
if (this.compilationState.entries.indexOf(entry) === -1) {
|
||||
this.compilationState.entries = [...this.compilationState.entries, entry];
|
||||
}
|
||||
}
|
||||
|
||||
getLatestResult () {
|
||||
if (this.compilationState.isCompiling || this.compilationState.isVerifyingCache) {
|
||||
throw new Error(
|
||||
'The child compiler is not done compiling. ' +
|
||||
"Please access the result after the compiler 'make' phase has started or " +
|
||||
'after the compilation is done.'
|
||||
);
|
||||
}
|
||||
return {
|
||||
mainCompilationHash: this.compilationState.mainCompilationHash,
|
||||
compilationResult: this.compilationState.compilationResult
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the cache is still valid
|
||||
* @private
|
||||
* @param {Snapshot | undefined} snapshot
|
||||
* @param {WebpackCompilation} mainCompilation
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
isCacheValid (snapshot, mainCompilation) {
|
||||
if (!this.compilationState.isVerifyingCache) {
|
||||
return Promise.reject(new Error('Cache validation can only be done right before the compilation starts'));
|
||||
}
|
||||
// If there are no entries we don't need a new child compilation
|
||||
if (this.compilationState.entries.length === 0) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
// If there are new entries the cache is invalid
|
||||
if (this.compilationState.entries !== this.compilationState.previousEntries) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
// Mark the cache as invalid if there is no snapshot
|
||||
if (!snapshot) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return fileWatcherApi.isSnapShotValid(snapshot, mainCompilation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start to compile all templates
|
||||
*
|
||||
* @private
|
||||
* @param {WebpackCompilation} mainCompilation
|
||||
* @param {string[]} entries
|
||||
* @returns {Promise<ChildCompilationResult>}
|
||||
*/
|
||||
compileEntries (mainCompilation, entries) {
|
||||
const compiler = new HtmlWebpackChildCompiler(entries);
|
||||
return compiler.compileTemplates(mainCompilation).then((result) => {
|
||||
return {
|
||||
// The compiled sources to render the content
|
||||
compiledEntries: result,
|
||||
// The file dependencies to find out if a
|
||||
// recompilation is required
|
||||
dependencies: compiler.fileDependencies,
|
||||
// The main compilation hash can be used to find out
|
||||
// if this compilation was done during the current compilation
|
||||
mainCompilationHash: mainCompilation.hash
|
||||
};
|
||||
}, error => ({
|
||||
// The compiled sources to render the content
|
||||
error,
|
||||
// The file dependencies to find out if a
|
||||
// recompilation is required
|
||||
dependencies: compiler.fileDependencies,
|
||||
// The main compilation hash can be used to find out
|
||||
// if this compilation was done during the current compilation
|
||||
mainCompilationHash: mainCompilation.hash
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {WebpackCompilation} mainCompilation
|
||||
* @param {FileDependencies} files
|
||||
*/
|
||||
watchFiles (mainCompilation, files) {
|
||||
fileWatcherApi.watchFiles(mainCompilation, files);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CachedChildCompilation
|
||||
};
|
Reference in New Issue
Block a user