Skip to content
Closed
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e22f492
feat(runtime): add shared webpack require accessor helpers
cursoragent Feb 17, 2026
dce6b4d
fix(utils): update remote container typing for helper import
cursoragent Feb 17, 2026
76c2216
refactor(runtime-core): decouple bundler ignore helper from sdk exports
cursoragent Feb 17, 2026
adff9b6
fix(utils): keep remote container type surface unchanged
cursoragent Feb 17, 2026
707aab5
fix(webpack-bundler-runtime): keep internal direct webpack require usage
cursoragent Feb 17, 2026
aa47578
refactor(utils): simplify esm remote initialization typing
cursoragent Feb 17, 2026
14d1b76
fix(webpack-bundler-runtime): use module-level webpack require accessor
cursoragent Feb 17, 2026
04ba1b4
feat(runtime-core): prefer bundler runtime import ignore helper
cursoragent Feb 17, 2026
7b274f1
refactor(webpack-bundler-runtime): keep ignore helper as standalone e…
cursoragent Feb 17, 2026
e3de5a3
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Feb 17, 2026
e0c0704
refactor(node): migrate runtime webpack require access to bundler acc…
cursoragent Feb 17, 2026
f953e6e
refactor: keep webpack require helpers on accessor entrypoint
cursoragent Feb 17, 2026
4c257d8
test(webpack-bundler-runtime): avoid global webpack require mutation
cursoragent Feb 17, 2026
92b7ec0
refactor(runtime-core): source bundler-ignore helper from sdk
cursoragent Feb 17, 2026
2e41604
refactor(sdk): expose bundler helper via standalone entrypoint
cursoragent Feb 17, 2026
49d79a6
refactor: route webpack runtime helpers through sdk bundler entry
cursoragent Feb 18, 2026
385f226
fix(utils): avoid esm namespace mutation and defer webpack require
cursoragent Feb 18, 2026
0211f52
refactor(webpack-bundler-runtime): remove accessor entrypoint leftovers
cursoragent Feb 18, 2026
ac23c9a
fix(webpack-bundler-runtime): use sdk bundler webpack require
cursoragent Feb 18, 2026
c911bb6
fix: add regression coverage and align release metadata
cursoragent Feb 18, 2026
ce7453a
fix(node): inline webpack require in runtime chunk strategies
cursoragent Feb 18, 2026
f2ba7eb
feat(sdk): expose webpack sharing accessors in bundler entry
cursoragent Feb 18, 2026
ca9fea3
fix(node): use sdk bundler accessor in strategies
cursoragent Feb 18, 2026
dd14287
fix(node): remove any cast in hot-reload webpack accessor
cursoragent Feb 18, 2026
6e56bd7
refactor(utils): remove async wrappers in sharing scope setup
cursoragent Feb 18, 2026
55fa8f6
test(core): add bundler and esm validation coverage
cursoragent Feb 18, 2026
bcff4b2
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Feb 19, 2026
e278fde
Merge remote-tracking branch 'origin/main' into cursor/bundler-runtim…
ScriptedAlchemy Feb 24, 2026
539aef8
fix(dts-plugin): align workspace entrypoints and RawSource typing
ScriptedAlchemy Feb 24, 2026
2fabca3
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Feb 25, 2026
fa6339f
fix(sdk): align package entrypoints with emitted artifacts
ScriptedAlchemy Feb 25, 2026
e85e0f4
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Feb 25, 2026
d550fd4
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Feb 26, 2026
cb38d95
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Feb 26, 2026
724a9b4
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Feb 27, 2026
9f163a2
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Feb 27, 2026
acbd6d6
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Feb 28, 2026
443d2df
chore: merge main into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Feb 28, 2026
535729c
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Mar 2, 2026
0b299ab
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Mar 3, 2026
949cfb7
Merge remote-tracking branch 'origin/main' into cursor/bundler-runtim…
ScriptedAlchemy Mar 5, 2026
c1fea4a
Merge branch 'main' into cursor/bundler-runtime-require-access-725d
ScriptedAlchemy Mar 9, 2026
c885b33
Merge remote-tracking branch 'origin/main' into cursor/bundler-runtim…
ScriptedAlchemy Mar 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/twelve-forks-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@module-federation/webpack-bundler-runtime': patch
'@module-federation/runtime-core': patch
'@module-federation/utilities': patch
'@module-federation/node': patch
'@module-federation/sdk': patch
---

