Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a758fb0
feat: add lazy compilation support to Rspack
jbroma Jan 21, 2026
083fd64
chore: update podfile lock
jbroma Jan 21, 2026
8158eeb
fix: clear stale asset from asset cache
jbroma Jan 21, 2026
a48e305
Merge remote-tracking branch 'origin/main' into feat/lazy-compilation
jbroma Jan 23, 2026
f015b78
Merge remote-tracking branch 'origin/main' into feat/lazy-compilation
jbroma Feb 6, 2026
63166bb
feat: rework to support persistent cache
jbroma Feb 6, 2026
b7df44e
test: add tests for lazy compilation
jbroma Feb 6, 2026
eca466e
test: remove redundant case
jbroma Feb 7, 2026
4bdbf68
Merge remote-tracking branch 'origin/main' into feat/lazy-compilation
jbroma Jul 29, 2026
dc5f3e2
chore: refresh Podfile locks
jbroma Jul 29, 2026
f04e61f
fix: propagate Rspack shutdown errors
jbroma Jul 29, 2026
a0fc330
test: assert lazy compilation build counts
jbroma Jul 29, 2026
7146347
fix: reject unsupported lazy compilation platforms
jbroma Jul 29, 2026
ef152d2
fix: reject pending bundles during shutdown
jbroma Jul 31, 2026
61378cf
fix: always stop dev server after compiler errors
jbroma Jul 31, 2026
341e05e
chore: add lazy compilation changeset
jbroma Jul 31, 2026
f73a1cc
fix: reject bundles after shutdown starts
jbroma Jul 31, 2026
c176eeb
test: make server teardown explicit
jbroma Jul 31, 2026
0f8d592
Merge remote-tracking branch 'origin/main' into feat/lazy-compilation
jbroma Jul 31, 2026
ed34985
docs: clarify lazy compilation changeset
jbroma Jul 31, 2026
e84526c
refactor: simplify Rspack hook setup
jbroma Jul 31, 2026
b746834
test: consolidate lazy compilation scenarios
jbroma Jul 31, 2026
b5e84c5
chore: drop unrelated Podfile lock churn
jbroma Jul 31, 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/lazy-platform-compilation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@callstack/repack": minor
---

Bring the Rspack development experience in line with Webpack by compiling each
platform only when its bundle is first requested. Multi-platform development
servers no longer eagerly build unused platforms, so launching an iOS app does
not wait for Android to compile, and vice versa.
106 changes: 106 additions & 0 deletions apps/tester-app/__tests__/lazy-compilation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import fs from 'node:fs';
import path from 'node:path';
import rspackCommands from '@callstack/repack/commands/rspack';
import { MultiCompiler } from '@rspack/core';
import getPort from 'get-port';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

const TMP_DIR = path.join(__dirname, 'out/lazy-compilation');

let port: number;
let stopServer: (() => Promise<void>) | undefined;

describe('lazy compilation', () => {
const startCommand = rspackCommands.find(
(command) => command.name === 'start'
);
if (!startCommand) throw new Error('start command not found');

const getStats = (platform: string) =>
fetch(`http://localhost:${port}/api/${platform}/stats`).then((response) =>
response.json()
);

beforeAll(async () => {
await fs.promises.rm(TMP_DIR, { recursive: true, force: true });

port = await getPort();

const config = {
root: path.join(__dirname, '..'),
platforms: { ios: {}, android: {} },
reactNativePath: path.join(__dirname, '../node_modules/react-native'),
};

const args = {
port,
// No `platform` arg — both ios and android are configured,
// which enables the lazy compilation watchRun gate mechanism.
logFile: path.join(TMP_DIR, 'server.log'),
webpackConfig: path.join(__dirname, 'configs', './rspack.config.mjs'),
};

// @ts-ignore
const { stop } = await startCommand.func([], config, args);
stopServer = stop;
});

afterAll(async () => {
if (stopServer) {
await stopServer();
}
});

it(
'compiles each platform when its bundle is first requested',
async () => {
const [initialIosStats, initialAndroidStats] = await Promise.all([
getStats('ios'),
getStats('android'),
]);
expect(initialIosStats.data).toBeNull();
expect(initialAndroidStats.data).toBeNull();

const iosResponse = await fetch(
`http://localhost:${port}/index.bundle?platform=ios`
);
await iosResponse.text();
expect(iosResponse.status).toBe(200);

const [iosStats, androidStats] = await Promise.all([
getStats('ios'),
getStats('android'),
]);
expect(iosStats.data).not.toBeNull();
expect(androidStats.data).toBeNull();

const androidResponse = await fetch(
`http://localhost:${port}/index.bundle?platform=android`
);
await androidResponse.text();
expect(androidResponse.status).toBe(200);
const finalAndroidStats = await getStats('android');
expect(finalAndroidStats.data).not.toBeNull();
},
60 * 1000
);

it('stops the dev server when compiler shutdown fails', async () => {
const stop = stopServer;
if (!stop) throw new Error('Dev server was not started');

const close = MultiCompiler.prototype.close;
const closeError = new Error('close failed');
MultiCompiler.prototype.close = function (callback) {
close.call(this, () => callback(closeError));
};

try {
await expect(stop()).rejects.toBe(closeError);
await expect(fetch(`http://localhost:${port}/status`)).rejects.toThrow();
stopServer = undefined;
} finally {
MultiCompiler.prototype.close = close;
}
});
});
Loading