Skip to content

Commit a712a08

Browse files
committed
feat: support restart when rslib config changed
1 parent 3f644f9 commit a712a08

File tree

11 files changed

+150
-16
lines changed

11 files changed

+150
-16
lines changed

packages/core/prebundle.config.mjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ export default {
1313
},
1414
dependencies: [
1515
'commander',
16-
'chokidar',
16+
{
17+
name: 'chokidar',
18+
// strip sourcemap comment
19+
prettier: true,
20+
},
1721
{
1822
name: 'rslog',
1923
afterBundle(task) {

packages/core/src/cli/build.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
import { type RsbuildInstance, createRsbuild } from '@rsbuild/core';
2-
import {
3-
composeRsbuildEnvironments,
4-
loadConfig,
5-
pruneEnvironments,
6-
} from '../config';
2+
import { composeRsbuildEnvironments, pruneEnvironments } from '../config';
73
import { onBeforeRestartServer, watchFilesForRestart } from '../restart';
4+
import type { RslibConfig } from '../types/config';
85
import type { BuildOptions } from './commands';
6+
import { loadRslibConfig } from './init';
97

108
export async function build(
11-
options: BuildOptions = {},
9+
config: RslibConfig,
10+
options: Pick<BuildOptions, 'lib' | 'watch'> & {
11+
configFilePath?: string;
12+
} = {},
1213
): Promise<RsbuildInstance> {
13-
const { content: config, filePath: configFilePath } = await loadConfig({
14-
path: options?.config,
15-
envMode: options?.envMode,
16-
});
17-
1814
const environments = await composeRsbuildEnvironments(config);
1915
const rsbuildInstance = await createRsbuild({
2016
rsbuildConfig: {
@@ -28,12 +24,17 @@ export async function build(
2824

2925
if (options?.watch) {
3026
const files: string[] = [];
31-
files.push(configFilePath);
27+
options.configFilePath && files.push(options?.configFilePath);
3228

3329
onBeforeRestartServer(buildInstance.close);
3430

3531
watchFilesForRestart(files, async () => {
36-
await build(options);
32+
const { content: rslibConfig, filePath: configFilePath } =
33+
await loadRslibConfig(options);
34+
await build(rslibConfig, {
35+
configFilePath,
36+
...options,
37+
});
3738
});
3839
} else {
3940
await buildInstance.close();

packages/core/src/cli/commands.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ export function runCli(): void {
6262
.description('build the library for production')
6363
.action(async (options: BuildOptions) => {
6464
try {
65-
await build(options);
65+
const { content: rslibConfig, filePath: configFilePath } =
66+
await loadRslibConfig(options);
67+
await build(rslibConfig, {
68+
configFilePath,
69+
lib: options.lib,
70+
watch: options.watch,
71+
});
6672
} catch (err) {
6773
logger.error('Failed to build.');
6874
logger.error(err);

packages/plugin-dts/src/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { fork } from 'node:child_process';
1+
import { type ChildProcess, fork } from 'node:child_process';
22
import { dirname, extname, join } from 'node:path';
33
import { fileURLToPath } from 'node:url';
44
import { type RsbuildConfig, type RsbuildPlugin, logger } from '@rsbuild/core';
@@ -60,6 +60,7 @@ export const pluginDts = (options: PluginDtsOptions = {}): RsbuildPlugin => ({
6060

6161
const dtsPromises: Promise<TaskResult>[] = [];
6262
let promisesResult: TaskResult[] = [];
63+
let childProcesses: ChildProcess[] = [];
6364

6465
api.onBeforeEnvironmentCompile(
6566
({ isWatch, isFirstCompile, environment }) => {
@@ -74,6 +75,8 @@ export const pluginDts = (options: PluginDtsOptions = {}): RsbuildPlugin => ({
7475
stdio: 'inherit',
7576
});
7677

78+
childProcesses.push(childProcess);
79+
7780
// TODO: @microsoft/api-extractor only support single entry to bundle DTS
7881
// use first element of Record<string, string> type entry config
7982
const dtsEntry = processSourceEntry(
@@ -141,5 +144,14 @@ export const pluginDts = (options: PluginDtsOptions = {}): RsbuildPlugin => ({
141144
},
142145
order: 'post',
143146
});
147+
148+
api.onCloseBuild(() => {
149+
for (const childProcess of childProcesses) {
150+
if (!childProcess.killed) {
151+
childProcess.kill();
152+
}
153+
}
154+
childProcesses = [];
155+
});
144156
},
145157
});

pnpm-lock.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { exec } from 'node:child_process';
2+
import path from 'node:path';
3+
import fse from 'fs-extra';
4+
import { awaitFileExists } from 'test-helper';
5+
import { describe, test } from 'vitest';
6+
7+
describe('build --watch command', async () => {
8+
test('basic', async () => {
9+
const distPath = path.join(__dirname, 'dist');
10+
await fse.remove(distPath);
11+
12+
const tempConfigFile = path.join(__dirname, 'test-temp-rslib.config.mjs');
13+
14+
fse.outputFileSync(
15+
tempConfigFile,
16+
`import { defineConfig } from '@rslib/core';
17+
import { generateBundleEsmConfig } from 'test-helper';
18+
19+
export default defineConfig({
20+
lib: [generateBundleEsmConfig()],
21+
});
22+
`,
23+
);
24+
25+
const process = exec(`npx rslib build --watch -c ${tempConfigFile}`, {
26+
cwd: __dirname,
27+
});
28+
29+
const distEsmIndexFile = path.join(__dirname, 'dist/esm/index.js');
30+
31+
await awaitFileExists(distEsmIndexFile);
32+
33+
await fse.remove(distPath);
34+
35+
fse.outputFileSync(
36+
tempConfigFile,
37+
`import { defineConfig } from '@rslib/core';
38+
import { generateBundleEsmConfig } from 'test-helper';
39+
40+
export default defineConfig({
41+
lib: [generateBundleEsmConfig()],
42+
});
43+
`,
44+
);
45+
46+
await awaitFileExists(distEsmIndexFile);
47+
48+
process.kill();
49+
});
50+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "cli-build-watch-test",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module"
6+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { defineConfig } from '@rslib/core';
2+
import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper';
3+
4+
export default defineConfig({
5+
lib: [
6+
generateBundleEsmConfig({
7+
dts: true,
8+
}),
9+
generateBundleCjsConfig({
10+
dts: true,
11+
}),
12+
],
13+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo = 'foo';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "@rslib/tsconfig/base",
3+
"compilerOptions": {
4+
"rootDir": "src",
5+
"baseUrl": ".",
6+
"composite": true
7+
},
8+
"include": ["src"]
9+
}

tests/scripts/helper.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,33 @@ export const proxyConsole = (
6161
},
6262
};
6363
};
64+
65+
export const waitFor = async (
66+
fn: () => boolean,
67+
{
68+
maxChecks = 100,
69+
interval = 20,
70+
}: {
71+
maxChecks?: number;
72+
interval?: number;
73+
} = {},
74+
) => {
75+
let checks = 0;
76+
77+
while (checks < maxChecks) {
78+
if (fn()) {
79+
return true;
80+
}
81+
checks++;
82+
await new Promise((resolve) => setTimeout(resolve, interval));
83+
}
84+
85+
return false;
86+
};
87+
88+
export const awaitFileExists = async (dir: string) => {
89+
const result = await waitFor(() => fse.existsSync(dir), { interval: 50 });
90+
if (!result) {
91+
throw new Error(`awaitFileExists failed: ${dir}`);
92+
}
93+
};

0 commit comments

Comments
 (0)