Add semantic token highlighting for 'that' variable and comment file references in VS Code extension Add Phone_Text_Input and Currency_Input components with formatting utilities Implement client widgets, form standardization, and soft delete functionality Add modal scroll lock and update documentation Implement comprehensive modal system with form integration and validation Fix modal component instantiation using jQuery plugin API Implement modal system with responsive sizing, queuing, and validation support Implement form submission with validation, error handling, and loading states Implement country/state selectors with dynamic data loading and Bootstrap styling Revert Rsx::Route() highlighting in Blade/PHP files Target specific PHP scopes for Rsx::Route() highlighting in Blade Expand injection selector for Rsx::Route() highlighting Add custom syntax highlighting for Rsx::Route() and Rsx.Route() calls Update jqhtml packages to v2.2.165 Add bundle path validation for common mistakes (development mode only) Create Ajax_Select_Input widget and Rsx_Reference_Data controller Create Country_Select_Input widget with default country support Initialize Tom Select on Select_Input widgets Add Tom Select bundle for enhanced select dropdowns Implement ISO 3166 geographic data system for country/region selection Implement widget-based form system with disabled state support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
895 lines
30 KiB
JavaScript
Executable File
895 lines
30 KiB
JavaScript
Executable File
/*
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
Author Tobias Koppers @sokra
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
const { SyncBailHook } = require("tapable");
|
|
const { RawSource } = require("webpack-sources");
|
|
const ChunkGraph = require("./ChunkGraph");
|
|
const Compilation = require("./Compilation");
|
|
const HotUpdateChunk = require("./HotUpdateChunk");
|
|
const {
|
|
JAVASCRIPT_MODULE_TYPE_AUTO,
|
|
JAVASCRIPT_MODULE_TYPE_DYNAMIC,
|
|
JAVASCRIPT_MODULE_TYPE_ESM,
|
|
WEBPACK_MODULE_TYPE_RUNTIME
|
|
} = require("./ModuleTypeConstants");
|
|
const NormalModule = require("./NormalModule");
|
|
const RuntimeGlobals = require("./RuntimeGlobals");
|
|
const WebpackError = require("./WebpackError");
|
|
const ConstDependency = require("./dependencies/ConstDependency");
|
|
const ImportMetaHotAcceptDependency = require("./dependencies/ImportMetaHotAcceptDependency");
|
|
const ImportMetaHotDeclineDependency = require("./dependencies/ImportMetaHotDeclineDependency");
|
|
const ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency");
|
|
const ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency");
|
|
const HotModuleReplacementRuntimeModule = require("./hmr/HotModuleReplacementRuntimeModule");
|
|
const JavascriptParser = require("./javascript/JavascriptParser");
|
|
const {
|
|
evaluateToIdentifier
|
|
} = require("./javascript/JavascriptParserHelpers");
|
|
const { find, isSubset } = require("./util/SetHelpers");
|
|
const TupleSet = require("./util/TupleSet");
|
|
const { compareModulesById } = require("./util/comparators");
|
|
const {
|
|
forEachRuntime,
|
|
getRuntimeKey,
|
|
intersectRuntime,
|
|
keyToRuntime,
|
|
mergeRuntimeOwned,
|
|
subtractRuntime
|
|
} = require("./util/runtime");
|
|
|
|
/** @typedef {import("estree").CallExpression} CallExpression */
|
|
/** @typedef {import("estree").Expression} Expression */
|
|
/** @typedef {import("estree").SpreadElement} SpreadElement */
|
|
/** @typedef {import("./Chunk")} Chunk */
|
|
/** @typedef {import("./Chunk").ChunkId} ChunkId */
|
|
/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */
|
|
/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
|
|
/** @typedef {import("./Compilation").Records} Records */
|
|
/** @typedef {import("./Compiler")} Compiler */
|
|
/** @typedef {import("./CodeGenerationResults")} CodeGenerationResults */
|
|
/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
|
|
/** @typedef {import("./Module")} Module */
|
|
/** @typedef {import("./Module").BuildInfo} BuildInfo */
|
|
/** @typedef {import("./RuntimeModule")} RuntimeModule */
|
|
/** @typedef {import("./javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
|
|
/** @typedef {import("./javascript/JavascriptParserHelpers").Range} Range */
|
|
/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */
|
|
|
|
/** @typedef {string[]} Requests */
|
|
|
|
/**
|
|
* @typedef {object} HMRJavascriptParserHooks
|
|
* @property {SyncBailHook<[Expression | SpreadElement, Requests], void>} hotAcceptCallback
|
|
* @property {SyncBailHook<[CallExpression, Requests], void>} hotAcceptWithoutCallback
|
|
*/
|
|
|
|
/** @typedef {number} HotIndex */
|
|
/** @typedef {Record<string, string>} FullHashChunkModuleHashes */
|
|
/** @typedef {Record<string, string>} ChunkModuleHashes */
|
|
/** @typedef {Record<ChunkId, string>} ChunkHashes */
|
|
/** @typedef {Record<ChunkId, string>} ChunkRuntime */
|
|
/** @typedef {Record<ChunkId, ModuleId[]>} ChunkModuleIds */
|
|
|
|
/** @typedef {{ updatedChunkIds: Set<ChunkId>, removedChunkIds: Set<ChunkId>, removedModules: Set<Module>, filename: string, assetInfo: AssetInfo }} HotUpdateMainContentByRuntimeItem */
|
|
/** @typedef {Map<string, HotUpdateMainContentByRuntimeItem>} HotUpdateMainContentByRuntime */
|
|
|
|
/** @type {WeakMap<JavascriptParser, HMRJavascriptParserHooks>} */
|
|
const parserHooksMap = new WeakMap();
|
|
|
|
const PLUGIN_NAME = "HotModuleReplacementPlugin";
|
|
|
|
class HotModuleReplacementPlugin {
|
|
/**
|
|
* @param {JavascriptParser} parser the parser
|
|
* @returns {HMRJavascriptParserHooks} the attached hooks
|
|
*/
|
|
static getParserHooks(parser) {
|
|
if (!(parser instanceof JavascriptParser)) {
|
|
throw new TypeError(
|
|
"The 'parser' argument must be an instance of JavascriptParser"
|
|
);
|
|
}
|
|
let hooks = parserHooksMap.get(parser);
|
|
if (hooks === undefined) {
|
|
hooks = {
|
|
hotAcceptCallback: new SyncBailHook(["expression", "requests"]),
|
|
hotAcceptWithoutCallback: new SyncBailHook(["expression", "requests"])
|
|
};
|
|
parserHooksMap.set(parser, hooks);
|
|
}
|
|
return hooks;
|
|
}
|
|
|
|
/**
|
|
* Apply the plugin
|
|
* @param {Compiler} compiler the compiler instance
|
|
* @returns {void}
|
|
*/
|
|
apply(compiler) {
|
|
const { _backCompat: backCompat } = compiler;
|
|
if (compiler.options.output.strictModuleErrorHandling === undefined) {
|
|
compiler.options.output.strictModuleErrorHandling = true;
|
|
}
|
|
const runtimeRequirements = [RuntimeGlobals.module];
|
|
|
|
/**
|
|
* @param {JavascriptParser} parser the parser
|
|
* @param {typeof ModuleHotAcceptDependency} ParamDependency dependency
|
|
* @returns {(expr: CallExpression) => boolean | undefined} callback
|
|
*/
|
|
const createAcceptHandler = (parser, ParamDependency) => {
|
|
const { hotAcceptCallback, hotAcceptWithoutCallback } =
|
|
HotModuleReplacementPlugin.getParserHooks(parser);
|
|
|
|
return (expr) => {
|
|
const module = parser.state.module;
|
|
const dep = new ConstDependency(
|
|
`${module.moduleArgument}.hot.accept`,
|
|
/** @type {Range} */ (expr.callee.range),
|
|
runtimeRequirements
|
|
);
|
|
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|
module.addPresentationalDependency(dep);
|
|
/** @type {BuildInfo} */
|
|
(module.buildInfo).moduleConcatenationBailout =
|
|
"Hot Module Replacement";
|
|
|
|
if (expr.arguments.length >= 1) {
|
|
const arg = parser.evaluateExpression(expr.arguments[0]);
|
|
/** @type {BasicEvaluatedExpression[]} */
|
|
let params = [];
|
|
if (arg.isString()) {
|
|
params = [arg];
|
|
} else if (arg.isArray()) {
|
|
params =
|
|
/** @type {BasicEvaluatedExpression[]} */
|
|
(arg.items).filter((param) => param.isString());
|
|
}
|
|
/** @type {Requests} */
|
|
const requests = [];
|
|
if (params.length > 0) {
|
|
for (const [idx, param] of params.entries()) {
|
|
const request = /** @type {string} */ (param.string);
|
|
const dep = new ParamDependency(
|
|
request,
|
|
/** @type {Range} */ (param.range)
|
|
);
|
|
dep.optional = true;
|
|
dep.loc = Object.create(
|
|
/** @type {DependencyLocation} */ (expr.loc)
|
|
);
|
|
dep.loc.index = idx;
|
|
module.addDependency(dep);
|
|
requests.push(request);
|
|
}
|
|
if (expr.arguments.length > 1) {
|
|
hotAcceptCallback.call(expr.arguments[1], requests);
|
|
for (let i = 1; i < expr.arguments.length; i++) {
|
|
parser.walkExpression(expr.arguments[i]);
|
|
}
|
|
return true;
|
|
}
|
|
hotAcceptWithoutCallback.call(expr, requests);
|
|
return true;
|
|
}
|
|
}
|
|
parser.walkExpressions(expr.arguments);
|
|
return true;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @param {JavascriptParser} parser the parser
|
|
* @param {typeof ModuleHotDeclineDependency} ParamDependency dependency
|
|
* @returns {(expr: CallExpression) => boolean | undefined} callback
|
|
*/
|
|
const createDeclineHandler = (parser, ParamDependency) => (expr) => {
|
|
const module = parser.state.module;
|
|
const dep = new ConstDependency(
|
|
`${module.moduleArgument}.hot.decline`,
|
|
/** @type {Range} */ (expr.callee.range),
|
|
runtimeRequirements
|
|
);
|
|
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|
module.addPresentationalDependency(dep);
|
|
/** @type {BuildInfo} */
|
|
(module.buildInfo).moduleConcatenationBailout = "Hot Module Replacement";
|
|
if (expr.arguments.length === 1) {
|
|
const arg = parser.evaluateExpression(expr.arguments[0]);
|
|
/** @type {BasicEvaluatedExpression[]} */
|
|
let params = [];
|
|
if (arg.isString()) {
|
|
params = [arg];
|
|
} else if (arg.isArray()) {
|
|
params =
|
|
/** @type {BasicEvaluatedExpression[]} */
|
|
(arg.items).filter((param) => param.isString());
|
|
}
|
|
for (const [idx, param] of params.entries()) {
|
|
const dep = new ParamDependency(
|
|
/** @type {string} */ (param.string),
|
|
/** @type {Range} */ (param.range)
|
|
);
|
|
dep.optional = true;
|
|
dep.loc = Object.create(/** @type {DependencyLocation} */ (expr.loc));
|
|
dep.loc.index = idx;
|
|
module.addDependency(dep);
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* @param {JavascriptParser} parser the parser
|
|
* @returns {(expr: Expression) => boolean | undefined} callback
|
|
*/
|
|
const createHMRExpressionHandler = (parser) => (expr) => {
|
|
const module = parser.state.module;
|
|
const dep = new ConstDependency(
|
|
`${module.moduleArgument}.hot`,
|
|
/** @type {Range} */ (expr.range),
|
|
runtimeRequirements
|
|
);
|
|
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|
module.addPresentationalDependency(dep);
|
|
/** @type {BuildInfo} */
|
|
(module.buildInfo).moduleConcatenationBailout = "Hot Module Replacement";
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* @param {JavascriptParser} parser the parser
|
|
* @returns {void}
|
|
*/
|
|
const applyModuleHot = (parser) => {
|
|
parser.hooks.evaluateIdentifier.for("module.hot").tap(
|
|
{
|
|
name: PLUGIN_NAME,
|
|
before: "NodeStuffPlugin"
|
|
},
|
|
(expr) =>
|
|
evaluateToIdentifier(
|
|
"module.hot",
|
|
"module",
|
|
() => ["hot"],
|
|
true
|
|
)(expr)
|
|
);
|
|
parser.hooks.call
|
|
.for("module.hot.accept")
|
|
.tap(
|
|
PLUGIN_NAME,
|
|
createAcceptHandler(parser, ModuleHotAcceptDependency)
|
|
);
|
|
parser.hooks.call
|
|
.for("module.hot.decline")
|
|
.tap(
|
|
PLUGIN_NAME,
|
|
createDeclineHandler(parser, ModuleHotDeclineDependency)
|
|
);
|
|
parser.hooks.expression
|
|
.for("module.hot")
|
|
.tap(PLUGIN_NAME, createHMRExpressionHandler(parser));
|
|
};
|
|
|
|
/**
|
|
* @param {JavascriptParser} parser the parser
|
|
* @returns {void}
|
|
*/
|
|
const applyImportMetaHot = (parser) => {
|
|
parser.hooks.evaluateIdentifier
|
|
.for("import.meta.webpackHot")
|
|
.tap(PLUGIN_NAME, (expr) =>
|
|
evaluateToIdentifier(
|
|
"import.meta.webpackHot",
|
|
"import.meta",
|
|
() => ["webpackHot"],
|
|
true
|
|
)(expr)
|
|
);
|
|
parser.hooks.call
|
|
.for("import.meta.webpackHot.accept")
|
|
.tap(
|
|
PLUGIN_NAME,
|
|
createAcceptHandler(parser, ImportMetaHotAcceptDependency)
|
|
);
|
|
parser.hooks.call
|
|
.for("import.meta.webpackHot.decline")
|
|
.tap(
|
|
PLUGIN_NAME,
|
|
createDeclineHandler(parser, ImportMetaHotDeclineDependency)
|
|
);
|
|
parser.hooks.expression
|
|
.for("import.meta.webpackHot")
|
|
.tap(PLUGIN_NAME, createHMRExpressionHandler(parser));
|
|
};
|
|
|
|
compiler.hooks.compilation.tap(
|
|
PLUGIN_NAME,
|
|
(compilation, { normalModuleFactory }) => {
|
|
// This applies the HMR plugin only to the targeted compiler
|
|
// It should not affect child compilations
|
|
if (compilation.compiler !== compiler) return;
|
|
|
|
// #region module.hot.* API
|
|
compilation.dependencyFactories.set(
|
|
ModuleHotAcceptDependency,
|
|
normalModuleFactory
|
|
);
|
|
compilation.dependencyTemplates.set(
|
|
ModuleHotAcceptDependency,
|
|
new ModuleHotAcceptDependency.Template()
|
|
);
|
|
compilation.dependencyFactories.set(
|
|
ModuleHotDeclineDependency,
|
|
normalModuleFactory
|
|
);
|
|
compilation.dependencyTemplates.set(
|
|
ModuleHotDeclineDependency,
|
|
new ModuleHotDeclineDependency.Template()
|
|
);
|
|
// #endregion
|
|
|
|
// #region import.meta.webpackHot.* API
|
|
compilation.dependencyFactories.set(
|
|
ImportMetaHotAcceptDependency,
|
|
normalModuleFactory
|
|
);
|
|
compilation.dependencyTemplates.set(
|
|
ImportMetaHotAcceptDependency,
|
|
new ImportMetaHotAcceptDependency.Template()
|
|
);
|
|
compilation.dependencyFactories.set(
|
|
ImportMetaHotDeclineDependency,
|
|
normalModuleFactory
|
|
);
|
|
compilation.dependencyTemplates.set(
|
|
ImportMetaHotDeclineDependency,
|
|
new ImportMetaHotDeclineDependency.Template()
|
|
);
|
|
// #endregion
|
|
|
|
/** @type {HotIndex} */
|
|
let hotIndex = 0;
|
|
/** @type {FullHashChunkModuleHashes} */
|
|
const fullHashChunkModuleHashes = {};
|
|
/** @type {ChunkModuleHashes} */
|
|
const chunkModuleHashes = {};
|
|
|
|
compilation.hooks.record.tap(PLUGIN_NAME, (compilation, records) => {
|
|
if (records.hash === compilation.hash) return;
|
|
const chunkGraph = compilation.chunkGraph;
|
|
records.hash = compilation.hash;
|
|
records.hotIndex = hotIndex;
|
|
records.fullHashChunkModuleHashes = fullHashChunkModuleHashes;
|
|
records.chunkModuleHashes = chunkModuleHashes;
|
|
records.chunkHashes = {};
|
|
records.chunkRuntime = {};
|
|
for (const chunk of compilation.chunks) {
|
|
const chunkId = /** @type {ChunkId} */ (chunk.id);
|
|
records.chunkHashes[chunkId] = /** @type {string} */ (chunk.hash);
|
|
records.chunkRuntime[chunkId] = getRuntimeKey(chunk.runtime);
|
|
}
|
|
records.chunkModuleIds = {};
|
|
for (const chunk of compilation.chunks) {
|
|
const chunkId = /** @type {ChunkId} */ (chunk.id);
|
|
|
|
records.chunkModuleIds[chunkId] = Array.from(
|
|
chunkGraph.getOrderedChunkModulesIterable(
|
|
chunk,
|
|
compareModulesById(chunkGraph)
|
|
),
|
|
(m) => /** @type {ModuleId} */ (chunkGraph.getModuleId(m))
|
|
);
|
|
}
|
|
});
|
|
/** @type {TupleSet<Module, Chunk>} */
|
|
const updatedModules = new TupleSet();
|
|
/** @type {TupleSet<Module, Chunk>} */
|
|
const fullHashModules = new TupleSet();
|
|
/** @type {TupleSet<Module, RuntimeSpec>} */
|
|
const nonCodeGeneratedModules = new TupleSet();
|
|
compilation.hooks.fullHash.tap(PLUGIN_NAME, (hash) => {
|
|
const chunkGraph = compilation.chunkGraph;
|
|
const records = /** @type {Records} */ (compilation.records);
|
|
for (const chunk of compilation.chunks) {
|
|
/**
|
|
* @param {Module} module module
|
|
* @returns {string} module hash
|
|
*/
|
|
const getModuleHash = (module) => {
|
|
const codeGenerationResults =
|
|
/** @type {CodeGenerationResults} */
|
|
(compilation.codeGenerationResults);
|
|
if (codeGenerationResults.has(module, chunk.runtime)) {
|
|
return codeGenerationResults.getHash(module, chunk.runtime);
|
|
}
|
|
nonCodeGeneratedModules.add(module, chunk.runtime);
|
|
return chunkGraph.getModuleHash(module, chunk.runtime);
|
|
};
|
|
const fullHashModulesInThisChunk =
|
|
chunkGraph.getChunkFullHashModulesSet(chunk);
|
|
if (fullHashModulesInThisChunk !== undefined) {
|
|
for (const module of fullHashModulesInThisChunk) {
|
|
fullHashModules.add(module, chunk);
|
|
}
|
|
}
|
|
const modules = chunkGraph.getChunkModulesIterable(chunk);
|
|
if (modules !== undefined) {
|
|
if (records.chunkModuleHashes) {
|
|
if (fullHashModulesInThisChunk !== undefined) {
|
|
for (const module of modules) {
|
|
const key = `${chunk.id}|${module.identifier()}`;
|
|
const hash = getModuleHash(module);
|
|
if (
|
|
fullHashModulesInThisChunk.has(
|
|
/** @type {RuntimeModule} */
|
|
(module)
|
|
)
|
|
) {
|
|
if (
|
|
/** @type {FullHashChunkModuleHashes} */
|
|
(records.fullHashChunkModuleHashes)[key] !== hash
|
|
) {
|
|
updatedModules.add(module, chunk);
|
|
}
|
|
fullHashChunkModuleHashes[key] = hash;
|
|
} else {
|
|
if (records.chunkModuleHashes[key] !== hash) {
|
|
updatedModules.add(module, chunk);
|
|
}
|
|
chunkModuleHashes[key] = hash;
|
|
}
|
|
}
|
|
} else {
|
|
for (const module of modules) {
|
|
const key = `${chunk.id}|${module.identifier()}`;
|
|
const hash = getModuleHash(module);
|
|
if (records.chunkModuleHashes[key] !== hash) {
|
|
updatedModules.add(module, chunk);
|
|
}
|
|
chunkModuleHashes[key] = hash;
|
|
}
|
|
}
|
|
} else if (fullHashModulesInThisChunk !== undefined) {
|
|
for (const module of modules) {
|
|
const key = `${chunk.id}|${module.identifier()}`;
|
|
const hash = getModuleHash(module);
|
|
if (
|
|
fullHashModulesInThisChunk.has(
|
|
/** @type {RuntimeModule} */ (module)
|
|
)
|
|
) {
|
|
fullHashChunkModuleHashes[key] = hash;
|
|
} else {
|
|
chunkModuleHashes[key] = hash;
|
|
}
|
|
}
|
|
} else {
|
|
for (const module of modules) {
|
|
const key = `${chunk.id}|${module.identifier()}`;
|
|
const hash = getModuleHash(module);
|
|
chunkModuleHashes[key] = hash;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
hotIndex = records.hotIndex || 0;
|
|
if (updatedModules.size > 0) hotIndex++;
|
|
|
|
hash.update(`${hotIndex}`);
|
|
});
|
|
compilation.hooks.processAssets.tap(
|
|
{
|
|
name: PLUGIN_NAME,
|
|
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
|
|
},
|
|
() => {
|
|
const chunkGraph = compilation.chunkGraph;
|
|
const records = /** @type {Records} */ (compilation.records);
|
|
if (records.hash === compilation.hash) return;
|
|
if (
|
|
!records.chunkModuleHashes ||
|
|
!records.chunkHashes ||
|
|
!records.chunkModuleIds
|
|
) {
|
|
return;
|
|
}
|
|
const codeGenerationResults =
|
|
/** @type {CodeGenerationResults} */
|
|
(compilation.codeGenerationResults);
|
|
for (const [module, chunk] of fullHashModules) {
|
|
const key = `${chunk.id}|${module.identifier()}`;
|
|
const hash = nonCodeGeneratedModules.has(module, chunk.runtime)
|
|
? chunkGraph.getModuleHash(module, chunk.runtime)
|
|
: codeGenerationResults.getHash(module, chunk.runtime);
|
|
if (records.chunkModuleHashes[key] !== hash) {
|
|
updatedModules.add(module, chunk);
|
|
}
|
|
chunkModuleHashes[key] = hash;
|
|
}
|
|
|
|
/** @type {HotUpdateMainContentByRuntime} */
|
|
const hotUpdateMainContentByRuntime = new Map();
|
|
let allOldRuntime;
|
|
const chunkRuntime =
|
|
/** @type {ChunkRuntime} */
|
|
(records.chunkRuntime);
|
|
for (const key of Object.keys(chunkRuntime)) {
|
|
const runtime = keyToRuntime(chunkRuntime[key]);
|
|
allOldRuntime = mergeRuntimeOwned(allOldRuntime, runtime);
|
|
}
|
|
forEachRuntime(allOldRuntime, (runtime) => {
|
|
const { path: filename, info: assetInfo } =
|
|
compilation.getPathWithInfo(
|
|
compilation.outputOptions.hotUpdateMainFilename,
|
|
{
|
|
hash: records.hash,
|
|
runtime
|
|
}
|
|
);
|
|
hotUpdateMainContentByRuntime.set(
|
|
/** @type {string} */ (runtime),
|
|
{
|
|
updatedChunkIds: new Set(),
|
|
removedChunkIds: new Set(),
|
|
removedModules: new Set(),
|
|
filename,
|
|
assetInfo
|
|
}
|
|
);
|
|
});
|
|
if (hotUpdateMainContentByRuntime.size === 0) return;
|
|
|
|
// Create a list of all active modules to verify which modules are removed completely
|
|
/** @type {Map<ModuleId, Module>} */
|
|
const allModules = new Map();
|
|
for (const module of compilation.modules) {
|
|
const id =
|
|
/** @type {ModuleId} */
|
|
(chunkGraph.getModuleId(module));
|
|
allModules.set(id, module);
|
|
}
|
|
|
|
// List of completely removed modules
|
|
/** @type {Set<ModuleId>} */
|
|
const completelyRemovedModules = new Set();
|
|
|
|
for (const key of Object.keys(records.chunkHashes)) {
|
|
const oldRuntime = keyToRuntime(
|
|
/** @type {ChunkRuntime} */
|
|
(records.chunkRuntime)[key]
|
|
);
|
|
/** @type {Module[]} */
|
|
const remainingModules = [];
|
|
// Check which modules are removed
|
|
for (const id of records.chunkModuleIds[key]) {
|
|
const module = allModules.get(id);
|
|
if (module === undefined) {
|
|
completelyRemovedModules.add(id);
|
|
} else {
|
|
remainingModules.push(module);
|
|
}
|
|
}
|
|
|
|
/** @type {ChunkId | null} */
|
|
let chunkId;
|
|
let newModules;
|
|
let newRuntimeModules;
|
|
let newFullHashModules;
|
|
let newDependentHashModules;
|
|
let newRuntime;
|
|
let removedFromRuntime;
|
|
const currentChunk = find(
|
|
compilation.chunks,
|
|
(chunk) => `${chunk.id}` === key
|
|
);
|
|
if (currentChunk) {
|
|
chunkId = currentChunk.id;
|
|
newRuntime = intersectRuntime(
|
|
currentChunk.runtime,
|
|
allOldRuntime
|
|
);
|
|
if (newRuntime === undefined) continue;
|
|
newModules = chunkGraph
|
|
.getChunkModules(currentChunk)
|
|
.filter((module) => updatedModules.has(module, currentChunk));
|
|
newRuntimeModules = [
|
|
...chunkGraph.getChunkRuntimeModulesIterable(currentChunk)
|
|
].filter((module) => updatedModules.has(module, currentChunk));
|
|
const fullHashModules =
|
|
chunkGraph.getChunkFullHashModulesIterable(currentChunk);
|
|
newFullHashModules =
|
|
fullHashModules &&
|
|
[...fullHashModules].filter((module) =>
|
|
updatedModules.has(module, currentChunk)
|
|
);
|
|
const dependentHashModules =
|
|
chunkGraph.getChunkDependentHashModulesIterable(currentChunk);
|
|
newDependentHashModules =
|
|
dependentHashModules &&
|
|
[...dependentHashModules].filter((module) =>
|
|
updatedModules.has(module, currentChunk)
|
|
);
|
|
removedFromRuntime = subtractRuntime(oldRuntime, newRuntime);
|
|
} else {
|
|
// chunk has completely removed
|
|
chunkId = `${Number(key)}` === key ? Number(key) : key;
|
|
removedFromRuntime = oldRuntime;
|
|
newRuntime = oldRuntime;
|
|
}
|
|
if (removedFromRuntime) {
|
|
// chunk was removed from some runtimes
|
|
forEachRuntime(removedFromRuntime, (runtime) => {
|
|
const item =
|
|
/** @type {HotUpdateMainContentByRuntimeItem} */
|
|
(
|
|
hotUpdateMainContentByRuntime.get(
|
|
/** @type {string} */ (runtime)
|
|
)
|
|
);
|
|
item.removedChunkIds.add(/** @type {ChunkId} */ (chunkId));
|
|
});
|
|
// dispose modules from the chunk in these runtimes
|
|
// where they are no longer in this runtime
|
|
for (const module of remainingModules) {
|
|
const moduleKey = `${key}|${module.identifier()}`;
|
|
const oldHash = records.chunkModuleHashes[moduleKey];
|
|
const runtimes = chunkGraph.getModuleRuntimes(module);
|
|
if (oldRuntime === newRuntime && runtimes.has(newRuntime)) {
|
|
// Module is still in the same runtime combination
|
|
const hash = nonCodeGeneratedModules.has(module, newRuntime)
|
|
? chunkGraph.getModuleHash(module, newRuntime)
|
|
: codeGenerationResults.getHash(module, newRuntime);
|
|
if (hash !== oldHash) {
|
|
if (module.type === WEBPACK_MODULE_TYPE_RUNTIME) {
|
|
newRuntimeModules = newRuntimeModules || [];
|
|
newRuntimeModules.push(
|
|
/** @type {RuntimeModule} */ (module)
|
|
);
|
|
} else {
|
|
newModules = newModules || [];
|
|
newModules.push(module);
|
|
}
|
|
}
|
|
} else {
|
|
// module is no longer in this runtime combination
|
|
// We (incorrectly) assume that it's not in an overlapping runtime combination
|
|
// and dispose it from the main runtimes the chunk was removed from
|
|
forEachRuntime(removedFromRuntime, (runtime) => {
|
|
// If the module is still used in this runtime, do not dispose it
|
|
// This could create a bad runtime state where the module is still loaded,
|
|
// but no chunk which contains it. This means we don't receive further HMR updates
|
|
// to this module and that's bad.
|
|
// TODO force load one of the chunks which contains the module
|
|
for (const moduleRuntime of runtimes) {
|
|
if (typeof moduleRuntime === "string") {
|
|
if (moduleRuntime === runtime) return;
|
|
} else if (
|
|
moduleRuntime !== undefined &&
|
|
moduleRuntime.has(/** @type {string} */ (runtime))
|
|
) {
|
|
return;
|
|
}
|
|
}
|
|
const item =
|
|
/** @type {HotUpdateMainContentByRuntimeItem} */ (
|
|
hotUpdateMainContentByRuntime.get(
|
|
/** @type {string} */ (runtime)
|
|
)
|
|
);
|
|
item.removedModules.add(module);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (
|
|
(newModules && newModules.length > 0) ||
|
|
(newRuntimeModules && newRuntimeModules.length > 0)
|
|
) {
|
|
const hotUpdateChunk = new HotUpdateChunk();
|
|
if (backCompat) {
|
|
ChunkGraph.setChunkGraphForChunk(hotUpdateChunk, chunkGraph);
|
|
}
|
|
hotUpdateChunk.id = chunkId;
|
|
hotUpdateChunk.runtime = currentChunk
|
|
? currentChunk.runtime
|
|
: newRuntime;
|
|
if (currentChunk) {
|
|
for (const group of currentChunk.groupsIterable) {
|
|
hotUpdateChunk.addGroup(group);
|
|
}
|
|
}
|
|
chunkGraph.attachModules(hotUpdateChunk, newModules || []);
|
|
chunkGraph.attachRuntimeModules(
|
|
hotUpdateChunk,
|
|
newRuntimeModules || []
|
|
);
|
|
if (newFullHashModules) {
|
|
chunkGraph.attachFullHashModules(
|
|
hotUpdateChunk,
|
|
newFullHashModules
|
|
);
|
|
}
|
|
if (newDependentHashModules) {
|
|
chunkGraph.attachDependentHashModules(
|
|
hotUpdateChunk,
|
|
newDependentHashModules
|
|
);
|
|
}
|
|
const renderManifest = compilation.getRenderManifest({
|
|
chunk: hotUpdateChunk,
|
|
hash: /** @type {string} */ (records.hash),
|
|
fullHash: /** @type {string} */ (records.hash),
|
|
outputOptions: compilation.outputOptions,
|
|
moduleTemplates: compilation.moduleTemplates,
|
|
dependencyTemplates: compilation.dependencyTemplates,
|
|
codeGenerationResults: /** @type {CodeGenerationResults} */ (
|
|
compilation.codeGenerationResults
|
|
),
|
|
runtimeTemplate: compilation.runtimeTemplate,
|
|
moduleGraph: compilation.moduleGraph,
|
|
chunkGraph
|
|
});
|
|
for (const entry of renderManifest) {
|
|
/** @type {string} */
|
|
let filename;
|
|
/** @type {AssetInfo} */
|
|
let assetInfo;
|
|
if ("filename" in entry) {
|
|
filename = entry.filename;
|
|
assetInfo = entry.info;
|
|
} else {
|
|
({ path: filename, info: assetInfo } =
|
|
compilation.getPathWithInfo(
|
|
entry.filenameTemplate,
|
|
entry.pathOptions
|
|
));
|
|
}
|
|
const source = entry.render();
|
|
compilation.additionalChunkAssets.push(filename);
|
|
compilation.emitAsset(filename, source, {
|
|
hotModuleReplacement: true,
|
|
...assetInfo
|
|
});
|
|
if (currentChunk) {
|
|
currentChunk.files.add(filename);
|
|
compilation.hooks.chunkAsset.call(currentChunk, filename);
|
|
}
|
|
}
|
|
forEachRuntime(newRuntime, (runtime) => {
|
|
const item =
|
|
/** @type {HotUpdateMainContentByRuntimeItem} */ (
|
|
hotUpdateMainContentByRuntime.get(
|
|
/** @type {string} */ (runtime)
|
|
)
|
|
);
|
|
item.updatedChunkIds.add(/** @type {ChunkId} */ (chunkId));
|
|
});
|
|
}
|
|
}
|
|
const completelyRemovedModulesArray = [...completelyRemovedModules];
|
|
const hotUpdateMainContentByFilename = new Map();
|
|
for (const {
|
|
removedChunkIds,
|
|
removedModules,
|
|
updatedChunkIds,
|
|
filename,
|
|
assetInfo
|
|
} of hotUpdateMainContentByRuntime.values()) {
|
|
const old = hotUpdateMainContentByFilename.get(filename);
|
|
if (
|
|
old &&
|
|
(!isSubset(old.removedChunkIds, removedChunkIds) ||
|
|
!isSubset(old.removedModules, removedModules) ||
|
|
!isSubset(old.updatedChunkIds, updatedChunkIds))
|
|
) {
|
|
compilation.warnings.push(
|
|
new WebpackError(`HotModuleReplacementPlugin
|
|
The configured output.hotUpdateMainFilename doesn't lead to unique filenames per runtime and HMR update differs between runtimes.
|
|
This might lead to incorrect runtime behavior of the applied update.
|
|
To fix this, make sure to include [runtime] in the output.hotUpdateMainFilename option, or use the default config.`)
|
|
);
|
|
for (const chunkId of removedChunkIds) {
|
|
old.removedChunkIds.add(chunkId);
|
|
}
|
|
for (const chunkId of removedModules) {
|
|
old.removedModules.add(chunkId);
|
|
}
|
|
for (const chunkId of updatedChunkIds) {
|
|
old.updatedChunkIds.add(chunkId);
|
|
}
|
|
continue;
|
|
}
|
|
hotUpdateMainContentByFilename.set(filename, {
|
|
removedChunkIds,
|
|
removedModules,
|
|
updatedChunkIds,
|
|
assetInfo
|
|
});
|
|
}
|
|
for (const [
|
|
filename,
|
|
{ removedChunkIds, removedModules, updatedChunkIds, assetInfo }
|
|
] of hotUpdateMainContentByFilename) {
|
|
const hotUpdateMainJson = {
|
|
c: [...updatedChunkIds],
|
|
r: [...removedChunkIds],
|
|
m:
|
|
removedModules.size === 0
|
|
? completelyRemovedModulesArray
|
|
: [
|
|
...completelyRemovedModulesArray,
|
|
...Array.from(
|
|
removedModules,
|
|
(m) =>
|
|
/** @type {ModuleId} */ (chunkGraph.getModuleId(m))
|
|
)
|
|
]
|
|
};
|
|
|
|
const source = new RawSource(
|
|
(filename.endsWith(".json") ? "" : "export default ") +
|
|
JSON.stringify(hotUpdateMainJson)
|
|
);
|
|
compilation.emitAsset(filename, source, {
|
|
hotModuleReplacement: true,
|
|
...assetInfo
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
compilation.hooks.additionalTreeRuntimeRequirements.tap(
|
|
PLUGIN_NAME,
|
|
(chunk, runtimeRequirements) => {
|
|
runtimeRequirements.add(RuntimeGlobals.hmrDownloadManifest);
|
|
runtimeRequirements.add(RuntimeGlobals.hmrDownloadUpdateHandlers);
|
|
runtimeRequirements.add(RuntimeGlobals.interceptModuleExecution);
|
|
runtimeRequirements.add(RuntimeGlobals.moduleCache);
|
|
compilation.addRuntimeModule(
|
|
chunk,
|
|
new HotModuleReplacementRuntimeModule()
|
|
);
|
|
}
|
|
);
|
|
|
|
normalModuleFactory.hooks.parser
|
|
.for(JAVASCRIPT_MODULE_TYPE_AUTO)
|
|
.tap(PLUGIN_NAME, (parser) => {
|
|
applyModuleHot(parser);
|
|
applyImportMetaHot(parser);
|
|
});
|
|
normalModuleFactory.hooks.parser
|
|
.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
|
|
.tap(PLUGIN_NAME, (parser) => {
|
|
applyModuleHot(parser);
|
|
});
|
|
normalModuleFactory.hooks.parser
|
|
.for(JAVASCRIPT_MODULE_TYPE_ESM)
|
|
.tap(PLUGIN_NAME, (parser) => {
|
|
applyImportMetaHot(parser);
|
|
});
|
|
normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module) => {
|
|
module.hot = true;
|
|
return module;
|
|
});
|
|
|
|
NormalModule.getCompilationHooks(compilation).loader.tap(
|
|
PLUGIN_NAME,
|
|
(context) => {
|
|
context.hot = true;
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = HotModuleReplacementPlugin;
|