fix: fix conflicts caused by multiple concatenateModules

This commit is contained in:
Xiao 2025-09-01 21:49:53 +08:00 committed by GitHub
parent df563e9b50
commit 62924911ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 90 additions and 2 deletions

View File

@ -10,6 +10,7 @@ const {
NAMESPACE_OBJECT_EXPORT
} = require("./util/concatenate");
/** @typedef {import("./Chunk")} Chunk */
/** @typedef {import("./Module")} Module */
/** @typedef {import("./optimize/ConcatenatedModule").ConcatenatedModuleInfo} ConcatenatedModuleInfo */
/** @typedef {import("./optimize/ConcatenatedModule").ModuleInfo} ModuleInfo */
@ -187,4 +188,7 @@ class ConcatenationScope {
ConcatenationScope.DEFAULT_EXPORT = DEFAULT_EXPORT;
ConcatenationScope.NAMESPACE_OBJECT_EXPORT = NAMESPACE_OBJECT_EXPORT;
/** @type {WeakMap<Chunk, Set<string>>} */
ConcatenationScope.chunkUsedNames = new WeakMap();
module.exports = ConcatenationScope;

View File

@ -239,6 +239,8 @@ const moveDeferToLast = (
return -1;
};
const INITIAL_USED_NAMES = new Set(RESERVED_NAMES);
/**
* @param {Iterable<string>} iterable iterable object
* @returns {string} joined iterable object
@ -1212,8 +1214,18 @@ class ConcatenatedModule extends Module {
/** @type {NeededNamespaceObjects} */
const neededNamespaceObjects = new Set();
// List of all used names to avoid conflicts
const allUsedNames = new Set(RESERVED_NAMES);
// Default disallowed names
const allUsedNames = new Set(INITIAL_USED_NAMES);
const chunks = chunkGraph.getModuleChunks(this);
// Add names already used in the current chunk scope
for (const chunk of chunks) {
if (ConcatenationScope.chunkUsedNames.has(chunk)) {
for (const name of ConcatenationScope.chunkUsedNames.get(chunk) || []) {
allUsedNames.add(name);
}
}
}
// Generate source code and analyse scopes
// Prepare a ReplaceSource for the final source
@ -1232,6 +1244,23 @@ class ConcatenatedModule extends Module {
);
}
// Record the names registered by the current ConcatenatedModule into the chunk scope
if (INITIAL_USED_NAMES.size !== allUsedNames.size) {
for (const name of allUsedNames) {
if (INITIAL_USED_NAMES.has(name)) continue;
for (const chunk of chunks) {
if (!ConcatenationScope.chunkUsedNames.has(chunk)) {
ConcatenationScope.chunkUsedNames.set(chunk, new Set([name]));
} else {
/** @type {Set<string>} */ (
ConcatenationScope.chunkUsedNames.get(chunk)
).add(name);
}
}
}
}
// Updated Top level declarations are created by renaming
/** @type {TopLevelDeclarations} */
const topLevelDeclarations = new Set();

View File

@ -0,0 +1,2 @@
require('./other-entry.js')

View File

@ -0,0 +1,6 @@
import { readFile } from 'externals-1/foo'
import './cjs'
it('should not optimize external modules in different concatenation scope', () => {
expect(readFile).toBeDefined()
})

View File

@ -0,0 +1,5 @@
import { readFile } from 'externals-2/foo'
it('should not optimize external modules in different concatenation scope', () => {
expect(readFile).toBeDefined()
})

View File

@ -0,0 +1,7 @@
"use strict";
module.exports = {
findBundle() {
return ["main.mjs"];
}
};

View File

@ -0,0 +1,34 @@
"use strict";
/** @type {import("../../../../types").Configuration} */
module.exports = {
cache: {
type: "memory" // Enable memory cache to test serialization
},
target: "node",
mode: "none",
entry: { main: "./index.js", test: "./other-entry.js" },
output: {
module: true,
library: {
type: "modern-module"
},
filename: "[name].mjs",
chunkFormat: "module"
},
experiments: {
outputModule: true
},
resolve: {
extensions: [".js"]
},
externalsType: "module",
externals: {
"externals-1/foo": "fs",
"externals-2/foo": "fs-extra"
},
optimization: {
concatenateModules: true,
usedExports: true
}
};

1
types.d.ts vendored
View File

@ -2891,6 +2891,7 @@ declare class ConcatenationScope {
): null | (ModuleReferenceOptions & { index: number });
static DEFAULT_EXPORT: string;
static NAMESPACE_OBJECT_EXPORT: string;
static chunkUsedNames: WeakMap<Chunk, Set<string>>;
}
/**