test: add test case for circular dependency with externals (#19623)
Github Actions / lint (push) Waiting to run Details
Github Actions / validate-legacy-node (push) Waiting to run Details
Github Actions / benchmark (1/4) (push) Waiting to run Details
Github Actions / benchmark (2/4) (push) Waiting to run Details
Github Actions / benchmark (3/4) (push) Waiting to run Details
Github Actions / benchmark (4/4) (push) Waiting to run Details
Github Actions / basic (push) Waiting to run Details
Github Actions / unit (push) Waiting to run Details
Github Actions / integration (10.x, macos-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (10.x, macos-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (10.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (10.x, ubuntu-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (10.x, windows-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (10.x, windows-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (12.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (14.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (16.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (18.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (20.x, macos-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (20.x, macos-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (20.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (20.x, ubuntu-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (20.x, windows-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (20.x, windows-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (22.x, macos-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (22.x, macos-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (22.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (22.x, ubuntu-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (22.x, windows-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (22.x, windows-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (24.x, macos-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (24.x, macos-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (24.x, ubuntu-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (24.x, ubuntu-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (24.x, windows-latest, a) (push) Blocked by required conditions Details
Github Actions / integration (24.x, windows-latest, b) (push) Blocked by required conditions Details
Github Actions / integration (lts/*, ubuntu-latest, a, 1) (push) Blocked by required conditions Details
Github Actions / integration (lts/*, ubuntu-latest, b, 1) (push) Blocked by required conditions Details

This commit is contained in:
Ryuya 2025-06-25 03:21:53 -07:00 committed by GitHub
parent 21b28a82f7
commit 10fb5566e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 165 additions and 0 deletions

View File

@ -0,0 +1,10 @@
import { externalValue as valueB, getOtherExternal as getB } from "./external-b.mjs";
export const externalValue = "external-A";
export function getOtherExternal() {
return valueB;
}
// Re-export to test circular re-exports
export { getB as getOtherValue };

View File

@ -0,0 +1,10 @@
import { externalValue as valueA, getOtherExternal as getA } from "./external-a.mjs";
export const externalValue = "external-B";
export function getOtherExternal() {
return valueA;
}
// Re-export to test circular re-exports
export { getA as getOtherValue };

View File

@ -0,0 +1,51 @@
import { valueA, getFromExternalA, callB } from "./module-a.js";
import { valueB, getFromExternalB, callA } from "./module-b.js";
import { externalValue as directExternalA } from "external-module-a";
import { externalValue as directExternalB } from "external-module-b";
it("should handle circular dependencies between internal modules", () => {
expect(valueA).toBe("module-A");
expect(valueB).toBe("module-B");
expect(callB()).toBe("module-B");
expect(callA()).toBe("module-A");
});
it("should handle imports from external modules", () => {
expect(getFromExternalA()).toBe("external-A");
expect(getFromExternalB()).toBe("external-B");
});
it("should handle direct imports from external modules", () => {
expect(directExternalA).toBe("external-A");
expect(directExternalB).toBe("external-B");
});
// ESM external modules with circular dependencies
it("should maintain live bindings for ESM external modules", async () => {
// Import external modules that have circular dependencies
const moduleA = await import("external-module-a");
const moduleB = await import("external-module-b");
// Verify that circular dependencies are resolved correctly
expect(moduleA.externalValue).toBe("external-A");
expect(moduleB.externalValue).toBe("external-B");
// Verify that re-exports work correctly in circular scenarios
expect(moduleA.getOtherValue).toBeDefined();
expect(moduleB.getOtherValue).toBeDefined();
// Test that the modules maintain their identity (live bindings)
expect(await import("external-module-a")).toBe(moduleA);
expect(await import("external-module-b")).toBe(moduleB);
});
// Edge case: Multiple imports of the same external module
it("should handle multiple imports of circular external modules", () => {
// This tests that the runtime module correctly caches external modules
const firstImportA = directExternalA;
const secondImportA = getFromExternalA();
// Both should reference the same value
expect(firstImportA).toBe(secondImportA);
expect(firstImportA).toBe("external-A");
});

View File

@ -0,0 +1,12 @@
import { valueB } from "./module-b.js";
import { externalValue } from "external-module-a";
export const valueA = "module-A";
export function getFromExternalA() {
return externalValue;
}
export function callB() {
return valueB;
}

View File

@ -0,0 +1,12 @@
import { valueA } from "./module-a.js";
import { externalValue } from "external-module-b";
export const valueB = "module-B";
export function getFromExternalB() {
return externalValue;
}
export function callA() {
return valueA;
}

View File

@ -0,0 +1,5 @@
module.exports = {
findBundle() {
return "./main.mjs";
}
};

View File

@ -0,0 +1,65 @@
const fs = require("fs");
const path = require("path");
/** @type {import("../../../../types").Configuration} */
module.exports = {
entry: "./index.js",
experiments: {
outputModule: true
},
output: {
module: true,
library: {
type: "module"
},
filename: "[name].mjs",
chunkFormat: "module"
},
externals: {
"external-module-a": "module ./external-a.mjs",
"external-module-b": "module ./external-b.mjs"
},
externalsType: "module",
optimization: {
concatenateModules: false
},
plugins: [
{
apply(compiler) {
compiler.hooks.thisCompilation.tap(
"copy-external-files",
compilation => {
compilation.hooks.processAssets.tap(
{
name: "copy-external-files",
stage:
compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
},
() => {
// Read the external module files
const externalA = fs.readFileSync(
path.join(__dirname, "external-a.mjs"),
"utf-8"
);
const externalB = fs.readFileSync(
path.join(__dirname, "external-b.mjs"),
"utf-8"
);
// Emit them as assets
compilation.emitAsset(
"external-a.mjs",
new compiler.webpack.sources.RawSource(externalA)
);
compilation.emitAsset(
"external-b.mjs",
new compiler.webpack.sources.RawSource(externalB)
);
}
);
}
);
}
}
]
};