Skip to content

Commit 3d2784a

Browse files
authored
fix(core): preserve options when restarting (#8161)
1 parent 44805db commit 3d2784a

16 files changed

Lines changed: 222 additions & 48 deletions

File tree

e2e/cases/javascript-api/dev-server-restart/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ test('should watch loaded config dependencies for restart', async () => {
4747
.toEqual({
4848
action: 'dev',
4949
filePath: configDep,
50+
options: {},
5051
});
5152
} finally {
5253
await server.close();

e2e/cases/javascript-api/restart-option/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ test('createRsbuild should support a restart function', async () => {
6868
context: {
6969
action: 'dev',
7070
filePath: watchedFile,
71+
options: {},
7172
},
7273
calls: ['onRestart', 'onCloseDevServer'],
7374
});
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
import { expect, test } from '@e2e/helper';
4+
import { createRsbuild, type RestartContext } from '@rsbuild/core';
5+
import { getRandomPort } from '@rstackjs/test-utils';
6+
7+
const watchedFile = path.join(import.meta.dirname, 'test-temp-watch.txt');
8+
9+
test.beforeEach(() => {
10+
fs.writeFileSync(watchedFile, '1');
11+
});
12+
13+
test.afterAll(() => {
14+
fs.rmSync(watchedFile, { force: true });
15+
});
16+
17+
test('should preserve startDevServer options after restart', async () => {
18+
let restartContext: RestartContext | undefined;
19+
const rsbuild = await createRsbuild({
20+
cwd: import.meta.dirname,
21+
config: {
22+
dev: {
23+
watchFiles: {
24+
paths: watchedFile,
25+
type: 'restart',
26+
},
27+
},
28+
server: {
29+
port: await getRandomPort(),
30+
},
31+
},
32+
restart: (context) => {
33+
restartContext = context;
34+
return true;
35+
},
36+
});
37+
38+
const { server } = await rsbuild.startDevServer({ getPortSilently: true });
39+
let version = 1;
40+
41+
try {
42+
await expect
43+
.poll(
44+
() => {
45+
if (!restartContext) {
46+
fs.writeFileSync(watchedFile, String(++version));
47+
}
48+
return restartContext;
49+
},
50+
{ timeout: 5_000 },
51+
)
52+
.toEqual({
53+
action: 'dev',
54+
filePath: watchedFile,
55+
options: {
56+
getPortSilently: true,
57+
},
58+
});
59+
} finally {
60+
await server.close();
61+
}
62+
});
63+
64+
test('should preserve build options after restart', async () => {
65+
let restartContext: RestartContext | undefined;
66+
const rsbuild = await createRsbuild({
67+
cwd: import.meta.dirname,
68+
config: {
69+
dev: {
70+
watchFiles: {
71+
paths: watchedFile,
72+
type: 'restart',
73+
},
74+
},
75+
},
76+
restart: (context) => {
77+
restartContext = context;
78+
return true;
79+
},
80+
});
81+
82+
const buildReady = new Promise<void>((resolve) => {
83+
rsbuild.onAfterBuild(() => resolve());
84+
});
85+
const result = await rsbuild.build({ watch: true });
86+
await buildReady;
87+
let version = 1;
88+
89+
try {
90+
await expect
91+
.poll(
92+
() => {
93+
if (!restartContext) {
94+
fs.writeFileSync(watchedFile, String(++version));
95+
}
96+
return restartContext;
97+
},
98+
{ timeout: 5_000 },
99+
)
100+
.toEqual({
101+
action: 'build',
102+
filePath: watchedFile,
103+
options: {
104+
watch: true,
105+
},
106+
});
107+
} finally {
108+
await result.close();
109+
}
110+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('restart preserve options');

e2e/cases/javascript-api/restart-watch-files/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const expectRestart = async (rsbuild: RsbuildInstance, action: RestartContext['a
4141
.toEqual({
4242
action,
4343
filePath: watchedFile,
44+
options: action === 'build' ? { watch: true } : {},
4445
});
4546
};
4647

packages/core/src/build.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ export const RSPACK_BUILD_ERROR = 'Rspack build failed.';
99

1010
export const build = async (
1111
initOptions: InitConfigsOptions,
12-
{ watch }: BuildOptions = {},
12+
buildOptions: BuildOptions = {},
1313
): Promise<ReturnType<Build>> => {
14+
const { watch } = buildOptions;
1415
const { context } = initOptions;
1516
const { logger } = context;
1617
const { compiler, rspackConfigs } = await createCompiler(initOptions);
@@ -84,7 +85,10 @@ export const build = async (
8485
restartWatcher = watchFilesForRestart({
8586
watchFiles: context.normalizedConfig!.dev.watchFiles,
8687
context,
87-
action: 'build',
88+
restartContext: {
89+
action: 'build',
90+
options: buildOptions,
91+
},
8892
});
8993
}
9094

packages/core/src/cli/init.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createRsbuild } from '../createRsbuild';
33
import { ensureAbsolutePath } from '../helpers/path';
44
import { loadConfig as baseLoadConfig } from '../loadConfig';
55
import { defaultLogger } from '../logger';
6-
import type { RestartContext, RsbuildInstance } from '../types';
6+
import type { RestartFn, RsbuildInstance } from '../types';
77
import type { CommonOptions } from './commands';
88

99
export type CommandName = 'dev' | 'build' | 'preview' | 'inspect';
@@ -113,7 +113,7 @@ const loadConfig = async (root: string) => {
113113
return result;
114114
};
115115

116-
const restart = async ({ action }: RestartContext): Promise<boolean> => {
116+
const restart: RestartFn = async (context) => {
117117
const rsbuild = await init({ isRestart: true });
118118

119119
// Skip restarting if the config cannot be loaded, for example while the
@@ -122,10 +122,10 @@ const restart = async ({ action }: RestartContext): Promise<boolean> => {
122122
return false;
123123
}
124124

125-
if (action === 'build') {
126-
await rsbuild.build({ watch: true });
125+
if (context.action === 'build') {
126+
await rsbuild.build(context.options);
127127
} else {
128-
await rsbuild.startDevServer();
128+
await rsbuild.startDevServer(context.options);
129129
}
130130
return true;
131131
};

packages/core/src/createRsbuild.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ import type {
6363
RsbuildPlugin,
6464
RsbuildPlugins,
6565
StartDevServer,
66-
StartDevServerOptions,
6766
} from './types';
6867

6968
function applyDefaultPlugins(pluginManager: PluginManager, context: InternalContext) {
@@ -253,17 +252,24 @@ export async function createRsbuild(options: CreateRsbuildOptions = {}): Promise
253252
return startPreviewServer(context, config, options);
254253
};
255254

256-
const build: Build = async (options) => {
255+
const build: Build = async (options = {}) => {
257256
context.action = 'build';
258257

259258
if (!getNodeEnv()) {
260259
setNodeEnv('production');
261260
}
262261

263-
return baseBuild({ context, pluginManager, rsbuildOptions: resolvedOptions }, options);
262+
return baseBuild(
263+
{
264+
context,
265+
pluginManager,
266+
rsbuildOptions: resolvedOptions,
267+
},
268+
{ ...options },
269+
);
264270
};
265271

266-
const startDevServer: StartDevServer = async (options?: StartDevServerOptions) => {
272+
const startDevServer: StartDevServer = async (options = {}) => {
267273
context.action = 'dev';
268274

269275
if (!getNodeEnv()) {
@@ -272,10 +278,14 @@ export async function createRsbuild(options: CreateRsbuildOptions = {}): Promise
272278

273279
const config = await initRsbuildConfig({ context, pluginManager });
274280
const server = await baseCreateDevServer(
275-
{ context, pluginManager, rsbuildOptions: resolvedOptions },
281+
{
282+
context,
283+
pluginManager,
284+
rsbuildOptions: resolvedOptions,
285+
},
276286
createCompiler,
277287
config,
278-
options,
288+
{ ...options },
279289
);
280290

281291
return server.listen();

packages/core/src/restart.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,18 @@ const clearConsole = () => {
1313
};
1414

1515
export const requestRestart = ({
16-
filePath,
1716
clear = true,
18-
action,
1917
logger,
18+
restartContext,
2019
restartManager,
21-
}: RestartContext & {
20+
}: {
2221
clear?: boolean;
2322
logger: Logger;
23+
restartContext: RestartContext;
2424
restartManager: RestartManager;
2525
}): Promise<boolean> => {
26+
const { action, filePath } = restartContext;
27+
2628
if (restartManager.canRestart) {
2729
if (clear) {
2830
clearConsole();
@@ -38,17 +40,17 @@ export const requestRestart = ({
3840
}
3941
}
4042

41-
return restartManager.requestRestart({ action, filePath });
43+
return restartManager.requestRestart(restartContext);
4244
};
4345

4446
export function watchFilesForRestart({
4547
watchFiles,
4648
context,
47-
action,
49+
restartContext,
4850
}: {
4951
watchFiles: WatchFiles[];
5052
context: InternalContext;
51-
action: RestartContext['action'];
53+
restartContext: RestartContext;
5254
}): WatchFilesResult | undefined {
5355
const defaultFiles = context.configFile
5456
? [context.configFile, ...context.configFileDependencies]
@@ -99,8 +101,10 @@ export function watchFilesForRestart({
99101

100102
try {
101103
const restarted = await requestRestart({
102-
action,
103-
filePath: absoluteFilePath,
104+
restartContext: {
105+
...restartContext,
106+
filePath: absoluteFilePath,
107+
},
104108
logger,
105109
restartManager,
106110
});
@@ -109,7 +113,9 @@ export function watchFilesForRestart({
109113
if (restarted) {
110114
await close();
111115
} else if (restartManager.canRestart) {
112-
logger.error(action === 'build' ? 'Restart build failed.' : 'Restart server failed.');
116+
logger.error(
117+
restartContext.action === 'build' ? 'Restart build failed.' : 'Restart server failed.',
118+
);
113119
}
114120
} catch (error) {
115121
logger.error(error);

packages/core/src/server/devServer.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Server } from 'node:http';
22
import type { Http2SecureServer } from 'node:http2';
3-
import { color } from '../helpers';
3+
import { color, pick } from '../helpers';
44
import { getPublicPathFromCompiler, isMultiCompiler } from '../helpers/compiler';
55
import { requestRestart, watchFilesForRestart } from '../restart';
66
import type {
@@ -85,8 +85,13 @@ export async function createDevServer<
8585
options: Options,
8686
createCompiler: CreateCompiler,
8787
config: NormalizedConfig,
88-
{ getPortSilently, runCompile = true }: CreateDevServerOptions = {},
88+
devServerOptions: CreateDevServerOptions = {},
8989
): Promise<RsbuildDevServer> {
90+
const { getPortSilently, runCompile = true } = devServerOptions;
91+
const restartContext = {
92+
action: 'dev' as const,
93+
options: pick(devServerOptions, ['getPortSilently']),
94+
};
9095
const { context } = options;
9196
const { logger } = context;
9297
logger.debug('create dev server');
@@ -228,7 +233,7 @@ export async function createDevServer<
228233
// Request a manual restart and close the old watcher only after it succeeds.
229234
const restartServer = async () => {
230235
const restarted = await requestRestart({
231-
action: 'dev',
236+
restartContext,
232237
clear: false,
233238
logger,
234239
restartManager: context.restartManager,
@@ -455,7 +460,7 @@ export async function createDevServer<
455460
state.restartWatcher = watchFilesForRestart({
456461
watchFiles: config.dev.watchFiles,
457462
context,
458-
action: 'dev',
463+
restartContext,
459464
});
460465

461466
logger.debug('create dev server done');

0 commit comments

Comments
 (0)