Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
3b6d364
feat(lynx): add native module federation support
ScriptedAlchemy Jul 16, 2026
a045ec0
fix(ci): gate Lynx E2E suite
ScriptedAlchemy Jul 16, 2026
59e4f5e
feat(lynx): add standalone iOS application
ScriptedAlchemy Jul 16, 2026
c67e601
fix(lynx): harden native iOS demo
ScriptedAlchemy Jul 16, 2026
a1b7251
fix(lynx): tighten iOS lifecycle tests
ScriptedAlchemy Jul 16, 2026
fd7e78e
fix(lynx): validate iOS device origin
ScriptedAlchemy Jul 16, 2026
a850822
fix(lynx): bound iOS path cache
ScriptedAlchemy Jul 16, 2026
7087042
fix(lynx): stabilize iOS readiness probe
ScriptedAlchemy Jul 16, 2026
36a52bd
ci(lynx): extend native iOS timeout
ScriptedAlchemy Jul 16, 2026
5fce98b
fix(lynx): pair native remote UI layers
ScriptedAlchemy Jul 16, 2026
65f525a
fix(lynx): preserve native lazy chunk semantics
ScriptedAlchemy Jul 16, 2026
1f1358e
fix(lynx): harden native federation boundaries
ScriptedAlchemy Jul 16, 2026
2b546e7
fix(lynx): inject lazy identity before runtime wrapper
ScriptedAlchemy Jul 16, 2026
1bafb73
fix(lynx): isolate native sharing by realm
ScriptedAlchemy Jul 16, 2026
2292f0b
feat(lynx): complete standalone federation support
ScriptedAlchemy Jul 17, 2026
49d996d
fix(lynx): avoid backtracking in output names
ScriptedAlchemy Jul 17, 2026
c4cb6ec
fix(lynx): use absolute native asset origin
ScriptedAlchemy Jul 17, 2026
3fd8670
ci(lynx): cache native dependencies
ScriptedAlchemy Jul 17, 2026
f855940
ci(lynx): invalidate cache on pod config
ScriptedAlchemy Jul 17, 2026
b972a77
ci(lynx): disable xcode indexing
ScriptedAlchemy Jul 17, 2026
b3a0f10
ci(lynx): consolidate ios release tests
ScriptedAlchemy Jul 18, 2026
a7ea37f
ci(lynx): retrigger iOS e2e
ScriptedAlchemy Jul 18, 2026
94f3f42
ci(lynx): retain release test diagnostics
ScriptedAlchemy Jul 18, 2026
4fb17c2
ci(metro): constrain android emulator partition
ScriptedAlchemy Jul 18, 2026
2d05685
docs(lynx): define architecture refactor
ScriptedAlchemy Jul 20, 2026
76b37ab
docs(lynx): plan architecture refactor
ScriptedAlchemy Jul 20, 2026
edd28cf
fix(runtime): make manifest URL caching atomic
ScriptedAlchemy Jul 20, 2026
428f8bb
fix(runtime): guard stale manifest cache writes
ScriptedAlchemy Jul 21, 2026
7db9fc7
Merge remote-tracking branch 'origin/main' into feat/lynx-federation
ScriptedAlchemy Jul 21, 2026
b656347
refactor(lynx): configure cache events through rspeedy
ScriptedAlchemy Jul 21, 2026
789db1c
refactor(lynx): isolate remote bundle compilation state
ScriptedAlchemy Jul 21, 2026
7cca67a
refactor(lynx): isolate lazy chunk load settlement
ScriptedAlchemy Jul 21, 2026
ca0409d
refactor(lynx): simplify lazy load state
ScriptedAlchemy Jul 21, 2026
8fbc9ba
refactor(lynx-demo): serialize remote loading
ScriptedAlchemy Jul 21, 2026
fcc61db
refactor(lynx-demo): isolate artifact test output
ScriptedAlchemy Jul 21, 2026
41b4d37
refactor(lynx-ios): isolate resource loading boundaries
ScriptedAlchemy Jul 21, 2026
8539062
docs(lynx): document rspeedy compatibility boundary
ScriptedAlchemy Jul 21, 2026
8c2a06c
Merge remote-tracking branch 'origin/main' into feat/lynx-federation
ScriptedAlchemy Jul 21, 2026
8402fdf
test(lynx): simplify bundle error coverage
ScriptedAlchemy Jul 21, 2026
10e9040
fix(lynx): harden artifact server paths
ScriptedAlchemy Jul 21, 2026
3b2b052
docs(lynx): describe artifact server boundary
ScriptedAlchemy Jul 21, 2026
f77ac7a
Merge remote-tracking branch 'origin/main' into feat/lynx-federation
ScriptedAlchemy Jul 21, 2026
f6b691f
test(lynx-ios): make resource boundaries deterministic
ScriptedAlchemy Jul 21, 2026
818406a
Merge remote-tracking branch 'origin/main' into feat/lynx-federation
ScriptedAlchemy Jul 21, 2026
5d956d1
refactor(lynx): close resource lifecycle gaps
ScriptedAlchemy Jul 21, 2026
ac671ad
chore(lynx): finalize package boundaries
ScriptedAlchemy Jul 21, 2026
298d326
feat(lynx): complete lazy federation integration
ScriptedAlchemy Jul 22, 2026
022dc5e
Merge remote-tracking branch 'origin/main' into feat/lynx-federation
ScriptedAlchemy Jul 22, 2026
be79436
ci(lynx): speed up iOS federation validation
ScriptedAlchemy Jul 22, 2026
b223844
ci(lynx): harden iOS cache seeding
ScriptedAlchemy Jul 22, 2026
0f1ae1e
fix(lynx): gate UI on background startup
ScriptedAlchemy Jul 22, 2026
65be391
ci(lynx): persist iOS dependencies before tests
ScriptedAlchemy Jul 22, 2026
29c9275
fix(lynx): synchronize first-screen handoff
ScriptedAlchemy Jul 23, 2026
d441951
ci(lynx): cache iOS build atomically
ScriptedAlchemy Jul 23, 2026
682af30
fix(lynx): release manual first-screen handoff
ScriptedAlchemy Jul 23, 2026
52e07ad
fix(lynx-ci): cache generated iOS workspace
ScriptedAlchemy Jul 23, 2026
a38efd0
fix(lynx): use built-in js-ready handoff
ScriptedAlchemy Jul 23, 2026
9081f2d
fix(lynx): complete public lazy bundle transport
ScriptedAlchemy Jul 23, 2026
42c3605
test(lynx): cover host lazy bundle identity
ScriptedAlchemy Jul 23, 2026
e35a41c
test(lynx): capture iOS simulator diagnostics
ScriptedAlchemy Jul 23, 2026
9ba07b1
fix(lynx): execute native lazy background chunks
ScriptedAlchemy Jul 23, 2026
dc76172
fix(lynx): bootstrap lazy chunks before async startup
ScriptedAlchemy Jul 23, 2026
adf8abc
docs(lynx): document startup loader requirement
ScriptedAlchemy Jul 23, 2026
022063b
docs(lynx): clarify automatic React bootstrap
ScriptedAlchemy Jul 23, 2026
d751ad0
fix(lynx): embed native main-thread container
ScriptedAlchemy Jul 23, 2026
9d76f69
docs(lynx): note paired entry validation
ScriptedAlchemy Jul 23, 2026
6ed7e5e
docs(lynx): describe paired native container
ScriptedAlchemy Jul 23, 2026
7cacc66
fix(lynx): emit main-thread container before runtime wrapper
ScriptedAlchemy Jul 23, 2026
3a06a6b
test(lynx): model runtime wrapper stage
ScriptedAlchemy Jul 23, 2026
946b6a5
ci: retry pkg preview registration race
ScriptedAlchemy Jul 23, 2026
25894cf
fix(lynx): encode external bundle mode
ScriptedAlchemy Jul 23, 2026
021a221
fix(lynx): normalize split bundle public paths
ScriptedAlchemy Jul 23, 2026
7e0f79f
test(lynx): use fresh lynx preview packages
ScriptedAlchemy Jul 24, 2026
626f5b8
chore: merge main into feat/lynx-federation
ScriptedAlchemy Aug 6, 2026
dd26568
chore: merge main into feat/lynx-federation
ScriptedAlchemy Aug 7, 2026
3d92cf7
chore: merge main into feat/lynx-federation
ScriptedAlchemy Aug 11, 2026
87b5035
chore: merge latest main into feat/lynx-federation
ScriptedAlchemy Aug 11, 2026
66c1c76
chore: merge main into feat/lynx-federation
ScriptedAlchemy Aug 14, 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
8 changes: 8 additions & 0 deletions .changeset/calm-tasms-federate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@module-federation/runtime-core': patch
'@module-federation/webpack-bundler-runtime': patch
---

