Skip to content

Commit

Permalink
add HMR test case and fix problems with it
Browse files Browse the repository at this point in the history
  • Loading branch information
sokra committed Apr 14, 2021
1 parent 24c5902 commit f46e816
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 115 deletions.
270 changes: 158 additions & 112 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 @@ -4025,6 +4030,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 @@ -4034,13 +4040,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 @@ -4120,6 +4122,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 @@ -4136,7 +4139,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 @@ -4145,59 +4152,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 __webpack_require__ = id => {
const cached = moduleCache[id];
if (cached !== undefined) {
if (cached.error) throw cached.error;
return cached.exports;
}
const module = modulesById.get(id);
return __webpack_require_module__(module, id);
};
const interceptModuleExecution = (__webpack_require__[
RuntimeGlobals.interceptModuleExecution.replace(
"__webpack_require__.",
""
)
] = []);
const moduleCache = (__webpack_require__[
RuntimeGlobals.moduleCache.replace("__webpack_require__.", "")
] = {});

/**
*
* @param {Module} module the module
* @param {string=} id id
* @returns {any} exports
*/
const __webpack_require_module__ = (module, id) => {
var execOptions = {
id,
module: {
id,
exports: {},
loaded: false,
error: undefined
},
require: __webpack_require__
};
interceptModuleExecution.forEach(handler =>
handler(execOptions)

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
);
this.buildTimeExecutedModules.add(module);
/** @type {ExecuteModuleArgument} */
const moduleArgument = {
module,
codeGenerationResult,
preparedInfo: undefined,
moduleObject: undefined
};
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 @@ -4207,71 +4202,122 @@ This prevents using hashes of each other and should be avoided.`);
});
}
}
const moduleObject = execOptions.module;
this.hooks.prepareModuleExecution.callAsync(
moduleArgument,
context,
callback
);
},
err => {
if (err) return callback(err);

let exports;
try {
if (id) moduleCache[id] = 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) {
if (id) delete moduleCache[id];
} 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);
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
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
2 changes: 2 additions & 0 deletions lib/dependencies/LoaderPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ class LoaderPlugin {
for (const d of result.buildDependencies) {
loaderContext.addBuildDependency(d);
}
if (result.cacheable === false)
loaderContext.cacheable(false);
for (const [name, { source, info }] of result.assets) {
const { buildInfo } = loaderContext._module;
if (!buildInfo.assets) {
Expand Down
5 changes: 5 additions & 0 deletions test/hotCases/loader-import-module/css/colors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const color = "#f00";
---
export const color = "#0f0";
---
export const color = "#0f0";
Binary file added test/hotCases/loader-import-module/css/file.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/hotCases/loader-import-module/css/file.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit f46e816

Please sign in to comment.