Skip to content

Commit

Permalink
Merge pull request #13134 from webpack/bugfix/hmr-in-execute-module
Browse files Browse the repository at this point in the history
importModule & HMR
  • Loading branch information
sokra committed Apr 14, 2021
2 parents 31353e3 + f46e816 commit 52178a5
Show file tree
Hide file tree
Showing 25 changed files with 332 additions and 137 deletions.
258 changes: 159 additions & 99 deletions lib/Compilation.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const {
SyncBailHook,
SyncWaterfallHook,
AsyncSeriesHook,
AsyncSeriesBailHook
AsyncSeriesBailHook,
AsyncParallelHook
} = require("tapable");
const util = require("util");
const { CachedSource } = require("webpack-sources");
Expand Down Expand Up @@ -183,6 +184,7 @@ const { isSourceEqual } = require("./util/source");
/**
* @typedef {Object} ExecuteModuleResult
* @property {any} exports
* @property {boolean} cacheable
* @property {Map<string, { source: Source, info: AssetInfo }>} assets
* @property {LazySet<string>} fileDependencies
* @property {LazySet<string>} contextDependencies
Expand All @@ -193,7 +195,8 @@ const { isSourceEqual } = require("./util/source");
/**
* @typedef {Object} ExecuteModuleArgument
* @property {Module} module
* @property {object} moduleObject
* @property {{ id: string, exports: any, loaded: boolean }=} moduleObject
* @property {any} preparedInfo
* @property {CodeGenerationResult} codeGenerationResult
*/

Expand All @@ -202,7 +205,7 @@ const { isSourceEqual } = require("./util/source");
* @property {Map<string, { source: Source, info: AssetInfo }>} assets
* @property {Chunk} chunk
* @property {ChunkGraph} chunkGraph
* @property {Function} __webpack_require__
* @property {function(string): any=} __webpack_require__
*/

/**
Expand Down Expand Up @@ -574,6 +577,8 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si

/** @type {SyncHook<[ExecuteModuleArgument, ExecuteModuleContext]>} */
executeModule: new SyncHook(["options", "context"]),
/** @type {AsyncParallelHook<[ExecuteModuleArgument, ExecuteModuleContext]>} */
prepareModuleExecution: new AsyncParallelHook(["options", "context"]),

/** @type {AsyncSeriesHook<[Iterable<Module>]>} */
finishModules: new AsyncSeriesHook(["modules"]),
Expand Down Expand Up @@ -2833,7 +2838,7 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o
}

// attach runtime module
module.attach(this, chunk);
module.attach(this, chunk, chunkGraph);

// Setup internals
const exportsInfo = this.moduleGraph.getExportsInfo(module);
Expand Down Expand Up @@ -4030,6 +4035,7 @@ This prevents using hashes of each other and should be avoided.`);

const entrypoint = new Entrypoint({
runtime,
chunkLoading: "none",
...options.entryOptions
});
chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
Expand All @@ -4039,13 +4045,9 @@ This prevents using hashes of each other and should be avoided.`);

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);
}
Expand Down Expand Up @@ -4125,6 +4127,7 @@ This prevents using hashes of each other and should be avoided.`);

// Hash runtime modules
for (const module of runtimeModules) {
modules.add(module);
this._createModuleHash(
module,
chunkGraph,
Expand All @@ -4141,7 +4144,11 @@ This prevents using hashes of each other and should be avoided.`);
if (err) return callback(err);
reportErrors();

let exports;
/** @type {Map<Module, ExecuteModuleArgument>} */
const moduleArgumentsMap = new Map();
/** @type {Map<string, ExecuteModuleArgument>} */
const moduleArgumentsById = new Map();