Avoid logical assignment syntax and use a standards-safe own-property check in
embedded federation runtime initializers so ES2019 and Lynx TASM consumers can
parse the generated runtime.
32 changes: 32 additions & 0 deletions .changeset/tiny-lynxes-federate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
'@module-federation/lynx': minor
'@module-federation/runtime-core': patch
'@module-federation/sdk': patch
---

Add an Rspeedy build adapter plus background and Lynx for Web main-thread
transports. Dual-realm remotes publish a standard Module Federation manifest
whose remote entry resolves to one HTTP-loadable external `.lynx.bundle`.
Compiled federated `import()` calls skip Lynx's local loader for remote-only
chunk IDs while preserving normal local JavaScript chunk loading.

Load non-eager host, remote-exposure, and descendant chunks through Lynx's
public lazy-bundle API. Split federation requires the Lynx Web Core and template
plugin releases that preserve automatic public paths, expose retryable lazy
loading, and omit assetless remote-only chunk groups.
Bootstrap the ReactLynx lazy-bundle loader before federation async startup so
initial shared chunks use FetchBundle instead of the legacy component loader.
Embed the paired container entry in native split remotes so fetched main-thread
chunks install their snapshot factories before React commits them, and reject
remote entries that omit the paired section. Emit that entry before the
background runtime wrapper so native main-thread evaluation does not require
the background-only `tt` module loader.
Accept the cache-events plugin 0.2 line used by Rspeedy 0.16.

