mirror of https://github.com/webpack/webpack.git
fix incorrect disposing of modules during HMR
This commit is contained in:
parent
15638abfac
commit
12997f0a6c
|
|
@ -27,6 +27,7 @@ const {
|
|||
const { find } = require("./util/SetHelpers");
|
||||
const TupleSet = require("./util/TupleSet");
|
||||
const { compareModulesById } = require("./util/comparators");
|
||||
const { getRuntimeKey, keyToRuntime } = require("./util/runtime");
|
||||
|
||||
/** @typedef {import("./Chunk")} Chunk */
|
||||
/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
|
||||
|
|
@ -298,8 +299,10 @@ class HotModuleReplacementPlugin {
|
|||
records.fullHashChunkModuleHashes = fullHashChunkModuleHashes;
|
||||
records.chunkModuleHashes = chunkModuleHashes;
|
||||
records.chunkHashs = {};
|
||||
records.chunkRuntime = {};
|
||||
for (const chunk of compilation.chunks) {
|
||||
records.chunkHashs[chunk.id] = chunk.hash;
|
||||
records.chunkRuntime[chunk.id] = getRuntimeKey(chunk.runtime);
|
||||
}
|
||||
records.chunkModuleIds = {};
|
||||
for (const chunk of compilation.chunks) {
|
||||
|
|
@ -403,104 +406,126 @@ class HotModuleReplacementPlugin {
|
|||
r: [],
|
||||
m: undefined
|
||||
};
|
||||
|
||||
// Create a list of all active modules to verify which modules are removed completely
|
||||
/** @type {Map<number|string, Module>} */
|
||||
const allModules = new Map();
|
||||
for (const module of compilation.modules) {
|
||||
allModules.set(chunkGraph.getModuleId(module), module);
|
||||
}
|
||||
|
||||
// List of completely removed modules
|
||||
const allRemovedModules = new Set();
|
||||
|
||||
for (const key of Object.keys(records.chunkHashs)) {
|
||||
// Check which modules are completely removed
|
||||
for (const id of records.chunkModuleIds[key]) {
|
||||
if (!allModules.has(id)) {
|
||||
allRemovedModules.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
let chunkId;
|
||||
let newModules;
|
||||
let newRuntimeModules;
|
||||
let newFullHashModules;
|
||||
let newRuntime;
|
||||
const currentChunk = find(
|
||||
compilation.chunks,
|
||||
chunk => `${chunk.id}` === key
|
||||
);
|
||||
if (currentChunk) {
|
||||
const chunkId = currentChunk.id;
|
||||
const newModules = chunkGraph
|
||||
chunkId = currentChunk.id;
|
||||
newRuntime = currentChunk.runtime;
|
||||
newModules = chunkGraph
|
||||
.getChunkModules(currentChunk)
|
||||
.filter(module => updatedModules.has(module, currentChunk));
|
||||
const newRuntimeModules = Array.from(
|
||||
newRuntimeModules = Array.from(
|
||||
chunkGraph.getChunkRuntimeModulesIterable(currentChunk)
|
||||
).filter(module => updatedModules.has(module, currentChunk));
|
||||
const fullHashModules = chunkGraph.getChunkFullHashModulesIterable(
|
||||
currentChunk
|
||||
);
|
||||
const newFullHashModules =
|
||||
newFullHashModules =
|
||||
fullHashModules &&
|
||||
Array.from(fullHashModules).filter(module =>
|
||||
updatedModules.has(module, currentChunk)
|
||||
);
|
||||
/** @type {Set<number|string>} */
|
||||
const allModules = new Set();
|
||||
for (const module of chunkGraph.getChunkModulesIterable(
|
||||
currentChunk
|
||||
)) {
|
||||
allModules.add(chunkGraph.getModuleId(module));
|
||||
}
|
||||
const removedModules = records.chunkModuleIds[chunkId].filter(
|
||||
id => !allModules.has(id)
|
||||
);
|
||||
if (
|
||||
newModules.length > 0 ||
|
||||
newRuntimeModules.length > 0 ||
|
||||
removedModules.length > 0
|
||||
) {
|
||||
const hotUpdateChunk = new HotUpdateChunk();
|
||||
ChunkGraph.setChunkGraphForChunk(hotUpdateChunk, chunkGraph);
|
||||
hotUpdateChunk.id = chunkId;
|
||||
hotUpdateChunk.runtime = currentChunk.runtime;
|
||||
chunkGraph.attachModules(hotUpdateChunk, newModules);
|
||||
chunkGraph.attachRuntimeModules(
|
||||
hotUpdateChunk,
|
||||
newRuntimeModules
|
||||
);
|
||||
if (newFullHashModules) {
|
||||
chunkGraph.attachFullHashModules(
|
||||
hotUpdateChunk,
|
||||
newFullHashModules
|
||||
);
|
||||
} else {
|
||||
chunkId = `${+key}` === key ? +key : key;
|
||||
hotUpdateMainContent.r.push(chunkId);
|
||||
const runtime = keyToRuntime(records.chunkRuntime[key]);
|
||||
for (const id of records.chunkModuleIds[key]) {
|
||||
const module = allModules.get(id);
|
||||
if (!module) continue;
|
||||
const hash = chunkGraph.getModuleHash(module, runtime);
|
||||
const moduleKey = `${key}|${module.identifier()}`;
|
||||
if (hash !== records.chunkModuleHashes[moduleKey]) {
|
||||
newModules = newModules || [];
|
||||
newModules.push(module);
|
||||
}
|
||||
hotUpdateChunk.removedModules = removedModules;
|
||||
const renderManifest = compilation.getRenderManifest({
|
||||
chunk: hotUpdateChunk,
|
||||
hash: records.hash,
|
||||
fullHash: records.hash,
|
||||
outputOptions: compilation.outputOptions,
|
||||
moduleTemplates: compilation.moduleTemplates,
|
||||
dependencyTemplates: compilation.dependencyTemplates,
|
||||
codeGenerationResults: compilation.codeGenerationResults,
|
||||
runtimeTemplate: compilation.runtimeTemplate,
|
||||
moduleGraph: compilation.moduleGraph,
|
||||
chunkGraph
|
||||
}
|
||||
}
|
||||
if (
|
||||
(newModules && newModules.length > 0) ||
|
||||
(newRuntimeModules && newRuntimeModules.length > 0)
|
||||
) {
|
||||
const hotUpdateChunk = new HotUpdateChunk();
|
||||
ChunkGraph.setChunkGraphForChunk(hotUpdateChunk, chunkGraph);
|
||||
hotUpdateChunk.id = chunkId;
|
||||
hotUpdateChunk.runtime = newRuntime;
|
||||
chunkGraph.attachModules(hotUpdateChunk, newModules || []);
|
||||
chunkGraph.attachRuntimeModules(
|
||||
hotUpdateChunk,
|
||||
newRuntimeModules || []
|
||||
);
|
||||
if (newFullHashModules) {
|
||||
chunkGraph.attachFullHashModules(
|
||||
hotUpdateChunk,
|
||||
newFullHashModules
|
||||
);
|
||||
}
|
||||
const renderManifest = compilation.getRenderManifest({
|
||||
chunk: hotUpdateChunk,
|
||||
hash: records.hash,
|
||||
fullHash: records.hash,
|
||||
outputOptions: compilation.outputOptions,
|
||||
moduleTemplates: compilation.moduleTemplates,
|
||||
dependencyTemplates: compilation.dependencyTemplates,
|
||||
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
|
||||
});
|
||||
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);
|
||||
}
|
||||
hotUpdateMainContent.c.push(chunkId);
|
||||
}
|
||||
} else {
|
||||
const chunkId = `${+key}` === key ? +key : key;
|
||||
hotUpdateMainContent.r.push(chunkId);
|
||||
for (const id of records.chunkModuleIds[chunkId])
|
||||
allRemovedModules.add(id);
|
||||
hotUpdateMainContent.c.push(chunkId);
|
||||
}
|
||||
}
|
||||
hotUpdateMainContent.m = Array.from(allRemovedModules);
|
||||
|
|
|
|||
|
|
@ -13,18 +13,6 @@ const Chunk = require("./Chunk");
|
|||
class HotUpdateChunk extends Chunk {
|
||||
constructor() {
|
||||
super();
|
||||
/** @type {(string|number)[]} */
|
||||
this.removedModules = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Hash} hash hash (will be modified)
|
||||
* @param {ChunkGraph} chunkGraph the chunk graph
|
||||
* @returns {void}
|
||||
*/
|
||||
updateHash(hash, chunkGraph) {
|
||||
super.updateHash(hash, chunkGraph);
|
||||
hash.update(JSON.stringify(this.removedModules));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
"use strict";
|
||||
|
||||
const { ConcatSource, PrefixSource } = require("webpack-sources");
|
||||
const HotUpdateChunk = require("./HotUpdateChunk");
|
||||
const { compareIds } = require("./util/comparators");
|
||||
|
||||
/** @typedef {import("webpack-sources").ConcatSource} ConcatSource */
|
||||
/** @typedef {import("webpack-sources").Source} Source */
|
||||
|
|
@ -292,16 +290,9 @@ class Template {
|
|||
* @returns {Source} rendered chunk modules in a Source object
|
||||
*/
|
||||
static renderChunkModules(renderContext, modules, renderModule, prefix = "") {
|
||||
const { chunk, chunkGraph } = renderContext;
|
||||
const { chunkGraph } = renderContext;
|
||||
var source = new ConcatSource();
|
||||
let removedModules;
|
||||
if (chunk instanceof HotUpdateChunk) {
|
||||
removedModules = chunk.removedModules;
|
||||
}
|
||||
if (
|
||||
modules.length === 0 &&
|
||||
(!removedModules || removedModules.length === 0)
|
||||
) {
|
||||
if (modules.length === 0) {
|
||||
return null;
|
||||
}
|
||||
/** @type {{id: string|number, source: Source|string}[]} */
|
||||
|
|
@ -311,15 +302,6 @@ class Template {
|
|||
source: renderModule(module) || "false"
|
||||
};
|
||||
});
|
||||
if (removedModules && removedModules.length > 0) {
|
||||
removedModules.sort(compareIds);
|
||||
for (const id of removedModules) {
|
||||
allModules.push({
|
||||
id,
|
||||
source: "false"
|
||||
});
|
||||
}
|
||||
}
|
||||
const bounds = Template.getModulesArrayBounds(allModules);
|
||||
if (bounds) {
|
||||
// Render a spare array
|
||||
|
|
|
|||
|
|
@ -320,7 +320,6 @@ class JavascriptModulesPlugin {
|
|||
hashFunction
|
||||
}
|
||||
} = compilation;
|
||||
const hotUpdateChunk = chunk instanceof HotUpdateChunk ? chunk : null;
|
||||
const hash = createHash(hashFunction);
|
||||
if (hashSalt) hash.update(hashSalt);
|
||||
hash.update(`${chunk.id} `);
|
||||
|
|
@ -352,9 +351,6 @@ class JavascriptModulesPlugin {
|
|||
}
|
||||
xor.updateHash(hash);
|
||||
}
|
||||
if (hotUpdateChunk) {
|
||||
hash.update(JSON.stringify(hotUpdateChunk.removedModules));
|
||||
}
|
||||
const digest = /** @type {string} */ (hash.digest(hashDigest));
|
||||
chunk.contentHash.javascript = digest.substr(0, hashDigestLength);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -83,6 +83,18 @@ const getRuntimeKey = runtime => {
|
|||
};
|
||||
exports.getRuntimeKey = getRuntimeKey;
|
||||
|
||||
/**
|
||||
* @param {string} key key of runtimes
|
||||
* @returns {RuntimeSpec} runtime(s)
|
||||
*/
|
||||
const keyToRuntime = key => {
|
||||
if (key === "*") return undefined;
|
||||
const items = key.split("\n");
|
||||
if (items.length === 1) return items[0];
|
||||
return new SortableSet(items);
|
||||
};
|
||||
exports.keyToRuntime = keyToRuntime;
|
||||
|
||||
const getRuntimesString = set => {
|
||||
set.sort();
|
||||
return Array.from(set).join("+");
|
||||
|
|
@ -314,12 +326,7 @@ class RuntimeSpecMap {
|
|||
}
|
||||
|
||||
keys() {
|
||||
return Array.from(this._map.keys(), key => {
|
||||
if (key === "*") return undefined;
|
||||
const items = key.split("\n");
|
||||
if (items.length === 1) return items[0];
|
||||
return new SortableSet(items);
|
||||
});
|
||||
return Array.from(this._map.keys(), keyToRuntime);
|
||||
}
|
||||
|
||||
values() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./shared";
|
||||
import.meta.webpackHot.accept("./shared");
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./shared";
|
||||
import.meta.webpackHot.accept("./shared");
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import module from "./module";
|
||||
|
||||
it("should not disposed shared modules when a chunk is removed", done => {
|
||||
import("./chunk1").then(chunk1 => {
|
||||
import.meta.webpackHot.accept("./module", async () => {
|
||||
expect(module).toBe(42);
|
||||
expect(chunk1).toMatchObject({
|
||||
active: true
|
||||
});
|
||||
done();
|
||||
});
|
||||
NEXT(require("../../update")(done));
|
||||
}, done);
|
||||
});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export default import("./chunk2");
|
||||
---
|
||||
export default 42;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export let active = true;
|
||||
|
||||
import.meta.webpackHot.dispose(() => {
|
||||
active = false;
|
||||
});
|
||||
Loading…
Reference in New Issue