Skip to content

Commit 7ee1b83

Browse files
jbromaclaude
andauthored
fix: add RN polyfills as runtime modules for correct execution order (#1340)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4023cc7 commit 7ee1b83

20 files changed

Lines changed: 4479 additions & 223 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@callstack/repack": patch
3+
---
4+
5+
Fix polyfill execution order when using Module Federation by adding a `PolyfillsRuntimeModule` to `NativeEntryPlugin`. Polyfills are now required from a runtime module that runs before Module Federation's startup wrapper, guaranteeing they execute before MF startup.

apps/tester-app/ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
PODS:
22
- boost (1.84.0)
3-
- callstack-repack (5.2.2):
3+
- callstack-repack (5.2.3):
44
- boost
55
- DoubleConversion
66
- fast_float
@@ -2930,7 +2930,7 @@ EXTERNAL SOURCES:
29302930

29312931
SPEC CHECKSUMS:
29322932
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
2933-
callstack-repack: c874fe60c49dcf3067bca0627b7ace673589737c
2933+
callstack-repack: 15b29626cee2b659cd3f9afa4e8c33b1d42f5c59
29342934
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
29352935
fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6
29362936
FBLazyVector: a867936a67af0d09c37935a1b900a1a3c795b6d1
@@ -3011,7 +3011,7 @@ SPEC CHECKSUMS:
30113011
RNWorklets: 20451b83d42e7509f43599b405993e57e3a038af
30123012
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
30133013
SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6
3014-
Yoga: b01392348aeea02064c21a2762a42893d82b60a7
3014+
Yoga: 00013dd9cde63a2d98e8002fcc4f5ddb66c10782
30153015

30163016
PODFILE CHECKSUM: 6d7cbe03444d5e87210979fb32a0eca299d758fe
30173017

apps/tester-federation-v2/ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
PODS:
22
- boost (1.84.0)
3-
- callstack-repack (5.2.0):
3+
- callstack-repack (5.2.3):
44
- boost
55
- DoubleConversion
66
- fast_float
@@ -2713,7 +2713,7 @@ EXTERNAL SOURCES:
27132713

27142714
SPEC CHECKSUMS:
27152715
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
2716-
callstack-repack: 9c91d2c48b139e38919c656474f43ab0494b4c21
2716+
callstack-repack: 15b29626cee2b659cd3f9afa4e8c33b1d42f5c59
27172717
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
27182718
fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6
27192719
FBLazyVector: a867936a67af0d09c37935a1b900a1a3c795b6d1
@@ -2791,7 +2791,7 @@ SPEC CHECKSUMS:
27912791
RNScreens: 5c7f22b19ee2e900e5de2c578471aeb153d1e502
27922792
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
27932793
SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6
2794-
Yoga: b01392348aeea02064c21a2762a42893d82b60a7
2794+
Yoga: 00013dd9cde63a2d98e8002fcc4f5ddb66c10782
27952795

27962796
PODFILE CHECKSUM: 3d5c18eefbf70d38fbbfe81a262195cadac1f5dd
27972797

apps/tester-federation/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
PODS:
22
- boost (1.84.0)
3-
- callstack-repack (5.2.0):
3+
- callstack-repack (5.2.3):
44
- boost
55
- DoubleConversion
66
- fast_float
@@ -2713,7 +2713,7 @@ EXTERNAL SOURCES:
27132713

27142714
SPEC CHECKSUMS:
27152715
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
2716-
callstack-repack: 9c91d2c48b139e38919c656474f43ab0494b4c21
2716+
callstack-repack: 15b29626cee2b659cd3f9afa4e8c33b1d42f5c59
27172717
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
27182718
fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6
27192719
FBLazyVector: a867936a67af0d09c37935a1b900a1a3c795b6d1

packages/repack/src/plugins/NativeEntryPlugin.ts renamed to packages/repack/src/plugins/NativeEntryPlugin/NativeEntryPlugin.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import path from 'node:path';
22
import type { ResolveAlias, Compiler as RspackCompiler } from '@rspack/core';
33
import type { Compiler as WebpackCompiler } from 'webpack';
4-
import { isRspackCompiler, moveElementBefore } from '../helpers/index.js';
4+
import { isRspackCompiler, moveElementBefore } from '../../helpers/index.js';
5+
import { makePolyfillsRuntimeModule } from './PolyfillsRuntimeModule.js';
56

67
export interface NativeEntryPluginConfig {
78
/**
@@ -55,18 +56,37 @@ export class NativeEntryPlugin {
5556
path.join(reactNativePath, 'Libraries/Core/InitializeCore.js');
5657

5758
const initializeScriptManagerPath = require.resolve(
58-
'../modules/InitializeScriptManager.js'
59+
'../../modules/InitializeScriptManager.js'
5960
);
6061

61-
const includeModulesPath = require.resolve('../modules/IncludeModules.js');
62+
const includeModulesPath = require.resolve(
63+
'../../modules/IncludeModules.js'
64+
);
65+
66+
const polyfillPaths = getReactNativePolyfills();
6267

6368
const nativeEntries = [
64-
...getReactNativePolyfills(),
69+
...polyfillPaths,
6570
initializeCorePath,
6671
initializeScriptManagerPath,
6772
includeModulesPath,
6873
];
6974

75+
// Polyfills are entry modules (processed by loaders), but we also require them
76+
// from a runtime module to guarantee they execute before Module Federation's
77+
// startup wrapper. The duplicate require during startup is a cache hit.
78+
compiler.hooks.compilation.tap('RepackNativeEntryPlugin', (compilation) => {
79+
compilation.hooks.additionalTreeRuntimeRequirements.tap(
80+
'RepackNativeEntryPlugin',
81+
(chunk) => {
82+
compilation.addRuntimeModule(
83+
chunk,
84+
makePolyfillsRuntimeModule(compiler, { polyfillPaths })
85+
);
86+
}
87+
);
88+
});
89+
7090
compiler.hooks.entryOption.tap(
7191
{ name: 'RepackNativeEntryPlugin', before: 'RepackDevelopmentPlugin' },
7292
(_, entry) => {
@@ -76,14 +96,12 @@ export class NativeEntryPlugin {
7696
);
7797
}
7898

99+
// add native entries (including polyfills) to each declared entry point
79100
Object.keys(entry).forEach((entryName) => {
80-
// runtime property defines the chunk name, otherwise it defaults to the entry key
81101
const entryChunkName = entry[entryName].runtime || entryName;
82-
83-
// add native entries to all declared entry points
84102
for (const nativeEntry of nativeEntries) {
85103
new compiler.webpack.EntryPlugin(compiler.context, nativeEntry, {
86-
name: entryChunkName, // prepends the entry to the chunk of specified name
104+
name: entryChunkName,
87105
}).apply(compiler);
88106
}
89107
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type {
2+
Compiler,
3+
NormalModule,
4+
RuntimeModule as RuntimeModuleType,
5+
} from '@rspack/core';
6+
7+
interface PolyfillsRuntimeModuleConfig {
8+
polyfillPaths: string[];
9+
}
10+
11+
/**
12+
* Runtime module that requires polyfill entry modules before the startup
13+
* function (__webpack_require__.x), ensuring they run before Module Federation's
14+
* embed_federation_runtime wrapper. Polyfills go through the normal loader
15+
* pipeline as entry modules; this module only controls execution timing.
16+
*/
17+
export const makePolyfillsRuntimeModule = (
18+
compiler: Compiler,
19+
moduleConfig: PolyfillsRuntimeModuleConfig
20+
): RuntimeModuleType => {
21+
const Template = compiler.webpack.Template;
22+
const RuntimeModule = compiler.webpack.RuntimeModule;
23+
const RuntimeGlobals = compiler.webpack.RuntimeGlobals;
24+
25+
const PolyfillsRuntimeModule = class extends RuntimeModule {
26+
constructor(private config: PolyfillsRuntimeModuleConfig) {
27+
super('repack/polyfills', RuntimeModule.STAGE_BASIC);
28+
}
29+
30+
generate() {
31+
const compilation = this.compilation!;
32+
const chunk = this.chunk!;
33+
const chunkGraph = compilation.chunkGraph;
34+
const chunkModules = new Set(chunkGraph.getChunkModules(chunk));
35+
36+
const requireCalls = this.config.polyfillPaths
37+
.map((polyfillPath) => {
38+
for (const mod of compilation.modules) {
39+
if ((mod as NormalModule).resource === polyfillPath) {
40+
if (!chunkModules.has(mod)) return null;
41+
const moduleId = chunkGraph.getModuleId(mod);
42+
return `${RuntimeGlobals.require}(${JSON.stringify(moduleId)});`;
43+
}
44+
}
45+
return null;
46+
})
47+
.filter(Boolean) as string[];
48+
49+
return Template.asString(requireCalls);
50+
}
51+
};
52+
53+
return new PolyfillsRuntimeModule(moduleConfig);
54+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { NativeEntryPlugin } from './NativeEntryPlugin.js';
2+
export type { NativeEntryPluginConfig } from './NativeEntryPlugin.js';

packages/repack/src/plugins/RepackPlugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Compiler as WebpackCompiler } from 'webpack';
33
import { BabelPlugin } from './BabelPlugin.js';
44
import { DevelopmentPlugin } from './DevelopmentPlugin.js';
55
import { LoggerPlugin, type LoggerPluginConfig } from './LoggerPlugin.js';
6-
import { NativeEntryPlugin } from './NativeEntryPlugin.js';
6+
import { NativeEntryPlugin } from './NativeEntryPlugin/index.js';
77
import { OutputPlugin, type OutputPluginConfig } from './OutputPlugin/index.js';
88
import { RepackTargetPlugin } from './RepackTargetPlugin/index.js';
99
import { SourceMapPlugin } from './SourceMapPlugin.js';

packages/repack/src/plugins/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export * from './ModuleFederationPluginV1.js';
99
export * from './ModuleFederationPluginV2.js';
1010
export * from './CodeSigningPlugin/index.js';
1111
export * from './HermesBytecodePlugin/index.js';
12+
export * from './NativeEntryPlugin/index.js';

0 commit comments

Comments
 (0)