Resolve manifest `publicPath: 'auto'` from the fetched response URL so
root-relative manifest entries remain portable in browser-like runtimes.
Normalize Lynx split-bundle public paths so relative, root-relative, absolute,
and protocol-relative asset prefixes work with or without boundary slashes.
Self-label generated Lynx external bundles as external/non-lazy in encoded
bundle config so Web Core can prefer producer metadata over runtime caller
fallbacks.
6 changes: 6 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ jobs:
uses: ./.github/workflows/e2e-runtime.yml
secrets: inherit

e2e-lynx:
needs: checkout-install
if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).lynx }}
uses: ./.github/workflows/e2e-lynx.yml
Comment thread
ScriptedAlchemy marked this conversation as resolved.
secrets: inherit

e2e-manifest:
needs: checkout-install
if: ${{ fromJSON(needs.checkout-install.outputs.e2e_suites).manifest }}
Expand Down
246 changes: 246 additions & 0 deletions .github/workflows/e2e-lynx.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
# .github/workflows/e2e-lynx.yml
name: E2E Test for Lynx Module Federation

on:
workflow_call:

jobs:
e2e-lynx-native:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout Repository
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0

- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4

- name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'

- name: Export SKIP_DEVTOOLS_POSTINSTALL
run: echo "SKIP_DEVTOOLS_POSTINSTALL=true" >> "$GITHUB_ENV"

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Validate Lynx and Android CI policy
run: pnpm --filter lynx-module-federation-demo run test:ci-policy

- name: Restore Turborepo cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.ref_name }}-
${{ runner.os }}-turbo-

- name: Build shared packages
run: pnpm run build:packages

- name: Test Lynx federation compiler and transport
run: pnpm --filter @module-federation/lynx test

- name: Build and validate native Lynx artifacts
run: pnpm --filter lynx-module-federation-demo run e2e:native:ci

- name: Validate standalone iOS project policy
run: pnpm --filter lynx-module-federation-demo run test:ios-project

- name: Upload native artifacts for iOS
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: lynx-native-ios-input
path: |
apps/lynx-module-federation-demo/dist/catalog-native/
apps/lynx-module-federation-demo/dist/host-native/
apps/lynx-module-federation-demo/dist/remote-native/
if-no-files-found: error
retention-days: 1

