diff --git a/README.md b/README.md index 438a29a..5ebafd9 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,11 @@ interface projects { */ match?: (stdout: string) => boolean; /** - * Whether to skip starting the current sub-project. Default is `false`. - * Useful for sub-projects that do not need to be started. - */ - skip?: boolean; + * 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. + * 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. + * 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. + */ + skip?: boolean | 'only'; } diff --git a/README.zh-CN.md b/README.zh-CN.md index 7121d25..ba7ebec 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -99,8 +99,10 @@ interface Projects { match?: (stdout: string) => boolean; /** * 是否跳过当前子项目的启动,默认值为 `false`,通常用于跳过一些不需要启动的子项目。 + * 当值为 `true` 时,会从指定项目进行剪枝,这意味着该项目以及他的所有直接和间接依赖都不会被插件启动。 + * 当值为 `only` 时,会跳过指定项目的启动,但不会进行剪枝,这意味着该项目的直接和间接依赖仍然会被插件启动。 */ - skip?: boolean; + skip?: boolean | 'only'; } // 例如,配置 lib1 子项目,用 build:watch 命令启动,匹配 watch success 日志 diff --git a/src/constant.ts b/src/constant.ts index 28eaa21..7467a33 100644 --- a/src/constant.ts +++ b/src/constant.ts @@ -1,5 +1,5 @@ export const PACKAGE_JSON = 'package.json'; -export const DEBUG_LOG_TITLE = '[Rsbuild Workspace Dev Plugin]: '; +export const PLUGIN_LOG_TITLE = '[Rsbuild Workspace Dev Plugin]: '; export const RSLIB_READY_MESSAGE = 'build complete, watching for changes'; export const MODERN_MODULE_READY_MESSAGE = 'Watching for file changes'; diff --git a/src/logger.ts b/src/logger.ts index f0e88d3..6cb5f16 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,5 +1,5 @@ import chalk from 'chalk'; -import { DEBUG_LOG_TITLE } from './constant.js'; +import { PLUGIN_LOG_TITLE } from './constant.js'; import { isDebug } from './utils.js'; enum LogType { @@ -26,7 +26,7 @@ export class Logger { this.name = name; this.stdout = ''; this.stderr = ''; - this.logTitle = DEBUG_LOG_TITLE; + this.logTitle = PLUGIN_LOG_TITLE; } appendLog(type: 'stdout' | 'stderr', log: string) { @@ -71,7 +71,7 @@ export class Logger { } } -export const debugLog = (msg: string, prefix = DEBUG_LOG_TITLE) => { +export const debugLog = (msg: string, prefix = PLUGIN_LOG_TITLE) => { if (isDebug) { console.log(prefix + msg); } diff --git a/src/plugin.ts b/src/plugin.ts index ad70bb0..2b7f6c3 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -17,7 +17,20 @@ export function pluginWorkspaceDev( cwd: rootPath, ...options, }); + await runner.init(); + await runner.start(); + Logger.setEndBanner(); + }); + + api.onBeforeBuild(async ({ isWatch, isFirstCompile }) => { + if (!isWatch || !isFirstCompile) { + return; + } + const runner = new WorkspaceDevRunner({ + cwd: rootPath, + ...options, + }); await runner.init(); await runner.start(); Logger.setEndBanner(); diff --git a/src/workspace-dev.ts b/src/workspace-dev.ts index 852de2a..450628a 100644 --- a/src/workspace-dev.ts +++ b/src/workspace-dev.ts @@ -4,9 +4,9 @@ import graphlib, { Graph } from 'graphlib'; import path from 'path'; import { - DEBUG_LOG_TITLE, MODERN_MODULE_READY_MESSAGE, PACKAGE_JSON, + PLUGIN_LOG_TITLE, RSLIB_READY_MESSAGE, TSUP_READY_MESSAGE, } from './constant.js'; @@ -27,7 +27,7 @@ export interface WorkspaceDevRunnerOptions { { match?: (stdout: string) => boolean; command?: string; - skip?: boolean; + skip?: boolean | 'only'; } >; startCurrent?: boolean; @@ -86,6 +86,10 @@ export class WorkspaceDevRunner { packageJson, path: dir, }; + const skip = this.options.projects?.[name]?.skip; + if (skip === true) { + return; + } this.graph.setNode(name, node); this.visited[name] = false; this.visiting[name] = false; @@ -103,14 +107,21 @@ export class WorkspaceDevRunner { (p) => p.packageJson.name === depName, ); + const skip = this.options.projects?.[depName]?.skip; if (isInternalDep) { - this.graph.setEdge(packageName, depName); - this.checkGraph(); - const depPackage = packages.find( - (pkg) => pkg.packageJson.name === depName, - )!; - if (!this.getNode(depName)) { - initNode(depPackage); + if (skip !== true) { + this.graph.setEdge(packageName, depName); + this.checkGraph(); + const depPackage = packages.find( + (pkg) => pkg.packageJson.name === depName, + )!; + if (!this.getNode(depName)) { + initNode(depPackage); + } + } else { + debugLog( + `Prune project ${depName} and its dependencies because it is marked as skip: true`, + ); } } } @@ -122,10 +133,13 @@ export class WorkspaceDevRunner { checkGraph() { const cycles = graphlib.alg.findCycles(this.graph); const nonSelfCycles = cycles.filter((c) => c.length !== 1); - debugLog(`cycles check: ${cycles}`); - if (nonSelfCycles.length) { + const nonSkipCycles = nonSelfCycles.filter((group) => { + const isSkip = group.some((node) => this.options.projects?.[node]?.skip); + return !isSkip; + }); + if (nonSkipCycles.length) { throw new Error( - `${DEBUG_LOG_TITLE}Dependency graph do not allow cycles.`, + `${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.`, ); } } @@ -143,7 +157,8 @@ export class WorkspaceDevRunner { const canStart = dependencies.every((dep) => { const selfStart = node === dep; const isVisiting = this.visiting[dep]; - const isVisited = selfStart || this.visited[dep]; + const skipDep = this.options.projects?.[dep]?.skip; + const isVisited = selfStart || this.visited[dep] || skipDep; return isVisited && !isVisiting; }); @@ -167,7 +182,7 @@ export class WorkspaceDevRunner { this.visited[node] = true; this.visiting[node] = false; debugLog(`Skip visit node: ${node}`); - logger.emitLogOnce('stdout', `skip visit node: ${name}`); + logger.emitLogOnce('stdout', `Skip visit node: ${name}`); return this.start().then(() => resolve()); } this.visiting[node] = true;