add experimental support for build time execution

allow to execute a part of the module graph at build time
e. g. to generate code or other assets

loaders have access to that via `this.importModule(request, options)`
This commit is contained in:
Tobias Koppers 2021-04-09 15:50:25 +02:00
parent 57f04262b2
commit 065177df66
22 changed files with 799 additions and 109 deletions

View File

@ -32,7 +32,10 @@ const {
connectChunkGroupAndChunk,
connectChunkGroupParentAndChild
} = require("./GraphHelpers");
const { makeWebpackError } = require("./HookWebpackError");
const {
makeWebpackError,
tryRunOrWebpackError
} = require("./HookWebpackError");
const MainTemplate = require("./MainTemplate");
const Module = require("./Module");
const ModuleDependencyError = require("./ModuleDependencyError");
@ -71,6 +74,7 @@ const {
soonFrozenObjectDeprecation,
createFakeHook
} = require("./util/deprecation");
const processAsyncTree = require("./util/processAsyncTree");
const { getRuntimeKey } = require("./util/runtime");
const { isSourceEqual } = require("./util/source");
@ -119,6 +123,13 @@ const { isSourceEqual } = require("./util/source");
* @returns {void}
*/
/**
* @callback ExportsCallback
* @param {WebpackError=} err
* @param {any=} exports
* @returns {void}
*/
/**
* @callback DepBlockVarDependenciesCallback
* @param {Dependency} dependency
@ -158,6 +169,31 @@ const { isSourceEqual } = require("./util/source");
* @property {ChunkGraph} chunkGraph the chunk graph
*/
/**
* @typedef {Object} RuntimeRequirementsContext
* @property {ChunkGraph} chunkGraph the chunk graph
* @property {CodeGenerationResults} codeGenerationResults the code generation results
*/
/**
* @typedef {Object} RunModuleOptions
* @property {EntryOptions=} entryOptions
*/
/**
* @typedef {Object} RunModuleArgument
* @property {Module} module
* @property {object} moduleObject
* @property {CodeGenerationResult} codeGenerationResult
*/
/**
* @typedef {Object} RunModuleContext
* @property {Chunk} chunk
* @property {ChunkGraph} chunkGraph
* @property {Function} __webpack_require__
*/
/**
* @typedef {Object} EntryData
* @property {Dependency[]} dependencies dependencies of the entrypoint that should be evaluated at startup
@ -525,6 +561,9 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
"runtime"
]),
/** @type {SyncHook<[RunModuleArgument, RunModuleContext]>} */
runModule: new SyncHook(["options", "context"]),
/** @type {AsyncSeriesHook<[Iterable<Module>]>} */
finishModules: new AsyncSeriesHook(["modules"]),
/** @type {AsyncSeriesHook<[Module]>} */
@ -568,32 +607,35 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
/** @type {SyncBailHook<[], boolean>} */
shouldRecord: new SyncBailHook([]),
/** @type {SyncHook<[Chunk, Set<string>]>} */
/** @type {SyncHook<[Chunk, Set<string>, RuntimeRequirementsContext]>} */
additionalChunkRuntimeRequirements: new SyncHook([
"chunk",
"runtimeRequirements"
"runtimeRequirements",
"context"
]),
/** @type {HookMap<SyncBailHook<[Chunk, Set<string>]>>} */
/** @type {HookMap<SyncBailHook<[Chunk, Set<string>, RuntimeRequirementsContext]>>} */
runtimeRequirementInChunk: new HookMap(
() => new SyncBailHook(["chunk", "runtimeRequirements"])
() => new SyncBailHook(["chunk", "runtimeRequirements", "context"])
),
/** @type {SyncHook<[Module, Set<string>]>} */
/** @type {SyncHook<[Module, Set<string>, RuntimeRequirementsContext]>} */
additionalModuleRuntimeRequirements: new SyncHook([
"module",
"runtimeRequirements"
"runtimeRequirements",
"context"
]),
/** @type {HookMap<SyncBailHook<[Module, Set<string>]>>} */
/** @type {HookMap<SyncBailHook<[Module, Set<string>, RuntimeRequirementsContext]>>} */
runtimeRequirementInModule: new HookMap(
() => new SyncBailHook(["module", "runtimeRequirements"])
() => new SyncBailHook(["module", "runtimeRequirements", "context"])
),
/** @type {SyncHook<[Chunk, Set<string>]>} */
/** @type {SyncHook<[Chunk, Set<string>, RuntimeRequirementsContext]>} */
additionalTreeRuntimeRequirements: new SyncHook([
"chunk",
"runtimeRequirements"
"runtimeRequirements",
"context"
]),
/** @type {HookMap<SyncBailHook<[Chunk, Set<string>]>>} */
/** @type {HookMap<SyncBailHook<[Chunk, Set<string>, RuntimeRequirementsContext]>>} */
runtimeRequirementInTree: new HookMap(
() => new SyncBailHook(["chunk", "runtimeRequirements"])
() => new SyncBailHook(["chunk", "runtimeRequirements", "context"])
),
/** @type {SyncHook<[RuntimeModule, Chunk]>} */
@ -928,6 +970,8 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
this.builtModules = new WeakSet();
/** @type {WeakSet<Module>} */
this.codeGeneratedModules = new WeakSet();
/** @type {WeakSet<Module>} */
this.buildTimeExecutedModules = new WeakSet();
/** @private @type {Map<Module, Callback[]>} */
this._rebuildingModules = new Map();
/** @type {Set<string>} */
@ -2644,59 +2688,7 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o
});
}
/**
* @returns {void}
*/
processRuntimeRequirements() {
const { chunkGraph } = this;
const additionalModuleRuntimeRequirements = this.hooks
.additionalModuleRuntimeRequirements;
const runtimeRequirementInModule = this.hooks.runtimeRequirementInModule;
for (const module of this.modules) {
if (chunkGraph.getNumberOfModuleChunks(module) > 0) {
for (const runtime of chunkGraph.getModuleRuntimes(module)) {
let set;
const runtimeRequirements = this.codeGenerationResults.getRuntimeRequirements(
module,
runtime
);
if (runtimeRequirements && runtimeRequirements.size > 0) {
set = new Set(runtimeRequirements);
} else if (additionalModuleRuntimeRequirements.isUsed()) {
set = new Set();
} else {
continue;
}
additionalModuleRuntimeRequirements.call(module, set);
for (const r of set) {
const hook = runtimeRequirementInModule.get(r);
if (hook !== undefined) hook.call(module, set);
}
chunkGraph.addModuleRuntimeRequirements(module, runtime, set);
}
}
}
for (const chunk of this.chunks) {
const set = new Set();
for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
const runtimeRequirements = chunkGraph.getModuleRuntimeRequirements(
module,
chunk.runtime
);
for (const r of runtimeRequirements) set.add(r);
}
this.hooks.additionalChunkRuntimeRequirements.call(chunk, set);
for (const r of set) {
this.hooks.runtimeRequirementInChunk.for(r).call(chunk, set);
}
chunkGraph.addChunkRuntimeRequirements(chunk, set);
}
_getChunkGraphEntries() {
/** @type {Set<Chunk>} */
const treeEntries = new Set();
for (const ep of this.entrypoints.values()) {
@ -2707,8 +2699,74 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o
const chunk = ep.getRuntimeChunk();
if (chunk) treeEntries.add(chunk);
}
return treeEntries;
}
for (const treeEntry of treeEntries) {
/**
* @param {Object} options options
* @param {ChunkGraph=} options.chunkGraph the chunk graph
* @param {Iterable<Module>=} options.modules modules
* @param {Iterable<Chunk>=} options.chunks chunks
* @param {CodeGenerationResults=} options.codeGenerationResults codeGenerationResults
* @param {Iterable<Chunk>=} options.chunkGraphEntries chunkGraphEntries
* @returns {void}
*/
processRuntimeRequirements({
chunkGraph = this.chunkGraph,
modules = this.modules,
chunks = this.chunks,
codeGenerationResults = this.codeGenerationResults,
chunkGraphEntries = this._getChunkGraphEntries()
} = {}) {
const context = { chunkGraph, codeGenerationResults };
const additionalModuleRuntimeRequirements = this.hooks
.additionalModuleRuntimeRequirements;
const runtimeRequirementInModule = this.hooks.runtimeRequirementInModule;
for (const module of modules) {
if (chunkGraph.getNumberOfModuleChunks(module) > 0) {
for (const runtime of chunkGraph.getModuleRuntimes(module)) {
let set;
const runtimeRequirements = codeGenerationResults.getRuntimeRequirements(
module,
runtime
);
if (runtimeRequirements && runtimeRequirements.size > 0) {
set = new Set(runtimeRequirements);
} else if (additionalModuleRuntimeRequirements.isUsed()) {
set = new Set();
} else {
continue;
}
additionalModuleRuntimeRequirements.call(module, set, context);
for (const r of set) {
const hook = runtimeRequirementInModule.get(r);
if (hook !== undefined) hook.call(module, set, context);
}
chunkGraph.addModuleRuntimeRequirements(module, runtime, set);
}
}
}
for (const chunk of chunks) {
const set = new Set();
for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
const runtimeRequirements = chunkGraph.getModuleRuntimeRequirements(
module,
chunk.runtime
);
for (const r of runtimeRequirements) set.add(r);
}
this.hooks.additionalChunkRuntimeRequirements.call(chunk, set, context);
for (const r of set) {
this.hooks.runtimeRequirementInChunk.for(r).call(chunk, set, context);
}
chunkGraph.addChunkRuntimeRequirements(chunk, set);
}
for (const treeEntry of chunkGraphEntries) {
const set = new Set();
for (const chunk of treeEntry.getAllReferencedChunks()) {
const runtimeRequirements = chunkGraph.getChunkRuntimeRequirements(
@ -2717,22 +2775,30 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o
for (const r of runtimeRequirements) set.add(r);
}
this.hooks.additionalTreeRuntimeRequirements.call(treeEntry, set);
this.hooks.additionalTreeRuntimeRequirements.call(
treeEntry,
set,
context
);
for (const r of set) {
this.hooks.runtimeRequirementInTree.for(r).call(treeEntry, set);
this.hooks.runtimeRequirementInTree
.for(r)
.call(treeEntry, set, context);
}
chunkGraph.addTreeRuntimeRequirements(treeEntry, set);
}
}
// TODO webpack 6 make chunkGraph argument non-optional
/**
* @param {Chunk} chunk target chunk
* @param {RuntimeModule} module runtime module
* @param {ChunkGraph} chunkGraph the chunk graph
* @returns {void}
*/
addRuntimeModule(chunk, module) {
addRuntimeModule(chunk, module, chunkGraph = this.chunkGraph) {
// Deprecated ModuleGraph association
ModuleGraph.setModuleGraphForModule(module, this.moduleGraph);
@ -2741,10 +2807,10 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o
this._modules.set(module.identifier(), module);
// connect to the chunk graph
this.chunkGraph.connectChunkAndModule(chunk, module);
this.chunkGraph.connectChunkAndRuntimeModule(chunk, module);
chunkGraph.connectChunkAndModule(chunk, module);
chunkGraph.connectChunkAndRuntimeModule(chunk, module);
if (module.fullHash) {
this.chunkGraph.addFullHashModuleToChunk(chunk, module);
chunkGraph.addFullHashModuleToChunk(chunk, module);
}
// attach runtime module
@ -2762,14 +2828,14 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o
exportsInfo.setUsedForSideEffectsOnly(runtime);
}
}
this.chunkGraph.addModuleRuntimeRequirements(
chunkGraph.addModuleRuntimeRequirements(
module,
chunk.runtime,
new Set([RuntimeGlobals.requireScope])
);
// runtime modules don't need ids
this.chunkGraph.setModuleId(module, "");
chunkGraph.setModuleId(module, "");
// Call hook
this.hooks.runtimeModule.call(module, chunk);
@ -3889,6 +3955,236 @@ This prevents using hashes of each other and should be avoided.`);
);
}
/**
* @param {Module} module the module
* @param {RunModuleOptions} options options
* @param {ExportsCallback} callback callback
*/
runModule(module, options, callback) {
// Aggregate all referenced modules and ensure they are ready
const modules = new Set([module]);
processAsyncTree(
modules,
10,
(module, push, callback) => {
this.addModuleQueue.waitFor(module, err => {
if (err) return callback(err);
this.buildQueue.waitFor(module, err => {
if (err) return callback(err);
this.processDependenciesQueue.waitFor(module, err => {
if (err) return callback(err);
for (const {
module: m
} of this.moduleGraph.getOutgoingConnections(module)) {
const size = modules.size;
modules.add(m);
if (modules.size !== size) push(m);
}
callback();
});
});
});
},
err => {
if (err) return callback(makeWebpackError(err, "runModule"));
// Create new chunk graph, chunk and entrypoint for the build time execution
const chunkGraph = new ChunkGraph(this.moduleGraph);
const runtime = "build time";
const {
hashFunction,
hashDigest,
hashDigestLength
} = this.outputOptions;
const runtimeTemplate = this.runtimeTemplate;
const chunk = new Chunk("build time chunk");
chunk.id = chunk.name;
chunk.ids = [chunk.id];
chunk.runtime = runtime;
const entrypoint = new Entrypoint({
runtime,
...options.entryOptions
});
chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
connectChunkGroupAndChunk(entrypoint, chunk);
entrypoint.setRuntimeChunk(chunk);
entrypoint.setEntrypointChunk(chunk);
const chunks = new Set([chunk]);
const modulesById = new Map();
// Assign ids to modules and modules to the chunk
for (const module of modules) {
const id = module.identifier();
modulesById.set(id, module);
chunkGraph.setModuleId(module, id);
chunkGraph.connectChunkAndModule(chunk, module);
}
// Hash modules
for (const module of modules) {
this._createModuleHash(
module,
chunkGraph,
runtime,
hashFunction,
runtimeTemplate,
hashDigest,
hashDigestLength
);
}
const codeGenerationResults = new CodeGenerationResults();
/** @type {WebpackError[]} */
const errors = [];
const codeGen = (module, callback) => {
this._codeGenerationModule(
module,
runtime,
[runtime],
chunkGraph.getModuleHash(module, runtime),
this.dependencyTemplates,
chunkGraph,
this.moduleGraph,
runtimeTemplate,
errors,
codeGenerationResults,
(err, codeGenerated) => {
callback(err);
}
);
};
// Generate code for all aggregated modules
asyncLib.eachLimit(modules, 10, codeGen, err => {
if (err) return callback(makeWebpackError(err, "runModule"));
// for backward-compat temporary set the chunk graph
// TODO webpack 6
const old = this.chunkGraph;
this.chunkGraph = chunkGraph;
this.processRuntimeRequirements({
chunkGraph,
modules,
chunks,
codeGenerationResults,
chunkGraphEntries: chunks
});
this.chunkGraph = old;
const runtimeModules = chunkGraph.getChunkRuntimeModulesIterable(
chunk
);
// Hash runtime modules
for (const module of runtimeModules) {
this._createModuleHash(
module,
chunkGraph,
runtime,
hashFunction,
runtimeTemplate,
hashDigest,
hashDigestLength
);
}
// Generate code for all runtime modules
asyncLib.eachLimit(runtimeModules, 10, codeGen, err => {
if (err) return callback(makeWebpackError(err, "runModule"));
let result;
try {
const {
strictModuleErrorHandling,
strictModuleExceptionHandling
} = this.outputOptions;
const moduleCache = new Map();
const __webpack_require__ = id => {
const module = modulesById.get(id);
return __webpack_require_module__(module, id);
};
const __webpack_require_module__ = (module, id) => {
const cached = moduleCache.get(module);
if (cached !== undefined) {
if (cached.error) throw cached.error;
return cached.exports;
}
const moduleObject = {
id,
exports: {},
loaded: false,
error: undefined
};
this.buildTimeExecutedModules.add(module);
try {
moduleCache.set(module, moduleObject);
const codeGenerationResult = codeGenerationResults.get(
module,
runtime
);
tryRunOrWebpackError(
() =>
this.hooks.runModule.call(
{
codeGenerationResult,
module,
moduleObject
},
context
),
"Compilation.hooks.runModule"
);
moduleObject.loaded = true;
return moduleObject.exports;
} catch (e) {
if (strictModuleExceptionHandling) {
moduleCache.delete(module);
} else if (strictModuleErrorHandling) {
moduleObject.error = e;
}
if (!e.module) e.module = module;
throw e;
}
};
/** @type {RunModuleContext} */
const context = {
__webpack_require__,
chunk,
chunkGraph
};
for (const runtimeModule of chunkGraph.getChunkRuntimeModulesInOrder(
chunk
)) {
__webpack_require_module__(
runtimeModule,
runtimeModule.identifier()
);
}
result = __webpack_require__(module.identifier());
} catch (e) {
const err = new WebpackError(
`Execution of module code from module graph (${module.readableIdentifier(
this.requestShortener
)}) failed: ${e.message}`
);
err.stack = e.stack;
err.module = e.module;
return callback(err);
}
callback(null, result);
});
});
}
);
}
checkConstraints() {
const chunkGraph = this.chunkGraph;

View File

@ -176,6 +176,20 @@ class AssetModulesPlugin {
return result;
});
compilation.hooks.runModule.tap(
"AssetModulesPlugin",
(options, context) => {
const { codeGenerationResult } = options;
const source = codeGenerationResult.sources.get("asset");
if (source === undefined) return;
compilation.emitAsset(
codeGenerationResult.data.get("filename"),
source,
codeGenerationResult.data.get("assetInfo")
);
}
);
}
);
}

View File

@ -0,0 +1,28 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const ModuleDependency = require("./ModuleDependency");
class LoaderImportDependency extends ModuleDependency {
/**
* @param {string} request request string
*/
constructor(request) {
super(request);
this.weak = true;
}
get type() {
return "loader import";
}
get category() {
return "loaderImport";
}
}
module.exports = LoaderImportDependency;

View File

@ -8,18 +8,37 @@
const NormalModule = require("../NormalModule");
const LazySet = require("../util/LazySet");
const LoaderDependency = require("./LoaderDependency");
const LoaderImportDependency = require("./LoaderImportDependency");
/** @typedef {import("../Compilation").DepConstructor} DepConstructor */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
/**
* @callback LoadModuleCallback
* @param {Error=} err error object
* @param {string=} source source code
* @param {string | Buffer=} source source code
* @param {object=} map source map
* @param {Module=} module loaded module if successful
*/
/**
* @callback ImportModuleCallback
* @param {Error=} err error object
* @param {any=} exports exports of the evaluated module
*/
/**
* @typedef {Object} ImportModuleOptions
* @property {string=} layer the target layer
*/
class LoaderPlugin {
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
"LoaderPlugin",
@ -28,6 +47,10 @@ class LoaderPlugin {
LoaderDependency,
normalModuleFactory
);
compilation.dependencyFactories.set(
LoaderImportDependency,
normalModuleFactory
);
}
);
@ -47,7 +70,7 @@ class LoaderPlugin {
name: request
};
const factory = compilation.dependencyFactories.get(
dep.constructor
/** @type {DepConstructor} */ (dep.constructor)
);
if (factory === undefined) {
return callback(
@ -123,6 +146,71 @@ class LoaderPlugin {
}
);
};
/**
* @param {string} request the request string to load the module from
* @param {ImportModuleOptions=} options options
* @param {ImportModuleCallback=} callback callback returning the exports
* @returns {void}
*/
const importModule = (request, options, callback) => {
const dep = new LoaderImportDependency(request);
dep.loc = {
name: request
};
const factory = compilation.dependencyFactories.get(
/** @type {DepConstructor} */ (dep.constructor)
);
if (factory === undefined) {
return callback(
new Error(
`No module factory available for dependency type: ${dep.constructor.name}`
)
);
}
compilation.buildQueue.increaseParallelism();
compilation.handleModuleCreation(
{
factory,
dependencies: [dep],
originModule: loaderContext._module,
contextInfo: {
issuerLayer: options.layer
},
context: loaderContext.context,
recursive: true
},
err => {
compilation.buildQueue.decreaseParallelism();
if (err) {
return callback(err);
}
const referencedModule = moduleGraph.getModule(dep);
if (!referencedModule) {
return callback(new Error("Cannot load the module"));
}
compilation.runModule(referencedModule, {}, callback);
}
);
};
/**
* @param {string} request the request string to load the module from
* @param {ImportModuleOptions} options options
* @param {ImportModuleCallback=} callback callback returning the exports
* @returns {Promise<any> | void} exports
*/
loaderContext.importModule = (request, options, callback) => {
if (!callback) {
return new Promise((resolve, reject) => {
importModule(request, options || {}, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
}
return importModule(request, options || {}, callback);
};
}
);
});

View File

@ -29,9 +29,9 @@ class ArrayPushCallbackChunkFormatPlugin {
compilation => {
compilation.hooks.additionalChunkRuntimeRequirements.tap(
"ArrayPushCallbackChunkFormatPlugin",
(chunk, set) => {
(chunk, set, { chunkGraph }) => {
if (chunk.hasRuntime()) return;
if (compilation.chunkGraph.getNumberOfEntryModules(chunk) > 0) {
if (chunkGraph.getNumberOfEntryModules(chunk) > 0) {
set.add(RuntimeGlobals.onChunksLoaded);
set.add(RuntimeGlobals.require);
}

View File

@ -31,9 +31,9 @@ class CommonJsChunkFormatPlugin {
compilation => {
compilation.hooks.additionalChunkRuntimeRequirements.tap(
"CommonJsChunkLoadingPlugin",
(chunk, set) => {
(chunk, set, { chunkGraph }) => {
if (chunk.hasRuntime()) return;
if (compilation.chunkGraph.getNumberOfEntryModules(chunk) > 0) {
if (chunkGraph.getNumberOfEntryModules(chunk) > 0) {
set.add(RuntimeGlobals.require);
set.add(RuntimeGlobals.startupEntrypoint);
set.add(RuntimeGlobals.externalInstallChunk);

View File

@ -6,6 +6,7 @@
"use strict";
const { SyncWaterfallHook, SyncHook, SyncBailHook } = require("tapable");
const vm = require("vm");
const {
ConcatSource,
OriginalSource,
@ -369,16 +370,58 @@ class JavascriptModulesPlugin {
});
compilation.hooks.additionalTreeRuntimeRequirements.tap(
"JavascriptModulesPlugin",
(chunk, set) => {
(chunk, set, { chunkGraph }) => {
if (
!set.has(RuntimeGlobals.startupNoDefault) &&
compilation.chunkGraph.hasChunkEntryDependentChunks(chunk)
chunkGraph.hasChunkEntryDependentChunks(chunk)
) {
set.add(RuntimeGlobals.onChunksLoaded);
set.add(RuntimeGlobals.require);
}
}
);
compilation.hooks.runModule.tap(
"JavascriptModulesPlugin",
(options, context) => {
const source = options.codeGenerationResult.sources.get(
"javascript"
);
if (source === undefined) return;
const { module, moduleObject } = options;
const code = source.source();
const fn = vm.runInThisContext(
`(function(${module.moduleArgument}, ${module.exportsArgument}, __webpack_require__) {\n${code}\n/**/})`,
{
filename: module.identifier(),
lineOffset: -1
}
);
fn.call(
moduleObject.exports,
moduleObject,
moduleObject.exports,
context.__webpack_require__
);
}
);
compilation.hooks.runModule.tap(
"JavascriptModulesPlugin",
(options, context) => {
const source = options.codeGenerationResult.sources.get("runtime");
if (source === undefined) return;
const code = source.source();
const fn = vm.runInThisContext(
`(function(__webpack_require__) {\n${code}\n/**/})`,
{
filename: options.module.identifier(),
lineOffset: -1
}
);
fn.call(null, context.__webpack_require__);
}
);
}
);
}

View File

@ -12,6 +12,7 @@ const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin")
/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */
/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */
/** @typedef {import("../Compiler")} Compiler */
@ -27,6 +28,7 @@ const COMMON_LIBRARY_NAME_MESSAGE =
* @template T
* @typedef {Object} LibraryContext
* @property {Compilation} compilation
* @property {ChunkGraph} chunkGraph
* @property {T} options
*/
@ -75,7 +77,8 @@ class AbstractLibraryPlugin {
if (module) {
this.finishEntryModule(module, name, {
options,
compilation
compilation,
chunkGraph: compilation.chunkGraph
});
}
}
@ -101,10 +104,14 @@ class AbstractLibraryPlugin {
) {
compilation.hooks.additionalChunkRuntimeRequirements.tap(
_pluginName,
(chunk, set) => {
(chunk, set, { chunkGraph }) => {
const options = getOptionsForChunk(chunk);
if (options !== false) {
this.runtimeRequirements(chunk, set, { options, compilation });
this.runtimeRequirements(chunk, set, {
options,
compilation,
chunkGraph
});
}
}
);
@ -116,7 +123,11 @@ class AbstractLibraryPlugin {
hooks.render.tap(_pluginName, (source, renderContext) => {
const options = getOptionsForChunk(renderContext.chunk);
if (options === false) return source;
return this.render(source, renderContext, { options, compilation });
return this.render(source, renderContext, {
options,
compilation,
chunkGraph: compilation.chunkGraph
});
});
}
@ -131,7 +142,8 @@ class AbstractLibraryPlugin {
if (options === false) return;
return this.embedInRuntimeBailout(module, renderContext, {
options,
compilation
compilation,
chunkGraph: compilation.chunkGraph
});
}
);
@ -146,7 +158,8 @@ class AbstractLibraryPlugin {
if (options === false) return;
return this.strictRuntimeBailout(renderContext, {
options,
compilation
compilation,
chunkGraph: compilation.chunkGraph
});
});
}
@ -161,7 +174,8 @@ class AbstractLibraryPlugin {
if (options === false) return source;
return this.renderStartup(source, module, renderContext, {
options,
compilation
compilation,
chunkGraph: compilation.chunkGraph
});
}
);
@ -170,7 +184,11 @@ class AbstractLibraryPlugin {
hooks.chunkHash.tap(_pluginName, (chunk, hash, context) => {
const options = getOptionsForChunk(chunk);
if (options === false) return;
this.chunkHash(chunk, hash, context, { options, compilation });
this.chunkHash(chunk, hash, context, {
options,
compilation,
chunkGraph: compilation.chunkGraph
});
});
});
}

View File

@ -24,8 +24,7 @@ class ChunkPrefetchPreloadPlugin {
compilation => {
compilation.hooks.additionalChunkRuntimeRequirements.tap(
"ChunkPrefetchPreloadPlugin",
(chunk, set) => {
const { chunkGraph } = compilation;
(chunk, set, { chunkGraph }) => {
if (chunkGraph.getNumberOfEntryModules(chunk) === 0) return;
const startupChildChunks = chunk.getChildrenOfTypeInOrder(
chunkGraph,
@ -43,8 +42,7 @@ class ChunkPrefetchPreloadPlugin {
);
compilation.hooks.additionalTreeRuntimeRequirements.tap(
"ChunkPrefetchPreloadPlugin",
(chunk, set) => {
const { chunkGraph } = compilation;
(chunk, set, { chunkGraph }) => {
const chunkMap = chunk.getChildIdsByOrdersMap(chunkGraph, false);
if (chunkMap.prefetch) {

View File

@ -37,9 +37,9 @@ class StartupChunkDependenciesPlugin {
};
compilation.hooks.additionalTreeRuntimeRequirements.tap(
"StartupChunkDependenciesPlugin",
(chunk, set) => {
(chunk, set, { chunkGraph }) => {
if (!isEnabledForChunk(chunk)) return;
if (compilation.chunkGraph.hasChunkEntryDependentChunks(chunk)) {
if (chunkGraph.hasChunkEntryDependentChunks(chunk)) {
set.add(RuntimeGlobals.startup);
set.add(RuntimeGlobals.ensureChunk);
set.add(RuntimeGlobals.ensureChunkIncludeEntries);

View File

@ -148,6 +148,7 @@ const { makePathsRelative, parseResource } = require("../util/identifier");
* @property {boolean=} cacheable
* @property {boolean=} built
* @property {boolean=} codeGenerated
* @property {boolean=} buildTimeExecuted
* @property {boolean=} cached
* @property {boolean=} optional
* @property {boolean=} orphan
@ -1077,6 +1078,9 @@ const SIMPLE_EXTRACTORS = {
const { compilation, type } = context;
const built = compilation.builtModules.has(module);
const codeGenerated = compilation.codeGeneratedModules.has(module);
const buildTimeExecuted = compilation.buildTimeExecutedModules.has(
module
);
/** @type {{[x: string]: number}} */
const sizes = {};
for (const sourceType of module.getSourceTypes()) {
@ -1091,6 +1095,7 @@ const SIMPLE_EXTRACTORS = {
sizes,
built,
codeGenerated,
buildTimeExecuted,
cached: !built && !codeGenerated
};
Object.assign(object, statsModule);

View File

@ -306,6 +306,8 @@ const SIMPLE_PRINTERS = {
built ? yellow(formatFlag("built")) : undefined,
"module.codeGenerated": (codeGenerated, { formatFlag, yellow }) =>
codeGenerated ? yellow(formatFlag("code generated")) : undefined,
"module.buildTimeExecuted": (buildTimeExecuted, { formatFlag, green }) =>
buildTimeExecuted ? green(formatFlag("build time executed")) : undefined,
"module.cached": (cached, { formatFlag, green }) =>
cached ? green(formatFlag("cached")) : undefined,
"module.assets": (assets, { formatFlag, magenta }) =>

View File

@ -99,7 +99,7 @@ class AsyncQueue {
}
/**
* @param {T} item a item
* @param {T} item an item
* @param {Callback<R>} callback callback function
* @returns {void}
*/
@ -144,7 +144,7 @@ class AsyncQueue {
}
/**
* @param {T} item a item
* @param {T} item an item
* @returns {void}
*/
invalidate(item) {
@ -156,6 +156,29 @@ class AsyncQueue {
}
}
/**
* Waits for an already started item
* @param {T} item an item
* @param {Callback<R>} callback callback function
* @returns {void}
*/
waitFor(item, callback) {
const key = this._getKey(item);
const entry = this._entries.get(key);
if (entry === undefined) {
return callback(
new Error("waitFor can only be called for an already started item")
);
}
if (entry.state === DONE_STATE) {
process.nextTick(() => callback(entry.error, entry.result));
} else if (entry.callbacks === undefined) {
entry.callbacks = [callback];
} else {
entry.callbacks.push(callback);
}
}
/**
* @returns {void}
*/

View File

@ -421,6 +421,26 @@ describe("Defaults", () => {
"...",
],
},
"loaderImport": Object {
"aliasFields": Array [
"browser",
],
"conditionNames": Array [
"import",
"module",
"...",
],
"extensions": Array [
".js",
".json",
".wasm",
],
"mainFields": Array [
"browser",
"module",
"...",
],
},
"undefined": Object {
"aliasFields": Array [
"browser",
@ -1089,6 +1109,13 @@ describe("Defaults", () => {
@@ ... @@
- "browser",
@@ ... @@
- "aliasFields": Array [
- "browser",
- ],
+ "aliasFields": Array [],
@@ ... @@
- "browser",
@@ ... @@
- "browser",
+ "node",
@@ ... @@
@ -1223,6 +1250,13 @@ describe("Defaults", () => {
@@ ... @@
- "browser",
@@ ... @@
- "aliasFields": Array [
- "browser",
- ],
+ "aliasFields": Array [],
@@ ... @@
- "browser",
@@ ... @@
- "browser",
+ "node",
+ "electron",
@ -1339,6 +1373,13 @@ describe("Defaults", () => {
@@ ... @@
- "browser",
@@ ... @@
- "aliasFields": Array [
- "browser",
- ],
+ "aliasFields": Array [],
@@ ... @@
- "browser",
@@ ... @@
+ "node",
@@ ... @@
+ "electron",

View File

@ -0,0 +1,2 @@
export const red = "#f00";
export const green = "#0f0";

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,13 @@
import stylesheet from "./stylesheet";
import stylesheet1 from "./stylesheet?1";
import otherStylesheet from "./other-stylesheet";
it("should be able to use build-time code", () => {
expect(stylesheet).toBe(
'body { background: url("/public/assets/file.png"); color: #f00; }'
);
expect(stylesheet1).toBe(
'body { background: url("/public/assets/file.png?1"); color: #f00; }'
);
expect(otherStylesheet).toBe("body { background-color: #0f0; }");
});

View File

@ -0,0 +1,6 @@
exports.pitch = async function (remaining) {
const result = await this.importModule(
this.resourcePath + ".webpack[javascript/auto]" + "!=!" + remaining
);
return result.default || result;
};

View File

@ -0,0 +1,2 @@
import { green } from "./colors.js";
export default `body { background-color: ${green}; }`;

View File

@ -0,0 +1,4 @@
import { red } from "./colors.js";
export default `body { background: url("${
new URL("./file.png", import.meta.url).href + __resourceQuery
}"); color: ${red}; }`;

View File

@ -0,0 +1,34 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
output: {
publicPath: "/public/"
},
module: {
parser: {
javascript: {
url: "relative"
}
},
generator: {
asset: {
filename: "assets/[name][ext][query]"
}
},
rules: [
{
test: /stylesheet\.js$/,
use: "./loader",
type: "asset/source"
}
]
},
plugins: [
compiler =>
compiler.hooks.done.tap("test case", stats =>
expect(stats.compilation.getAsset("assets/file.png")).toHaveProperty(
"info",
expect.objectContaining({ sourceFilename: "file.png" })
)
)
]
};

93
types.d.ts vendored
View File

@ -345,6 +345,11 @@ declare abstract class AsyncQueue<T, K, R> {
};
add(item: T, callback: CallbackFunction<R>): void;
invalidate(item: T): void;
/**
* Waits for an already started item
*/
waitFor(item: T, callback: CallbackFunction<R>): void;
stop(): void;
increaseParallelism(): void;
decreaseParallelism(): void;
@ -1242,6 +1247,7 @@ declare class Compilation {
dependencyReferencedExports: SyncWaterfallHook<
[(string[] | ReferencedExport)[], Dependency, RuntimeSpec]
>;
runModule: SyncHook<[RunModuleArgument, RunModuleContext]>;
finishModules: AsyncSeriesHook<[Iterable<Module>]>;
finishRebuildingModule: AsyncSeriesHook<[Module]>;
unseal: SyncHook<[]>;
@ -1263,14 +1269,24 @@ declare class Compilation {
>;
afterOptimizeChunkModules: SyncHook<[Iterable<Chunk>, Iterable<Module>]>;
shouldRecord: SyncBailHook<[], boolean>;
additionalChunkRuntimeRequirements: SyncHook<[Chunk, Set<string>]>;
runtimeRequirementInChunk: HookMap<SyncBailHook<[Chunk, Set<string>], any>>;
additionalModuleRuntimeRequirements: SyncHook<[Module, Set<string>]>;
runtimeRequirementInModule: HookMap<
SyncBailHook<[Module, Set<string>], any>
additionalChunkRuntimeRequirements: SyncHook<
[Chunk, Set<string>, RuntimeRequirementsContext]
>;
runtimeRequirementInChunk: HookMap<
SyncBailHook<[Chunk, Set<string>, RuntimeRequirementsContext], any>
>;
additionalModuleRuntimeRequirements: SyncHook<
[Module, Set<string>, RuntimeRequirementsContext]
>;
runtimeRequirementInModule: HookMap<
SyncBailHook<[Module, Set<string>, RuntimeRequirementsContext], any>
>;
additionalTreeRuntimeRequirements: SyncHook<
[Chunk, Set<string>, RuntimeRequirementsContext]
>;
runtimeRequirementInTree: HookMap<
SyncBailHook<[Chunk, Set<string>, RuntimeRequirementsContext], any>
>;
additionalTreeRuntimeRequirements: SyncHook<[Chunk, Set<string>]>;
runtimeRequirementInTree: HookMap<SyncBailHook<[Chunk, Set<string>], any>>;
runtimeModule: SyncHook<[RuntimeModule, Chunk]>;
reviveModules: SyncHook<[Iterable<Module>, any]>;
beforeModuleIds: SyncHook<[Iterable<Module>]>;
@ -1376,7 +1392,7 @@ declare class Compilation {
runtimeTemplate: RuntimeTemplate;
moduleTemplates: { javascript: ModuleTemplate };
moduleGraph: ModuleGraph;
chunkGraph?: ChunkGraph;
chunkGraph: any;
codeGenerationResults: CodeGenerationResults;
processDependenciesQueue: AsyncQueue<Module, Module, Module>;
addModuleQueue: AsyncQueue<Module, string, Module>;
@ -1415,6 +1431,7 @@ declare class Compilation {
needAdditionalPass: boolean;
builtModules: WeakSet<Module>;
codeGeneratedModules: WeakSet<Module>;
buildTimeExecutedModules: WeakSet<Module>;
emittedAssets: Set<string>;
comparedForEmitAssets: Set<string>;
fileDependencies: LazySet<string>;
@ -1512,8 +1529,33 @@ declare class Compilation {
blocks: DependenciesBlock[]
): void;
codeGeneration(callback?: any): void;
processRuntimeRequirements(): void;
addRuntimeModule(chunk: Chunk, module: RuntimeModule): void;
processRuntimeRequirements(__0?: {
/**
* the chunk graph
*/
chunkGraph?: ChunkGraph;
/**
* modules
*/
modules?: Iterable<Module>;
/**
* chunks
*/
chunks?: Iterable<Chunk>;
/**
* codeGenerationResults
*/
codeGenerationResults?: CodeGenerationResults;
/**
* chunkGraphEntries
*/
chunkGraphEntries?: Iterable<Chunk>;
}): void;
addRuntimeModule(
chunk: Chunk,
module: RuntimeModule,
chunkGraph?: ChunkGraph
): void;
addChunkInGroup(
groupOptions: string | ChunkGroupOptions,
module: Module,
@ -1601,6 +1643,11 @@ declare class Compilation {
| WebpackPluginInstance
)[]
): Compiler;
runModule(
module: Module,
options: RunModuleOptions,
callback: (err?: WebpackError, exports?: any) => void
): void;
checkConstraints(): void;
/**
@ -5348,6 +5395,7 @@ declare interface KnownStatsModule {
cacheable?: boolean;
built?: boolean;
codeGenerated?: boolean;
buildTimeExecuted?: boolean;
cached?: boolean;
optional?: boolean;
orphan?: boolean;
@ -5484,6 +5532,7 @@ declare class LibManifestPlugin {
}
declare interface LibraryContext<T> {
compilation: Compilation;
chunkGraph: ChunkGraph;
options: T;
}
@ -9253,6 +9302,19 @@ type RuleSetUseItem =
options?: string | { [index: string]: any };
}
| __TypeWebpackOptions;
declare interface RunModuleArgument {
module: Module;
moduleObject: object;
codeGenerationResult: CodeGenerationResult;
}
declare interface RunModuleContext {
chunk: Chunk;
chunkGraph: ChunkGraph;
__webpack_require__: Function;
}
declare interface RunModuleOptions {
entryOptions?: EntryOptions;
}
declare class RuntimeChunkPlugin {
constructor(options?: any);
options: any;
@ -9294,6 +9356,17 @@ declare class RuntimeModule extends Module {
*/
static STAGE_TRIGGER: number;
}
declare interface RuntimeRequirementsContext {
/**
* the chunk graph
*/
chunkGraph: ChunkGraph;
/**
* the code generation results
*/
codeGenerationResults: CodeGenerationResults;
}
type RuntimeSpec = undefined | string | SortableSet<string>;
declare abstract class RuntimeSpecMap<T> {
get(runtime: RuntimeSpec): T;