Skip to content

Commit d751ad0

Browse files
fix(lynx): embed native main-thread container
1 parent 022063b commit d751ad0

6 files changed

Lines changed: 55 additions & 7 deletions

File tree

.changeset/tiny-lynxes-federate.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ plugin releases that preserve automatic public paths, expose retryable lazy
1616
loading, and omit assetless remote-only chunk groups.
1717
Bootstrap the ReactLynx lazy-bundle loader before federation async startup so
1818
initial shared chunks use FetchBundle instead of the legacy component loader.
19+
Embed the paired container entry in native split remotes so fetched main-thread
20+
chunks install their snapshot factories before React commits them.
1921
Accept the cache-events plugin 0.2 line used by Rspeedy 0.16.
2022

2123
Resolve manifest `publicPath: 'auto'` from the fetched response URL so

apps/lynx-module-federation-demo/test/native-artifacts.mjs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,17 @@ assert.equal(standaloneTemplate['app-type'], 'card');
9494
assert.equal(standaloneTemplate['engine-version'], '3.9');
9595
assert.equal(remoteTemplate['app-type'], 'DynamicComponent');
9696
assert.equal(remoteTemplate['engine-version'], '3.9');
97-
assert.deepEqual(Object.keys(remoteTemplate['custom-sections']), ['catalog']);
97+
assert.deepEqual(Object.keys(remoteTemplate['custom-sections']).sort(), [
98+
'catalog',
99+
'catalog__main-thread',
100+
]);
101+
const remoteMainThreadEntry = Buffer.from(
102+
remoteTemplate['custom-sections']['catalog__main-thread'],
103+
);
104+
assert.ok(
105+
remoteMainThreadEntry.includes(Buffer.from('processEvalResultByHost')),
106+
'native remote has no main-thread chunk installer',
107+
);
98108
const hostBackgroundSource = hostTemplate['background-thread-script']
99109
.map(({ content }) => content)
100110
.join('\n');

packages/lynx/src/plugin.remoteBundle.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ describe('pluginLynxModuleFederation remote bundles', () => {
393393
expect(encoder.options).toMatchObject({
394394
bundleFileName: 'catalog-native.lynx.bundle',
395395
engineVersion: '3.6',
396-
entryAssets: ['catalog.js'],
396+
entryAssets: ['catalog.js', 'catalog__main-thread.js'],
397397
stateStore: { for: expect.any(Function) },
398398
});
399399
expect(encoder.options.encode).toBeTypeOf('function');

packages/lynx/src/remoteBundle.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ type RemoteBundlePlan = RemoteBundlePlanBase &
5050
| {
5151
chunking: 'split';
5252
mainThreadChunkPrefix: string;
53-
mainThreadEntry: undefined;
53+
mainThreadEntry: string;
5454
mode: 'native-split';
5555
}
5656
| {
@@ -122,8 +122,8 @@ const normalizeRemoteBundlePlan = (
122122
includedChunkPrefixes: [backgroundChunkPrefix, mainThreadChunkPrefix],
123123
mainThreadChunkPrefix,
124124
};
125+
const mainThreadEntry = `${outputName}__main-thread.js`;
125126
if (remoteBundle.target === 'web') {
126-
const mainThreadEntry = `${outputName}__main-thread.js`;
127127
return {
128128
...splitPlan,
129129
entryAssets: [backgroundEntry, mainThreadEntry],
@@ -134,8 +134,8 @@ const normalizeRemoteBundlePlan = (
134134

135135
return {
136136
...splitPlan,
137-
entryAssets: [backgroundEntry],
138-
mainThreadEntry: undefined,
137+
entryAssets: [backgroundEntry, mainThreadEntry],
138+
mainThreadEntry,
139139
mode: 'native-split',
140140
};
141141
};

packages/lynx/src/runtimeEntryLoader.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,17 @@ const preparePairedMainThreadEntry = (
7171
entry,
7272
sectionPath: `${entryGlobalName}__main-thread`,
7373
},
74-
() => resolve(),
74+
(result: unknown) => {
75+
if (result === false) {
76+
reject(
77+
new Error(
78+
`Lynx remote bundle "${entryGlobalName}" did not expose its paired main-thread entry.`,
79+
),
80+
);
81+
return;
82+
}
83+
resolve();
84+
},
7585
);
7686
} catch (error) {
7787
reject(error);

packages/lynx/src/runtimePlugin.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,32 @@ describe('lynxRuntimePlugin entry loading', () => {
236236
expect(registry.size).toBe(0);
237237
});
238238

239+
it('rejects a remote whose paired main-thread entry is missing', async () => {
240+
const registry = new Map<string, string>();
241+
(
242+
globalThis as unknown as Record<
243+
PropertyKey,
244+
Map<string, string> | undefined
245+
>
246+
)[LYNX_BUNDLE_REGISTRY] = registry;
247+
setLynx({
248+
fetchBundle: async () => ({ code: 0, url: 'lynx-cache://remote' }),
249+
getNativeApp: () => ({
250+
callLepusMethod: (
251+
_name: string,
252+
_payload: unknown,
253+
callback: (result: boolean) => void,
254+
) => callback(false),
255+
}),
256+
loadScript: () => createContainer(),
257+
});
258+
259+
await expect(
260+
loadEntry(lynxRuntimePlugin(), bundleRemoteInfo),
261+
).rejects.toThrow('did not expose its paired main-thread entry');
262+
expect(registry.size).toBe(0);
263+
});
264+
239265
it('loads bundle entries from the main-thread section', async () => {
240266
const container = createContainer();
241267
const globalRecord = globalThis as unknown as Record<string, unknown>;

0 commit comments

Comments
 (0)