Merge pull request #13102 from webpack/feature/built-time-execution

This commit is contained in:
Tobias Koppers 2021-04-12 15:00:32 +02:00 committed by GitHub
commit 03961f3391
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1093 additions and 142 deletions

View File

@ -1105,6 +1105,10 @@ export interface Experiments {
* Support WebAssembly as asynchronous EcmaScript Module.
*/
asyncWebAssembly?: boolean;
/**
* Enable build-time execution of modules from the module graph for plugins and loaders.
*/
executeModule?: boolean;
/**
* Enable module and chunk layers.
*/

View File

@ -15,7 +15,7 @@ const WebpackError = require("./WebpackError");
* @returns {Module[]} sorted version of original modules
*/
const sortModules = modules => {
return modules.slice().sort((a, b) => {
return modules.sort((a, b) => {
const aIdent = a.identifier();
const bIdent = b.identifier();
/* istanbul ignore next */
@ -52,11 +52,11 @@ const createModulesListMessage = (modules, moduleGraph) => {
class CaseSensitiveModulesWarning extends WebpackError {
/**
* Creates an instance of CaseSensitiveModulesWarning.
* @param {Module[]} modules modules that were detected
* @param {Iterable<Module>} modules modules that were detected
* @param {ModuleGraph} moduleGraph the module graph
*/
constructor(modules, moduleGraph) {
const sortedModules = sortModules(modules);
const sortedModules = sortModules(Array.from(modules));
const modulesList = createModulesListMessage(sortedModules, moduleGraph);
super(`There are multiple modules with names that only differ in casing.
This can lead to unexpected behavior when compiling on a filesystem with other case-semantic.

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 ExecuteModuleCallback
* @param {WebpackError=} err
* @param {ExecuteModuleResult=} result
* @returns {void}
*/
/**
* @callback DepBlockVarDependenciesCallback
* @param {Dependency} dependency
@ -158,6 +169,42 @@ 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} ExecuteModuleOptions
* @property {EntryOptions=} entryOptions
*/
/**
* @typedef {Object} ExecuteModuleResult
* @property {any} exports
* @property {Map<string, { source: Source, info: AssetInfo }>} assets
* @property {LazySet<string>} fileDependencies
* @property {LazySet<string>} contextDependencies
* @property {LazySet<string>} missingDependencies
* @property {LazySet<string>} buildDependencies
*/
/**
* @typedef {Object} ExecuteModuleArgument
* @property {Module} module
* @property {object} moduleObject
* @property {CodeGenerationResult} codeGenerationResult
*/
/**
* @typedef {Object} ExecuteModuleContext
* @property {Map<string, { source: Source, info: AssetInfo }>} assets
* @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 +572,9 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
"runtime"
]),
/** @type {SyncHook<[ExecuteModuleArgument, ExecuteModuleContext]>} */
executeModule: new SyncHook(["options", "context"]),
/** @type {AsyncSeriesHook<[Iterable<Module>]>} */
finishModules: new AsyncSeriesHook(["modules"]),
/** @type {AsyncSeriesHook<[Module]>} */
@ -568,32 +618,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]>} */
@ -824,6 +877,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
});
this.moduleGraph = new ModuleGraph();
/** @type {ChunkGraph} */
this.chunkGraph = undefined;
/** @type {CodeGenerationResults} */
this.codeGenerationResults = undefined;
@ -928,6 +982,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>} */
@ -1435,6 +1491,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
* @property {Partial<ModuleFactoryCreateDataContextInfo>=} contextInfo
* @property {string=} context
* @property {boolean=} recursive recurse into dependencies of the created module
* @property {boolean=} connectOrigin connect the resolved module with the origin module
*/
/**
@ -1449,7 +1506,8 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
originModule,
contextInfo,
context,
recursive = true
recursive = true,
connectOrigin = recursive
},
callback
) {
@ -1497,7 +1555,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
for (let i = 0; i < dependencies.length; i++) {
const dependency = dependencies[i];
moduleGraph.setResolvedModule(
recursive ? originModule : null,
connectOrigin ? originModule : null,
dependency,
module
);
@ -2644,59 +2702,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 +2713,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 +2789,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 +2821,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 +2842,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 +3969,301 @@ This prevents using hashes of each other and should be avoided.`);
);
}
/**
* @param {Module} module the module
* @param {ExecuteModuleOptions} options options
* @param {ExecuteModuleCallback} callback callback
*/
executeModule(module, options, callback) {
// Aggregate all referenced modules and ensure they are ready
const modules = new Set([module]);
processAsyncTree(
modules,
10,
/**
* @param {Module} module the module
* @param {function(Module): void} push push more jobs
* @param {Callback} callback callback
* @returns {void}
*/
(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(err);
// 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]);
/** @type {Map<string, Module>} */
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 = [];
/**
* @param {Module} module the module
* @param {Callback} callback callback
* @returns {void}
*/
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);
}
);
};
const reportErrors = () => {
if (errors.length > 0) {
errors.sort(
compareSelect(err => err.module, compareModulesByIdentifier)
);
for (const error of errors) {
this.errors.push(error);
}
errors.length = 0;
}
};
// Generate code for all aggregated modules
asyncLib.eachLimit(modules, 10, codeGen, err => {
if (err) return callback(err);
reportErrors();
// 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(err);
reportErrors();
let exports;
/** @type {ExecuteModuleResult["fileDependencies"]} */
const fileDependencies = new LazySet();
/** @type {ExecuteModuleResult["contextDependencies"]} */
const contextDependencies = new LazySet();
/** @type {ExecuteModuleResult["missingDependencies"]} */
const missingDependencies = new LazySet();
/** @type {ExecuteModuleResult["buildDependencies"]} */
const buildDependencies = new LazySet();
/** @type {ExecuteModuleResult["assets"]} */
const assets = new Map();
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);
};
/**
*
* @param {Module} module the module
* @param {string} id id
* @returns {any} exports
*/
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);
module.addCacheDependencies(
fileDependencies,
contextDependencies,
missingDependencies,
buildDependencies
);
if (module.buildInfo && module.buildInfo.assets) {
const { assets: moduleAssets, assetsInfo } = module.buildInfo;
for (const assetName of Object.keys(moduleAssets)) {
assets.set(assetName, {
source: moduleAssets[assetName],
info: assetsInfo ? assetsInfo.get(assetName) : undefined
});
}
}
try {
moduleCache.set(module, moduleObject);
const codeGenerationResult = codeGenerationResults.get(
module,
runtime
);
tryRunOrWebpackError(
() =>
this.hooks.executeModule.call(
{
codeGenerationResult,
module,
moduleObject
},
context
),
"Compilation.hooks.executeModule"
);
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 {ExecuteModuleContext} */
const context = {
assets,
__webpack_require__,
chunk,
chunkGraph
};
for (const runtimeModule of chunkGraph.getChunkRuntimeModulesInOrder(
chunk
)) {
__webpack_require_module__(
runtimeModule,
runtimeModule.identifier()
);
}
exports = __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, {
exports,
assets,
fileDependencies,
contextDependencies,
missingDependencies,
buildDependencies
});
});
});
}
);
}
checkConstraints() {
const chunkGraph = this.chunkGraph;

View File

@ -496,7 +496,21 @@ class NormalModuleFactory extends ModuleFactory {
for (const loader of loaders) allLoaders.push(loader);
}
for (const loader of preLoaders) allLoaders.push(loader);
const type = settings.type;
let type = settings.type;
if (!type) {
const resource =
(matchResourceData && matchResourceData.resource) ||
resourceData.resource;
let match;
if (
typeof resource === "string" &&
(match = /\.webpack\[([^\]]+)\]$/.exec(resource))
) {
type = match[1];
} else {
type = "javascript/auto";
}
}
const resolveOptions = settings.resolve;
const layer = settings.layer;
if (layer !== undefined && !layers) {

View File

@ -8,6 +8,7 @@
const CaseSensitiveModulesWarning = require("./CaseSensitiveModulesWarning");
/** @typedef {import("./Compiler")} Compiler */
/** @typedef {import("./Module")} Module */
class WarnCaseSensitiveModulesPlugin {
/**
@ -20,21 +21,26 @@ class WarnCaseSensitiveModulesPlugin {
"WarnCaseSensitiveModulesPlugin",
compilation => {
compilation.hooks.seal.tap("WarnCaseSensitiveModulesPlugin", () => {
/** @type {Map<string, Map<string, Module>>} */
const moduleWithoutCase = new Map();
for (const module of compilation.modules) {
const identifier = module.identifier().toLowerCase();
const array = moduleWithoutCase.get(identifier);
if (array) {
array.push(module);
} else {
moduleWithoutCase.set(identifier, [module]);
const identifier = module.identifier();
const lowerIdentifier = identifier.toLowerCase();
let map = moduleWithoutCase.get(lowerIdentifier);
if (map === undefined) {
map = new Map();
moduleWithoutCase.set(lowerIdentifier, map);
}
map.set(identifier, module);
}
for (const pair of moduleWithoutCase) {
const array = pair[1];
if (array.length > 1) {
const map = pair[1];
if (map.size > 1) {
compilation.warnings.push(
new CaseSensitiveModulesWarning(array, compilation.moduleGraph)
new CaseSensitiveModulesWarning(
map.values(),
compilation.moduleGraph
)
);
}
}

View File

@ -296,7 +296,9 @@ class WebpackOptionsApply extends OptionsApply {
new RequireJsStuffPlugin().apply(compiler);
}
new CommonJsPlugin().apply(compiler);
new LoaderPlugin().apply(compiler);
new LoaderPlugin({
enableExecuteModule: options.experiments.executeModule
}).apply(compiler);
if (options.node !== false) {
const NodeStuffPlugin = require("./NodeStuffPlugin");
new NodeStuffPlugin(options.node).apply(compiler);

View File

@ -176,6 +176,19 @@ class AssetModulesPlugin {
return result;
});
compilation.hooks.executeModule.tap(
"AssetModulesPlugin",
(options, context) => {
const { codeGenerationResult } = options;
const source = codeGenerationResult.sources.get("asset");
if (source === undefined) return;
context.assets.set(codeGenerationResult.data.get("filename"), {
source,
info: codeGenerationResult.data.get("assetInfo")
});
}
);
}
);
}

View File

@ -431,9 +431,6 @@ const applyModuleDefaults = (
};
/** @type {RuleSetRules} */
const rules = [
{
type: "javascript/auto"
},
{
mimetype: "application/node",
type: "javascript/auto"
@ -1031,6 +1028,7 @@ const getResolveDefaults = ({ cache, context, targetProperties, mode }) => {
byDependency: {
wasm: esmDeps(),
esm: esmDeps(),
loaderImport: esmDeps(),
url: {
preferRelative: true
},

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,44 @@
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 {
/**
* @param {Object} options options
* @param {boolean=} options.enableExecuteModule execute module enabled
*/
constructor(options = {}) {
this._enableExecuteModule = !!options.enableExecuteModule;
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
"LoaderPlugin",
@ -28,6 +54,10 @@ class LoaderPlugin {
LoaderDependency,
normalModuleFactory
);
compilation.dependencyFactories.set(
LoaderImportDependency,
normalModuleFactory
);
}
);
@ -47,7 +77,7 @@ class LoaderPlugin {
name: request
};
const factory = compilation.dependencyFactories.get(
dep.constructor
/** @type {DepConstructor} */ (dep.constructor)
);
if (factory === undefined) {
return callback(
@ -123,6 +153,101 @@ class LoaderPlugin {
}
);
};
if (this._enableExecuteModule) {
/**
* @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,
connectOrigin: false
},
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.executeModule(
referencedModule,
{},
(err, result) => {
if (err) return callback(err);
for (const d of result.fileDependencies) {
loaderContext.addDependency(d);
}
for (const d of result.contextDependencies) {
loaderContext.addContextDependency(d);
}
for (const d of result.missingDependencies) {
loaderContext.addMissingDependency(d);
}
for (const d of result.buildDependencies) {
loaderContext.addBuildDependency(d);
}
for (const [name, { source, info }] of result.assets) {
const { buildInfo } = loaderContext._module;
if (!buildInfo.assets) {
buildInfo.assets = Object.create(null);
buildInfo.assetsInfo = new Map();
}
buildInfo.assets[name] = source;
buildInfo.assetsInfo.set(name, info);
}
callback(null, result.exports);
}
);
}
);
};
/**
* @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.executeModule.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.executeModule.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

@ -6,6 +6,8 @@
"use strict";
const { SyncHook, AsyncSeriesHook } = require("tapable");
const { makeWebpackError } = require("../HookWebpackError");
const WebpackError = require("../WebpackError");
const ArrayQueue = require("./ArrayQueue");
const QUEUED_STATE = 0;
@ -17,7 +19,7 @@ let inHandleResult = 0;
/**
* @template T
* @callback Callback
* @param {Error=} err
* @param {WebpackError=} err
* @param {T=} result
*/
@ -39,6 +41,7 @@ class AsyncQueueEntry {
/** @type {Callback<R>[] | undefined} */
this.callbacks = undefined;
this.result = undefined;
/** @type {WebpackError | undefined} */
this.error = undefined;
}
}
@ -99,15 +102,17 @@ class AsyncQueue {
}
/**
* @param {T} item a item
* @param {T} item an item
* @param {Callback<R>} callback callback function
* @returns {void}
*/
add(item, callback) {
if (this._stopped) return callback(new Error("Queue was stopped"));
if (this._stopped) return callback(new WebpackError("Queue was stopped"));
this.hooks.beforeAdd.callAsync(item, err => {
if (err) {
callback(err);
callback(
makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeAdd`)
);
return;
}
const key = this._getKey(item);
@ -127,7 +132,7 @@ class AsyncQueue {
this.hooks.added.call(item);
this._root._activeTasks++;
process.nextTick(() =>
this._handleResult(newEntry, new Error("Queue was stopped"))
this._handleResult(newEntry, new WebpackError("Queue was stopped"))
);
} else {
this._entries.set(key, newEntry);
@ -144,7 +149,7 @@ class AsyncQueue {
}
/**
* @param {T} item a item
* @param {T} item an item
* @returns {void}
*/
invalidate(item) {
@ -156,6 +161,31 @@ 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 WebpackError(
"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}
*/
@ -167,7 +197,7 @@ class AsyncQueue {
for (const entry of queue) {
this._entries.delete(this._getKey(entry.item));
root._activeTasks++;
this._handleResult(entry, new Error("Queue was stopped"));
this._handleResult(entry, new WebpackError("Queue was stopped"));
}
}
@ -257,7 +287,10 @@ class AsyncQueue {
_startProcessing(entry) {
this.hooks.beforeStart.callAsync(entry.item, err => {
if (err) {
this._handleResult(entry, err);
this._handleResult(
entry,
makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeStart`)
);
return;
}
let inCallback = false;
@ -276,13 +309,15 @@ class AsyncQueue {
/**
* @param {AsyncQueueEntry<T, K, R>} entry the entry
* @param {Error=} err error, if any
* @param {WebpackError=} err error, if any
* @param {R=} result result, if any
* @returns {void}
*/
_handleResult(entry, err, result) {
this.hooks.result.callAsync(entry.item, err, result, hookError => {
const error = hookError || err;
const error = hookError
? makeWebpackError(hookError, `AsyncQueue(${this._name}).hooks.result`)
: err;
const callback = entry.callback;
const callbacks = entry.callbacks;

View File

@ -7,10 +7,11 @@
/**
* @template T
* @template {Error} E
* @param {Iterable<T>} items initial items
* @param {number} concurrency number of items running in parallel
* @param {function(T, function(T): void, function(Error=): void): void} processor worker which pushes more items
* @param {function(Error=): void} callback all items processed
* @param {function(T, function(T): void, function(E=): void): void} processor worker which pushes more items
* @param {function(E=): void} callback all items processed
* @returns {void}
*/
const processAsyncTree = (items, concurrency, processor, callback) => {

View File

@ -675,6 +675,10 @@
"description": "Support WebAssembly as asynchronous EcmaScript Module.",
"type": "boolean"
},
"executeModule": {
"description": "Enable build-time execution of modules from the module graph for plugins and loaders.",
"type": "boolean"
},
"layers": {
"description": "Enable module and chunk layers.",
"type": "boolean"

View File

@ -115,9 +115,6 @@ describe("Defaults", () => {
"mode": "none",
"module": Object {
"defaultRules": Array [
Object {
"type": "javascript/auto",
},
Object {
"mimetype": "application/node",
"type": "javascript/auto",
@ -424,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",
@ -1092,6 +1109,13 @@ describe("Defaults", () => {
@@ ... @@
- "browser",
@@ ... @@
- "aliasFields": Array [
- "browser",
- ],
+ "aliasFields": Array [],
@@ ... @@
- "browser",
@@ ... @@
- "browser",
+ "node",
@@ ... @@
@ -1226,6 +1250,13 @@ describe("Defaults", () => {
@@ ... @@
- "browser",
@@ ... @@
- "aliasFields": Array [
- "browser",
- ],
+ "aliasFields": Array [],
@@ ... @@
- "browser",
@@ ... @@
- "browser",
+ "node",
+ "electron",
@ -1342,6 +1373,13 @@ describe("Defaults", () => {
@@ ... @@
- "browser",
@@ ... @@
- "aliasFields": Array [
- "browser",
- ],
+ "aliasFields": Array [],
@@ ... @@
- "browser",
@@ ... @@
+ "node",
@@ ... @@
+ "electron",

View File

@ -393,6 +393,19 @@ Object {
"multiple": false,
"simpleType": "boolean",
},
"experiments-execute-module": Object {
"configs": Array [
Object {
"description": "Enable build-time execution of modules from the module graph for plugins and loaders.",
"multiple": false,
"path": "experiments.executeModule",
"type": "boolean",
},
],
"description": "Enable build-time execution of modules from the module graph for plugins and loaders.",
"multiple": false,
"simpleType": "boolean",
},
"experiments-layers": Object {
"configs": Array [
Object {

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,15 @@
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: url("/public/assets/file.jpg"); 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,3 @@
import { green } from "./colors.js";
import file from "./file.jpg";
export default `body { background: url("${file}"); color: ${green}; }`;

View File

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

View File

@ -0,0 +1,52 @@
/** @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"
},
{
test: /\.jpg$/,
loader: "file-loader",
options: {
name: "assets/[name].[ext]"
}
}
]
},
experiments: {
executeModule: true
},
plugins: [
compiler =>
compiler.hooks.done.tap("test case", stats => {
expect(stats.compilation.getAsset("assets/file.png")).toHaveProperty(
"info",
expect.objectContaining({ sourceFilename: "file.png" })
);
expect(stats.compilation.getAsset("assets/file.jpg")).toHaveProperty(
"info",
expect.objectContaining({ sourceFilename: "file.jpg" })
);
const { auxiliaryFiles } = stats.compilation.namedChunks.get("main");
expect(auxiliaryFiles).toContain("assets/file.png");
expect(auxiliaryFiles).toContain("assets/file.png?1");
expect(auxiliaryFiles).toContain("assets/file.jpg");
})
]
};

View File

@ -0,0 +1,4 @@
export const value = 42;
export * from "./imported.js";
export { default as nested } from "./b.generate-json.js";
export const random = Math.random();

View File

@ -0,0 +1,2 @@
export const value = 42;
export * from "./imported.js";

View File

@ -0,0 +1,2 @@
export const a = "a";
export const b = "b";

View File

@ -0,0 +1,18 @@
import a from "./a.generate-json.js";
import { value as unrelated } from "./unrelated";
it("should have to correct values and validate on change", () => {
const step = +WATCH_STEP;
expect(a.value).toBe(42);
expect(a.a).toBe("a");
expect(a.nested.value).toBe(step < 3 ? 42 : 24);
expect(a.nested.a).toBe(step < 3 ? "a" : undefined);
expect(a.b).toBe(step < 1 ? "b" : undefined);
expect(a.nested.b).toBe(step < 1 ? "b" : undefined);
expect(a.c).toBe(step < 1 ? undefined : "c");
expect(a.nested.c).toBe(step < 1 || step >= 3 ? undefined : "c");
if (step !== 0) {
expect(STATE.random === a.random).toBe(step === 2);
}
STATE.random = a.random;
});

View File

@ -0,0 +1,6 @@
exports.pitch = async function (remaining) {
const result = await this.importModule(
`${this.resourcePath}.webpack[javascript/auto]!=!${remaining}`
);
return JSON.stringify(result, null, 2);
};

View File

@ -0,0 +1 @@
export const value = 42;

View File

@ -0,0 +1,2 @@
export const a = "a";
export const c = "c";

View File

@ -0,0 +1 @@
export const value = 24;

View File

@ -0,0 +1 @@
export const value = 24;

View File

@ -0,0 +1,15 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
module: {
rules: [
{
test: /\.generate-json\.js$/,
use: "./loader",
type: "json"
}
]
},
experiments: {
executeModule: true
}
};

117
types.d.ts vendored
View File

@ -343,8 +343,13 @@ declare abstract class AsyncQueue<T, K, R> {
started: SyncHook<[T]>;
result: SyncHook<[T, Error, R]>;
};
add(item: T, callback: CallbackFunction<R>): void;
add(item: T, callback: CallbackAsyncQueue<R>): void;
invalidate(item: T): void;
/**
* Waits for an already started item
*/
waitFor(item: T, callback: CallbackAsyncQueue<R>): void;
stop(): void;
increaseParallelism(): void;
decreaseParallelism(): void;
@ -657,6 +662,9 @@ declare interface CallExpressionInfo {
name: string;
getMembers: () => string[];
}
declare interface CallbackAsyncQueue<T> {
(err?: WebpackError, result?: T): any;
}
declare interface CallbackCache<T> {
(err?: WebpackError, result?: T): void;
}
@ -1242,6 +1250,7 @@ declare class Compilation {
dependencyReferencedExports: SyncWaterfallHook<
[(string[] | ReferencedExport)[], Dependency, RuntimeSpec]
>;
executeModule: SyncHook<[ExecuteModuleArgument, ExecuteModuleContext]>;
finishModules: AsyncSeriesHook<[Iterable<Module>]>;
finishRebuildingModule: AsyncSeriesHook<[Module]>;
unseal: SyncHook<[]>;
@ -1263,14 +1272,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 +1395,7 @@ declare class Compilation {
runtimeTemplate: RuntimeTemplate;
moduleTemplates: { javascript: ModuleTemplate };
moduleGraph: ModuleGraph;
chunkGraph?: ChunkGraph;
chunkGraph: ChunkGraph;
codeGenerationResults: CodeGenerationResults;
processDependenciesQueue: AsyncQueue<Module, Module, Module>;
addModuleQueue: AsyncQueue<Module, string, Module>;
@ -1415,6 +1434,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 +1532,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 +1646,11 @@ declare class Compilation {
| WebpackPluginInstance
)[]
): Compiler;
executeModule(
module: Module,
options: ExecuteModuleOptions,
callback: (err?: WebpackError, result?: ExecuteModuleResult) => void
): void;
checkConstraints(): void;
/**
@ -3085,6 +3135,28 @@ declare class EvalSourceMapDevToolPlugin {
*/
apply(compiler: Compiler): void;
}
declare interface ExecuteModuleArgument {
module: Module;
moduleObject: object;
codeGenerationResult: CodeGenerationResult;
}
declare interface ExecuteModuleContext {
assets: Map<string, { source: Source; info: AssetInfo }>;
chunk: Chunk;
chunkGraph: ChunkGraph;
__webpack_require__: Function;
}
declare interface ExecuteModuleOptions {
entryOptions?: EntryOptions;
}
declare interface ExecuteModuleResult {
exports: any;
assets: Map<string, { source: Source; info: AssetInfo }>;
fileDependencies: LazySet<string>;
contextDependencies: LazySet<string>;
missingDependencies: LazySet<string>;
buildDependencies: LazySet<string>;
}
/**
* Enables/Disables experiments (experimental features with relax SemVer compatibility).
@ -3100,6 +3172,11 @@ declare interface Experiments {
*/
asyncWebAssembly?: boolean;
/**
* Enable build-time execution of modules from the module graph for plugins and loaders.
*/
executeModule?: boolean;
/**
* Enable module and chunk layers.
*/
@ -4048,6 +4125,11 @@ declare interface HandleModuleCreationOptions {
* recurse into dependencies of the created module
*/
recursive?: boolean;
/**
* connect the resolved module with the origin module
*/
connectOrigin?: boolean;
}
declare class Hash {
constructor();
@ -5348,6 +5430,7 @@ declare interface KnownStatsModule {
cacheable?: boolean;
built?: boolean;
codeGenerated?: boolean;
buildTimeExecuted?: boolean;
cached?: boolean;
optional?: boolean;
orphan?: boolean;
@ -5484,6 +5567,7 @@ declare class LibManifestPlugin {
}
declare interface LibraryContext<T> {
compilation: Compilation;
chunkGraph: ChunkGraph;
options: T;
}
@ -9294,6 +9378,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;