Skip to content

Commit 4bdc305

Browse files
authored
feat: support prune dep tree from specific project (#10)
1 parent 9639cad commit 4bdc305

File tree

6 files changed

+54
-23
lines changed

6 files changed

+54
-23
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,11 @@ interface projects {
104104
*/
105105
match?: (stdout: string) => boolean;
106106
/**
107-
* Whether to skip starting the current sub-project. Default is `false`.
108-
* Useful for sub-projects that do not need to be started.
109-
*/
110-
skip?: boolean;
107+
* Whether to skip starting the current sub-project. The default value is `false`, typically used to skip sub-projects that don't need to be started.
108+
* When the value is `true`, pruning will be performed on the specified project, meaning that this project and all its direct and indirect dependencies will not be started by the plugin.
109+
* When the value is `only`, starting the specified project will be skipped, but no pruning will be performed, meaning that the project's direct and indirect dependencies will still be started by the plugin.
110+
*/
111+
skip?: boolean | 'only';
111112
}
112113
113114

README.zh-CN.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,10 @@ interface Projects {
9999
match?: (stdout: string) => boolean;
100100
/**
101101
* 是否跳过当前子项目的启动,默认值为 `false`,通常用于跳过一些不需要启动的子项目。
102+
* 当值为 `true` 时,会从指定项目进行剪枝,这意味着该项目以及他的所有直接和间接依赖都不会被插件启动。
103+
* 当值为 `only` 时,会跳过指定项目的启动,但不会进行剪枝,这意味着该项目的直接和间接依赖仍然会被插件启动。
102104
*/
103-
skip?: boolean;
105+
skip?: boolean | 'only';
104106
}
105107
106108
// 例如,配置 lib1 子项目,用 build:watch 命令启动,匹配 watch success 日志

src/constant.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const PACKAGE_JSON = 'package.json';
2-
export const DEBUG_LOG_TITLE = '[Rsbuild Workspace Dev Plugin]: ';
2+
export const PLUGIN_LOG_TITLE = '[Rsbuild Workspace Dev Plugin]: ';
33

44
export const RSLIB_READY_MESSAGE = 'build complete, watching for changes';
55
export const MODERN_MODULE_READY_MESSAGE = 'Watching for file changes';

src/logger.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import chalk from 'chalk';
2-
import { DEBUG_LOG_TITLE } from './constant.js';
2+
import { PLUGIN_LOG_TITLE } from './constant.js';
33
import { isDebug } from './utils.js';
44

55
enum LogType {
@@ -26,7 +26,7 @@ export class Logger {
2626
this.name = name;
2727
this.stdout = '';
2828
this.stderr = '';
29-
this.logTitle = DEBUG_LOG_TITLE;
29+
this.logTitle = PLUGIN_LOG_TITLE;
3030
}
3131

3232
appendLog(type: 'stdout' | 'stderr', log: string) {
@@ -71,7 +71,7 @@ export class Logger {
7171
}
7272
}
7373

74-
export const debugLog = (msg: string, prefix = DEBUG_LOG_TITLE) => {
74+
export const debugLog = (msg: string, prefix = PLUGIN_LOG_TITLE) => {
7575
if (isDebug) {
7676
console.log(prefix + msg);
7777
}

src/plugin.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,20 @@ export function pluginWorkspaceDev(
1717
cwd: rootPath,
1818
...options,
1919
});
20+
await runner.init();
21+
await runner.start();
22+
Logger.setEndBanner();
23+
});
24+
25+
api.onBeforeBuild(async ({ isWatch, isFirstCompile }) => {
26+
if (!isWatch || !isFirstCompile) {
27+
return;
28+
}
2029

30+
const runner = new WorkspaceDevRunner({
31+
cwd: rootPath,
32+
...options,
33+
});
2134
await runner.init();
2235
await runner.start();
2336
Logger.setEndBanner();

src/workspace-dev.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import graphlib, { Graph } from 'graphlib';
44
import path from 'path';
55

66
import {
7-
DEBUG_LOG_TITLE,
87
MODERN_MODULE_READY_MESSAGE,
98
PACKAGE_JSON,
9+
PLUGIN_LOG_TITLE,
1010
RSLIB_READY_MESSAGE,
1111
TSUP_READY_MESSAGE,
1212
} from './constant.js';
@@ -27,7 +27,7 @@ export interface WorkspaceDevRunnerOptions {
2727
{
2828
match?: (stdout: string) => boolean;
2929
command?: string;
30-
skip?: boolean;
30+
skip?: boolean | 'only';
3131
}
3232
>;
3333
startCurrent?: boolean;
@@ -86,6 +86,10 @@ export class WorkspaceDevRunner {
8686
packageJson,
8787
path: dir,
8888
};
89+
const skip = this.options.projects?.[name]?.skip;
90+
if (skip === true) {
91+
return;
92+
}
8993
this.graph.setNode(name, node);
9094
this.visited[name] = false;
9195
this.visiting[name] = false;
@@ -103,14 +107,21 @@ export class WorkspaceDevRunner {
103107
(p) => p.packageJson.name === depName,
104108
);
105109

110+
const skip = this.options.projects?.[depName]?.skip;
106111
if (isInternalDep) {
107-
this.graph.setEdge(packageName, depName);
108-
this.checkGraph();
109-
const depPackage = packages.find(
110-
(pkg) => pkg.packageJson.name === depName,
111-
)!;
112-
if (!this.getNode(depName)) {
113-
initNode(depPackage);
112+
if (skip !== true) {
113+
this.graph.setEdge(packageName, depName);
114+
this.checkGraph();
115+
const depPackage = packages.find(
116+
(pkg) => pkg.packageJson.name === depName,
117+
)!;
118+
if (!this.getNode(depName)) {
119+
initNode(depPackage);
120+
}
121+
} else {
122+
debugLog(
123+
`Prune project ${depName} and its dependencies because it is marked as skip: true`,
124+
);
114125
}
115126
}
116127
}
@@ -122,10 +133,13 @@ export class WorkspaceDevRunner {
122133
checkGraph() {
123134
const cycles = graphlib.alg.findCycles(this.graph);
124135
const nonSelfCycles = cycles.filter((c) => c.length !== 1);
125-
debugLog(`cycles check: ${cycles}`);
126-
if (nonSelfCycles.length) {
136+
const nonSkipCycles = nonSelfCycles.filter((group) => {
137+
const isSkip = group.some((node) => this.options.projects?.[node]?.skip);
138+
return !isSkip;
139+
});
140+
if (nonSkipCycles.length) {
127141
throw new Error(
128-
`${DEBUG_LOG_TITLE}Dependency graph do not allow cycles.`,
142+
`${PLUGIN_LOG_TITLE} Cycle dependency graph found: ${nonSkipCycles}, you should config projects in plugin options to skip someone, or fix the cycle dependency. Otherwise, a loop of dev will occur.`,
129143
);
130144
}
131145
}
@@ -143,7 +157,8 @@ export class WorkspaceDevRunner {
143157
const canStart = dependencies.every((dep) => {
144158
const selfStart = node === dep;
145159
const isVisiting = this.visiting[dep];
146-
const isVisited = selfStart || this.visited[dep];
160+
const skipDep = this.options.projects?.[dep]?.skip;
161+
const isVisited = selfStart || this.visited[dep] || skipDep;
147162
return isVisited && !isVisiting;
148163
});
149164

@@ -167,7 +182,7 @@ export class WorkspaceDevRunner {
167182
this.visited[node] = true;
168183
this.visiting[node] = false;
169184
debugLog(`Skip visit node: ${node}`);
170-
logger.emitLogOnce('stdout', `skip visit node: ${name}`);
185+
logger.emitLogOnce('stdout', `Skip visit node: ${name}`);
171186
return this.start().then(() => resolve());
172187
}
173188
this.visiting[node] = true;

0 commit comments

Comments
 (0)