- name: Upload native failure artifacts
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: lynx-native-e2e-failure
path: |
apps/lynx-module-federation-demo/dist/catalog-native/
apps/lynx-module-federation-demo/dist/host-native/
apps/lynx-module-federation-demo/dist/remote-native/
if-no-files-found: ignore
retention-days: 7

e2e-lynx-web:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout Repository
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0

- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4

- name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: '24'
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'

- name: Export SKIP_DEVTOOLS_POSTINSTALL
run: echo "SKIP_DEVTOOLS_POSTINSTALL=true" >> "$GITHUB_ENV"

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Restore Turborepo cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.ref_name }}-
${{ runner.os }}-turbo-

- name: Build shared packages
run: pnpm run build:packages

- name: Install Playwright Chromium
run: pnpm --filter lynx-module-federation-demo exec playwright install --with-deps chromium

- name: Run real Lynx for Web E2E
run: pnpm --filter lynx-module-federation-demo run e2e:web:ci

- name: Upload Web E2E failure artifacts
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: lynx-module-federation-e2e-failure
path: |
apps/lynx-module-federation-demo/test/real-web/artifacts/
apps/lynx-module-federation-demo/dist/catalog-web/
apps/lynx-module-federation-demo/dist/host-web/
apps/lynx-module-federation-demo/dist/remote-web/
if-no-files-found: ignore
retention-days: 7

e2e-lynx-ios:
needs: e2e-lynx-native
runs-on: macos-15
timeout-minutes: 120
env:
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
NO_COLOR: 1
RUBY_VERSION: 3.4.9
steps:
- name: Checkout Repository
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 1
sparse-checkout: apps/lynx-module-federation-demo

- name: Setup Node.js 24
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: '24'

- name: Setup Ruby
uses: ruby/setup-ruby@97ecb7b512899eb71ab1bf2310a624c6f1589ac6 # v1
with:
ruby-version: ${{ env.RUBY_VERSION }}

- name: Restore iOS build cache
id: ios-build-cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cocoapods/repos/trunk
~/Library/Caches/CocoaPods
apps/lynx-module-federation-demo/ios/Pods
apps/lynx-module-federation-demo/ios/OrbitControl.xcworkspace
apps/lynx-module-federation-demo/ios/vendor/bundle
apps/lynx-module-federation-demo/ios/build/DerivedData/Build
apps/lynx-module-federation-demo/ios/build/DerivedData/ModuleCache.noindex
apps/lynx-module-federation-demo/ios/build/DerivedData/SDKStatCaches.noindex
key: ${{ runner.os }}-${{ runner.arch }}-lynx-ios-build-v3-xcode-16.4-ruby-${{ env.RUBY_VERSION }}-${{ hashFiles('apps/lynx-module-federation-demo/ios/Gemfile', 'apps/lynx-module-federation-demo/ios/Gemfile.lock', 'apps/lynx-module-federation-demo/ios/Podfile', 'apps/lynx-module-federation-demo/ios/Podfile.lock') }}-${{ hashFiles('apps/lynx-module-federation-demo/ios/OrbitControl.xcodeproj/project.pbxproj') }}

- name: Download native artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: lynx-native-ios-input
path: apps/lynx-module-federation-demo/dist

- name: Embed native host bundle
run: node apps/lynx-module-federation-demo/scripts/sync-ios-bundle.mjs

- name: Install iOS dependencies
if: steps.ios-build-cache.outputs.cache-hit != 'true'
working-directory: apps/lynx-module-federation-demo/ios
run: |
bundle config set path vendor/bundle
bundle install --jobs 4 --retry 3
bundle exec pod install --deployment

- name: Validate cached iOS dependencies
if: steps.ios-build-cache.outputs.cache-hit == 'true'
working-directory: apps/lynx-module-federation-demo/ios
run: |
bundle config set path vendor/bundle
bundle check
cmp Podfile.lock Pods/Manifest.lock
test -f OrbitControl.xcworkspace/contents.xcworkspacedata

- name: Run standalone iOS federation E2E
run: node apps/lynx-module-federation-demo/test/ios/run.mjs