Add runtime-safe access helpers for webpack require and ignored dynamic imports, and migrate core/node runtime loaders to use these helpers. The dynamic import helper is exposed via a standalone `@module-federation/sdk/bundler` entrypoint so it can be built and consumed independently from the SDK main index bundle.
28 changes: 14 additions & 14 deletions packages/node/src/__tests__/runtimePlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ global.fetch = jest.fn().mockResolvedValue({
text: jest.fn().mockResolvedValue('// mock chunk content'),
});

const mockWebpackRequire = {
const mockWebpackRequire = Object.assign(jest.fn(), {
u: jest.fn((chunkId: string) => `/chunks/${chunkId}.js`),
p: 'http://localhost:3000/',
m: {},
Expand All @@ -72,7 +72,7 @@ const mockWebpackRequire = {
require: jest.fn(),
readFileVm: jest.fn(),
},
};
});

const mockNonWebpackRequire = jest.fn().mockImplementation((id: string) => {
if (id === 'path') return require('path');
Expand Down Expand Up @@ -682,7 +682,7 @@ describe('runtimePlugin', () => {
beforeEach(() => {
jest.clearAllMocks();
// Set up webpack_require properties needed for the test
(global as any).__webpack_require__ = {
(global as any).__webpack_require__ = Object.assign(jest.fn(), {
...mockWebpackRequire,
federation: {
chunkMatcher: jest.fn().mockReturnValue(true),
Expand All @@ -693,7 +693,7 @@ describe('runtimePlugin', () => {
require: undefined,
readFileVm: undefined,
},
};
});
});

it('should return a handler for chunk loading and reuse existing promises', () => {
Expand Down Expand Up @@ -755,13 +755,13 @@ describe('runtimePlugin', () => {
beforeEach(() => {
jest.clearAllMocks();
// Reset webpack require to ensure f exists with require already defined
(global as any).__webpack_require__ = {
(global as any).__webpack_require__ = Object.assign(jest.fn(), {
...mockWebpackRequire,
f: {
require: jest.fn(), // This needs to exist for the function to patch it
readFileVm: jest.fn(), // This needs to exist for the function to patch it
},
};
});
// Mock console.warn for testing
console.warn = jest.fn();
});
Expand Down Expand Up @@ -804,7 +804,7 @@ describe('runtimePlugin', () => {
beforeEach(() => {
jest.clearAllMocks();
// Reset webpack require to ensure clean state
(global as any).__webpack_require__ = {
(global as any).__webpack_require__ = Object.assign(jest.fn(), {
...mockWebpackRequire,
federation: {
...mockWebpackRequire.federation,
Expand All @@ -819,7 +819,7 @@ describe('runtimePlugin', () => {
require: undefined,
readFileVm: undefined,
},
};
});
});

it('should return the provided args', () => {
Expand Down Expand Up @@ -880,7 +880,7 @@ describe('runtimePlugin', () => {
describe('webpack chunk loading', () => {
beforeEach(() => {
// Reset the webpack require object to ensure it's properly initialized
(global as any).__webpack_require__ = {
(global as any).__webpack_require__ = Object.assign(jest.fn(), {
...mockWebpackRequire,
federation: {
...mockWebpackRequire.federation,
Expand All @@ -895,7 +895,7 @@ describe('runtimePlugin', () => {
require: jest.fn(),
readFileVm: jest.fn(),
},
};
});

const mockArgs = {
origin: {
Expand Down Expand Up @@ -1018,7 +1018,7 @@ describe('runtimePlugin', () => {
describe('Webpack require functionality', () => {
beforeEach(() => {
// Ensure the webpack require object is properly initialized
(global as any).__webpack_require__ = {
(global as any).__webpack_require__ = Object.assign(jest.fn(), {
...mockWebpackRequire,
federation: {
...mockWebpackRequire.federation,
Expand All @@ -1033,7 +1033,7 @@ describe('runtimePlugin', () => {
require: jest.fn(),
readFileVm: jest.fn(),
},
};
});

const mockArgs = {
origin: {
Expand Down Expand Up @@ -1122,7 +1122,7 @@ describe('runtimePlugin', () => {
describe('Remote entry loading', () => {
beforeEach(() => {
// Ensure the webpack require object is properly initialized
(global as any).__webpack_require__ = {
(global as any).__webpack_require__ = Object.assign(jest.fn(), {
...mockWebpackRequire,
federation: {
...mockWebpackRequire.federation,
Expand All @@ -1137,7 +1137,7 @@ describe('runtimePlugin', () => {
require: jest.fn(),
readFileVm: jest.fn(),
},
};
});

const mockArgs = {
origin: {
Expand Down
17 changes: 17 additions & 0 deletions packages/node/src/__tests__/stratagies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {
fileSystemRunInContextStrategy,
httpEvalStrategy,
httpVmStrategy,
} from '../filesystem/stratagies';

describe('filesystem chunk loading strategies', () => {
test.each([fileSystemRunInContextStrategy, httpEvalStrategy, httpVmStrategy])(
'%p inlines webpack require access for toString runtime emission',
(strategyFn) => {
const source = strategyFn.toString();

expect(source).toContain("typeof __webpack_require__ === 'function'");
expect(source).not.toContain('getWebpackRequire(');
},
);
});
27 changes: 24 additions & 3 deletions packages/node/src/filesystem/stratagies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ export async function fileSystemRunInContextStrategy(
remotes: Remotes,
callback: CallbackFunction,
) {
const webpackRequire =
typeof __webpack_require__ === 'function' ? __webpack_require__ : undefined;
if (!webpackRequire) {
throw new Error(
'Unable to access __webpack_require__. Ensure this code runs inside a webpack-compatible runtime.',
);
}
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const filename = path.join(
__dirname,
rootOutputDir + __webpack_require__.u(chunkId),
rootOutputDir + webpackRequire.u(chunkId),
);
if (fs.existsSync(filename)) {
fs.readFile(filename, 'utf-8', (err: Error, content: string) => {
Expand Down Expand Up @@ -45,9 +52,16 @@ export async function httpEvalStrategy(
remotes: Remotes,
callback: CallbackFunction,
) {
const webpackRequire =
typeof __webpack_require__ === 'function' ? __webpack_require__ : undefined;
if (!webpackRequire) {
throw new Error(
'Unable to access __webpack_require__. Ensure this code runs inside a webpack-compatible runtime.',
);
}
let url;
try {
url = new URL(chunkName, __webpack_require__.p);
url = new URL(chunkName, webpackRequire.p);
} catch (e) {
console.error(
'module-federation: failed to construct absolute chunk path of',
Expand Down Expand Up @@ -102,6 +116,13 @@ export async function httpVmStrategy(
remotes: Remotes,
callback: CallbackFunction,
): Promise<void> {
const webpackRequire =
typeof __webpack_require__ === 'function' ? __webpack_require__ : undefined;
if (!webpackRequire) {
throw new Error(
'Unable to access __webpack_require__. Ensure this code runs inside a webpack-compatible runtime.',
);
}
const http = require('http') as typeof import('http');
const https = require('https') as typeof import('https');
const vm = require('vm') as typeof import('vm');
Expand All @@ -110,7 +131,7 @@ export async function httpVmStrategy(
const globalThisVal = new Function('return globalThis')();

try {
url = new URL(chunkName, __webpack_require__.p);
url = new URL(chunkName, webpackRequire.p);
} catch (e) {
console.error(
'module-federation: failed to construct absolute chunk path of',
Expand Down
53 changes: 28 additions & 25 deletions packages/node/src/runtimePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
ModuleFederationRuntimePlugin,
ModuleFederation,
} from '@module-federation/runtime';
import { getWebpackRequireOrThrow } from '@module-federation/sdk/bundler';
type WebpackRequire = {
(id: string): any;
u: (chunkId: string) => string;
Expand Down Expand Up @@ -35,9 +36,9 @@ type WebpackRequire = {
readFileVm?: (chunkId: string, promises: any[]) => void;
};
};

declare const __webpack_require__: WebpackRequire;
declare const __non_webpack_require__: (id: string) => any;
const getWebpackRequire = (): WebpackRequire =>
getWebpackRequireOrThrow() as unknown as WebpackRequire;

export const nodeRuntimeImportCache = new Map<string, Promise<any>>();

Expand Down Expand Up @@ -69,7 +70,8 @@ export function importNodeModule<T>(name: string): Promise<T> {
// Hoisted utility function to resolve file paths for chunks
export const resolveFile = (rootOutputDir: string, chunkId: string): string => {
const path = __non_webpack_require__('path');
return path.join(__dirname, rootOutputDir + __webpack_require__.u(chunkId));
const webpackRequire = getWebpackRequire();
return path.join(__dirname, rootOutputDir + webpackRequire.u(chunkId));
};

// Hoisted utility function to get remote entry from cache
Expand Down Expand Up @@ -185,8 +187,9 @@ export const resolveUrl = (
remoteName: string,
chunkName: string,
): URL | null => {
const webpackRequire = getWebpackRequire();
try {
return new URL(chunkName, __webpack_require__.p);
return new URL(chunkName, webpackRequire.p);
} catch {
const entryUrl =
returnFromCache(remoteName) || returnFromGlobalInstances(remoteName);
Expand All @@ -204,7 +207,7 @@ export const resolveUrl = (
lastSlashIndex >= 0 ? urlPath.substring(0, lastSlashIndex + 1) : '/';

// Get rootDir from webpack configuration
const rootDir = __webpack_require__.federation.rootOutputDir || '';
const rootDir = webpackRequire.federation.rootOutputDir || '';

// Use path.join to combine the paths properly while handling slashes
// Convert Windows-style paths to URL-style paths
Expand Down Expand Up @@ -240,10 +243,11 @@ export const installChunk = (
chunk: any,
installedChunks: { [key: string]: any },
): void => {
const webpackRequire = getWebpackRequire();
for (const moduleId in chunk.modules) {
__webpack_require__.m[moduleId] = chunk.modules[moduleId];
webpackRequire.m[moduleId] = chunk.modules[moduleId];
}
if (chunk.runtime) chunk.runtime(__webpack_require__);
if (chunk.runtime) chunk.runtime(webpackRequire);
for (const chunkId of chunk.ids) {
if (installedChunks[chunkId]) installedChunks[chunkId][0]();
installedChunks[chunkId] = 0;
Expand All @@ -261,23 +265,20 @@ export const deleteChunk = (

// Hoisted function to set up webpack script loader
export const setupScriptLoader = (): void => {
__webpack_require__.l = (
const webpackRequire = getWebpackRequire();
webpackRequire.l = (
url: string,
done: (res: any) => void,
key: string,
chunkId: string,
): void => {
if (!key || chunkId)
throw new Error(`__webpack_require__.l name is required for ${url}`);
__webpack_require__.federation.runtime
webpackRequire.federation.runtime
.loadScriptNode(url, { attrs: { globalName: key } })
.then((res) => {
const enhancedRemote =
__webpack_require__.federation.instance.initRawContainer(
key,
url,
res,
);
webpackRequire.federation.instance.initRawContainer(key, url, res);
new Function('return globalThis')()[key] = enhancedRemote;
done(enhancedRemote);
})
Expand All @@ -291,13 +292,14 @@ export const setupChunkHandler = (
args: any,
): ((chunkId: string, promises: any[]) => void) => {
return (chunkId: string, promises: any[]): void => {
const webpackRequire = getWebpackRequire();
let installedChunkData = installedChunks[chunkId];
if (installedChunkData !== 0) {
if (installedChunkData) {
promises.push(installedChunkData[2]);
} else {
const matcher = __webpack_require__.federation.chunkMatcher
? __webpack_require__.federation.chunkMatcher(chunkId)
const matcher = webpackRequire.federation.chunkMatcher
? webpackRequire.federation.chunkMatcher(chunkId)
: true;

if (matcher) {
Expand All @@ -310,7 +312,7 @@ export const setupChunkHandler = (
const filename =
typeof process !== 'undefined'
? resolveFile(
__webpack_require__.federation.rootOutputDir || '',
webpackRequire.federation.rootOutputDir || '',
chunkId,
)
: false;
Expand All @@ -319,7 +321,7 @@ export const setupChunkHandler = (
loadChunk(
'filesystem',
chunkId,
__webpack_require__.federation.rootOutputDir || '',
webpackRequire.federation.rootOutputDir || '',
(err, chunk) => {
if (err)
return deleteChunk(chunkId, installedChunks) && reject(err);
Expand All @@ -329,13 +331,13 @@ export const setupChunkHandler = (
args,
);
} else {
const chunkName = __webpack_require__.u(chunkId);
const chunkName = webpackRequire.u(chunkId);
const loadingStrategy =
typeof process === 'undefined' ? 'http-eval' : 'http-vm';
loadChunk(
loadingStrategy,
chunkName,
__webpack_require__.federation.initOptions.name,
webpackRequire.federation.initOptions.name,
(err, chunk) => {
if (err)
return deleteChunk(chunkId, installedChunks) && reject(err);
Expand All @@ -359,17 +361,18 @@ export const setupChunkHandler = (
export const setupWebpackRequirePatching = (
handle: (chunkId: string, promises: any[]) => void,
): void => {
if (__webpack_require__.f) {
if (__webpack_require__.f.require) {
const webpackRequire = getWebpackRequire();
if (webpackRequire.f) {
if (webpackRequire.f.require) {
console.warn(
'\x1b[33m%s\x1b[0m',
'CAUTION: build target is not set to "async-node", attempting to patch additional chunk handlers. This may not work',
);
__webpack_require__.f.require = handle;
webpackRequire.f.require = handle;
}

if (__webpack_require__.f.readFileVm) {
__webpack_require__.f.readFileVm = handle;
if (webpackRequire.f.readFileVm) {
webpackRequire.f.readFileVm = handle;
}
}
};
Expand Down
4 changes: 2 additions & 2 deletions packages/node/src/utils/hot-reload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getAllKnownRemotes } from './flush-chunks';
import crypto from 'crypto';
import helpers from '@module-federation/runtime/helpers';
import path from 'path';
import { getWebpackRequire } from '@module-federation/sdk/bundler';

declare global {
var mfHashMap: Record<string, string> | undefined;
Expand Down Expand Up @@ -156,8 +157,7 @@ export const performReload = async (
delete gs[i.name];
}
});
//@ts-ignore
__webpack_require__?.federation?.instance?.moduleCache?.clear();
(getWebpackRequire() as any)?.federation?.instance?.moduleCache?.clear();
helpers.global.resetFederationGlobalInfo();
globalThis.moduleGraphDirty = false;
globalThis.mfHashMap = {};
Expand Down
Loading
Loading