/** @type {ExecuteModuleResult["fileDependencies"]} */
const fileDependencies = new LazySet();
/** @type {ExecuteModuleResult["contextDependencies"]} */
Expand All @@ -4150,43 +4157,47 @@ This prevents using hashes of each other and should be avoided.`);
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

let cacheable = true;

/** @type {ExecuteModuleContext} */
const context = {
assets,
__webpack_require__: undefined,
chunk,
chunkGraph
};

// Prepare execution
asyncLib.eachLimit(
modules,
10,
(module, callback) => {
const codeGenerationResult = codeGenerationResults.get(
module,
runtime
);
/** @type {ExecuteModuleArgument} */
const moduleArgument = {
module,
codeGenerationResult,
preparedInfo: undefined,
moduleObject: undefined
};
this.buildTimeExecutedModules.add(module);
moduleArgumentsMap.set(module, moduleArgument);
moduleArgumentsById.set(module.identifier(), moduleArgument);
module.addCacheDependencies(
fileDependencies,
contextDependencies,
missingDependencies,
buildDependencies
);
if (module.buildInfo.cacheable === false) {
cacheable = false;
}
if (module.buildInfo && module.buildInfo.assets) {
const { assets: moduleAssets, assetsInfo } = module.buildInfo;
for (const assetName of Object.keys(moduleAssets)) {
Expand All @@ -4196,73 +4207,122 @@ This prevents using hashes of each other and should be avoided.`);
});
}
}
this.hooks.prepareModuleExecution.callAsync(
moduleArgument,
context,
callback
);
},
err => {
if (err) return callback(err);

let exports;
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;
const {
strictModuleErrorHandling,
strictModuleExceptionHandling
} = this.outputOptions;
const __webpack_require__ = id => {
const cached = moduleCache[id];
if (cached !== undefined) {
if (cached.error) throw cached.error;
return cached.exports;
}
const moduleArgument = moduleArgumentsById.get(id);
return __webpack_require_module__(moduleArgument, id);
};
const interceptModuleExecution = (__webpack_require__[
RuntimeGlobals.interceptModuleExecution.replace(
"__webpack_require__.",
""
)
] = []);
const moduleCache = (__webpack_require__[
RuntimeGlobals.moduleCache.replace(
"__webpack_require__.",
""
)
] = {});

context.__webpack_require__ = __webpack_require__;

/**
* @param {ExecuteModuleArgument} moduleArgument the module argument
* @param {string=} id id
* @returns {any} exports
*/
const __webpack_require_module__ = (moduleArgument, id) => {
var execOptions = {
id,
module: {
id,
exports: {},
loaded: false,
error: undefined
},
require: __webpack_require__
};
interceptModuleExecution.forEach(handler =>
handler(execOptions)
);
const module = moduleArgument.module;
this.buildTimeExecutedModules.add(module);
const moduleObject = execOptions.module;
moduleArgument.moduleObject = moduleObject;
try {
if (id) moduleCache[id] = moduleObject;

tryRunOrWebpackError(
() =>
this.hooks.executeModule.call(
moduleArgument,
context
),
"Compilation.hooks.executeModule"
);
moduleObject.loaded = true;
return moduleObject.exports;
} catch (e) {
if (strictModuleExceptionHandling) {
if (id) delete moduleCache[id];
} else if (strictModuleErrorHandling) {
moduleObject.error = e;
}
if (!e.module) e.module = module;
throw e;
}
};

for (const runtimeModule of chunkGraph.getChunkRuntimeModulesInOrder(
chunk
)) {
__webpack_require_module__(
moduleArgumentsMap.get(runtimeModule)
);
}
if (!e.module) e.module = module;
throw e;
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);
}
};

/** @type {ExecuteModuleContext} */
const context = {
assets,
__webpack_require__,
chunk,
chunkGraph
};

for (const runtimeModule of chunkGraph.getChunkRuntimeModulesInOrder(
chunk
)) {
__webpack_require_module__(
runtimeModule,
runtimeModule.identifier()
);
callback(null, {
exports,
assets,
cacheable,
fileDependencies,
contextDependencies,
missingDependencies,
buildDependencies
});
}
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
});
);
});
});
}
Expand Down
6 changes: 5 additions & 1 deletion lib/RuntimeModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class RuntimeModule extends Module {
this.compilation = undefined;
/** @type {Chunk} */
this.chunk = undefined;
/** @type {ChunkGraph} */
this.chunkGraph = undefined;
this.fullHash = false;
/** @type {string} */
this._cachedGeneratedCode = undefined;
Expand All @@ -49,11 +51,13 @@ class RuntimeModule extends Module {
/**
* @param {Compilation} compilation the compilation
* @param {Chunk} chunk the chunk
* @param {ChunkGraph} chunkGraph the chunk graph
* @returns {void}
*/
attach(compilation, chunk) {
attach(compilation, chunk, chunkGraph = compilation.chunkGraph) {
this.compilation = compilation;
this.chunk = chunk;
this.chunkGraph = chunkGraph;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/asset/AssetModulesPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class AssetModulesPlugin {
return result;
});

compilation.hooks.executeModule.tap(
compilation.hooks.prepareModuleExecution.tap(
"AssetModulesPlugin",
(options, context) => {
const { codeGenerationResult } = options;
Expand Down

0 comments on commit 52178a5

Please sign in to comment.