- name: Save iOS build cache
if: >-
always() &&
steps.ios-build-cache.outputs.cache-hit != 'true' &&
hashFiles('apps/lynx-module-federation-demo/ios/build/DerivedData/Build/Products/Release-iphonesimulator/OrbitControl.app/OrbitControl') != ''
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cocoapods/repos/trunk
~/Library/Caches/CocoaPods
apps/lynx-module-federation-demo/ios/Pods
apps/lynx-module-federation-demo/ios/OrbitControl.xcworkspace
apps/lynx-module-federation-demo/ios/vendor/bundle
apps/lynx-module-federation-demo/ios/build/DerivedData/Build
apps/lynx-module-federation-demo/ios/build/DerivedData/ModuleCache.noindex
apps/lynx-module-federation-demo/ios/build/DerivedData/SDKStatCaches.noindex
key: ${{ runner.os }}-${{ runner.arch }}-lynx-ios-build-v3-xcode-16.4-ruby-${{ env.RUBY_VERSION }}-${{ hashFiles('apps/lynx-module-federation-demo/ios/Gemfile', 'apps/lynx-module-federation-demo/ios/Gemfile.lock', 'apps/lynx-module-federation-demo/ios/Podfile', 'apps/lynx-module-federation-demo/ios/Podfile.lock') }}-${{ hashFiles('apps/lynx-module-federation-demo/ios/OrbitControl.xcodeproj/project.pbxproj') }}

- name: Upload iOS E2E evidence
if: success()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: lynx-ios-e2e
path: |
apps/lynx-module-federation-demo/ios/build/orbit-control.png
apps/lynx-module-federation-demo/ios/build/requests.json
apps/lynx-module-federation-demo/ios/build/simulator.log
if-no-files-found: error
retention-days: 7

- name: Upload iOS E2E failure artifacts
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: lynx-ios-e2e-failure
path: |
apps/lynx-module-federation-demo/ios/build/OrbitControl-Release.xcresult/
apps/lynx-module-federation-demo/ios/build/orbit-control.png
apps/lynx-module-federation-demo/ios/build/requests.json
apps/lynx-module-federation-demo/ios/build/simulator.log
if-no-files-found: ignore
retention-days: 7
7 changes: 4 additions & 3 deletions .github/workflows/e2e-metro.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
ANDROID_EMULATOR_API_LEVEL: 28
ANDROID_EMULATOR_TARGET: default
ANDROID_EMULATOR_ARCH: x86_64
ANDROID_EMULATOR_DISK_SPACE: 1024M
ANDROID_EMULATOR_PARTITION_SIZE_MB: 1024
ANDROID_EMULATOR_RAM_SIZE: 256M
ANDROID_EMULATOR_HEAP_SIZE: 256M
ANDROID_EMULATOR_BOOT_TIMEOUT: 2700
Expand Down Expand Up @@ -110,11 +110,12 @@ jobs:
arch: ${{ env.ANDROID_EMULATOR_ARCH }}
ram-size: ${{ env.ANDROID_EMULATOR_RAM_SIZE }}
heap-size: ${{ env.ANDROID_EMULATOR_HEAP_SIZE }}
disk-size: ${{ env.ANDROID_EMULATOR_DISK_SPACE }}
disk-size: ${{ env.ANDROID_EMULATOR_PARTITION_SIZE_MB }}M
emulator-boot-timeout: ${{ env.ANDROID_EMULATOR_BOOT_TIMEOUT }}
force-avd-creation: false
disable-animations: true
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
# Emulator 36.6 can ignore the AVD disk-size config unless this is explicit.
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -partition-size ${{ env.ANDROID_EMULATOR_PARTITION_SIZE_MB }}
script: |
node tools/scripts/run-metro-e2e.mjs --platform=android --appName=${{ env.METRO_APP_NAME }} --skip-rock-cache-auth

Expand Down
12 changes: 12 additions & 0 deletions apps/lynx-module-federation-demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
dist/
.lynx-e2e-*/
.turbo/
test/real-web/artifacts/
ios/.bundle/
ios/Pods/
ios/OrbitControl.xcworkspace/
ios/Resources/main.lynx.bundle
ios/Resources/host-native/
ios/Resources/static/
ios/build/
ios/vendor/bundle/
Loading
Loading