rsbuild-plugin-workspace-dev 0.0.1-beta.0 → 0.0.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,149 @@
1
- # rsbuild-plugin-workspace-dev
1
+ # rsbuild-plugin-workspace-dev
2
+
3
+ Start monorepo sub-projects in topological order.
4
+
5
+ `rsbuild-plugin-workspace-dev` is designed for monorepo development. It computes the dependency graph starting from the current project and starts sub-projects in topological order.
6
+
7
+ <p>
8
+ <a href="https://npmjs.com/package/rsbuild-plugin-workspace-dev">
9
+ <img src="https://img.shields.io/npm/v/rsbuild-plugin-workspace-dev?style=flat-square&colorA=564341&colorB=EDED91" alt="npm version" />
10
+ </a>
11
+ <img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square&colorA=564341&colorB=EDED91" alt="license" />
12
+ </p>
13
+
14
+ ## Usage
15
+
16
+ Install:
17
+
18
+ ```bash
19
+ npm add rsbuild-plugin-workspace-dev -D
20
+ ```
21
+
22
+ Register the plugin in `rsbuild.config.ts`:
23
+
24
+ ```ts
25
+ // rsbuild.config.ts
26
+ import { pluginWorkspaceDev } from "rsbuild-plugin-workspace-dev";
27
+
28
+ export default {
29
+ plugins: [pluginWorkspaceDev()],
30
+ };
31
+ ```
32
+
33
+ ## Use Cases
34
+
35
+ In a monorepo, one project may depend on multiple sub-projects, and those sub-projects can also depend on each other.
36
+
37
+ For example, the monorepo contains an app and several lib packages:
38
+
39
+ ```ts
40
+ monorepo
41
+ ├── app
42
+ └── lib1
43
+ └── lib2
44
+ └── lib3
45
+ ```
46
+
47
+ Here, app is built with Rsbuild, and lib is built with Rslib. The app depends on lib1 and lib2:
48
+
49
+ ```json
50
+ {
51
+ "name": "app",
52
+ "dependencies": {
53
+ "lib1": "workspace:*",
54
+ "lib2": "workspace:*"
55
+ }
56
+ }
57
+ ```
58
+
59
+ `lib2` depends on `lib3`:
60
+
61
+ ```json
62
+ {
63
+ "name": "lib2",
64
+ "dependencies": {
65
+ "lib3": "workspace:*"
66
+ }
67
+ }
68
+ ```
69
+
70
+ When you run `pnpm dev` under app, sub-projects start in topological order: first lib1 and lib3, then lib2, and finally app. Starting a lib refers to running its dev command, for example:
71
+
72
+ ```json
73
+ {
74
+ "scripts": {
75
+ "dev": "rslib build --watch"
76
+ }
77
+ }
78
+ ```
79
+
80
+ Whether a sub-project has finished starting is determined by matching sub-project logs. By default, logs from Rslib and tsup sub-projects are recognized. You can also provide a custom match function to determine when a sub-project is ready.
81
+
82
+ ## Options
83
+
84
+ ### projectConfig
85
+ Configure how sub-projects are started and define custom log matching logic.
86
+
87
+ - Type:
88
+ ```
89
+ interface ProjectConfig {
90
+ /**
91
+ * Custom sub-project start command. Default is `dev` (runs `npm run dev`).
92
+ */
93
+ command?: string;
94
+ /**
95
+ * Custom logic to detect when a sub-project has started.
96
+ * By default, logs from `Rsbuild` and `tsup` are supported.
97
+ */
98
+ match?: (stdout: string) => boolean;
99
+ /**
100
+ * Whether to skip starting the current sub-project. Default is `false`.
101
+ * Useful for sub-projects that do not need to be started.
102
+ */
103
+ skip?: boolean;
104
+ }
105
+ ```
106
+
107
+ ### ignoreSelf
108
+
109
+ - Type: `boolean`
110
+ - Default: `true`
111
+
112
+ Whether to ignore starting the current project. The default is `true`. In most cases, you start the current project manually, so the plugin does not interfere.
113
+
114
+ Consider a scenario where docs and lib are in the same project, and docs needs to debug the output of lib. In this case, you want to run `pnpm doc` for the docs, while lib should run `pnpm dev`. After configuring this option in your Rspress config, starting `pnpm doc` will automatically run `pnpm dev` to start the lib sub-project.
115
+
116
+ ```
117
+ ├── docs
118
+ │ └── index.mdx
119
+ ├── package.json
120
+ ├── src
121
+ │ └── Button.tsx
122
+ ├── rslib.config.ts
123
+ ├── rspress.config.ts
124
+ ```
125
+
126
+ ```
127
+ "scripts": {
128
+ "dev": "rslib build --watch",
129
+ "doc": "rspress dev"
130
+ },
131
+ ```
132
+
133
+ ### cwd
134
+
135
+ - Type: `string`
136
+ - Default: `process.cwd()`
137
+
138
+ Set the current working directory. The default is the current project directory; usually no configuration is needed.
139
+
140
+ ### workspaceFileDir
141
+
142
+ - Type: `string`
143
+ - Default: `process.cwd()`
144
+
145
+ Set the directory where the workspace file resides. The default is the current project directory; usually no configuration is needed.
146
+
147
+ ## License
148
+
149
+ [MIT](./LICENSE).
package/README.zh-CN.md CHANGED
@@ -136,6 +136,16 @@ interface ProjectConfig {
136
136
 
137
137
  用于配置 workspace 文件目录,默认值为当前项目目录,一般无需配置。
138
138
 
139
+
140
+ ## 常见问题
141
+
142
+ ### 日志输出卡住
143
+ 这是由于子项目日志是启动完成后一次性输出的,卡住可能是因为子项目构建过慢等原因,可以通过添加环境变量来开启调试模式,查看当前处理到了哪个子项目卡住。如果长时间卡住,可以手动到子项目中执行 dev 命令查看是否。
144
+ ```
145
+ DEBUG=rsbuild pnpm dev
146
+ ```
147
+
148
+
139
149
  ## License
140
150
 
141
151
  [MIT](./LICENSE).
package/dist/index.cjs CHANGED
@@ -38,6 +38,21 @@ __webpack_require__.d(__webpack_exports__, {
38
38
  });
39
39
  const external_chalk_namespaceObject = require("chalk");
40
40
  var external_chalk_default = /*#__PURE__*/ __webpack_require__.n(external_chalk_namespaceObject);
41
+ const external_fs_namespaceObject = require("fs");
42
+ var external_fs_default = /*#__PURE__*/ __webpack_require__.n(external_fs_namespaceObject);
43
+ const external_json5_namespaceObject = require("json5");
44
+ var external_json5_default = /*#__PURE__*/ __webpack_require__.n(external_json5_namespaceObject);
45
+ async function pathExists(path) {
46
+ return external_fs_default().promises.access(path).then(()=>true).catch(()=>false);
47
+ }
48
+ const readJson = async (jsonFileAbsPath)=>{
49
+ if (!await pathExists(jsonFileAbsPath)) return {};
50
+ const content = await external_fs_default().promises.readFile(jsonFileAbsPath, 'utf-8');
51
+ const json = external_json5_default().parse(content);
52
+ return json;
53
+ };
54
+ const readPackageJson = async (pkgJsonFilePath)=>readJson(pkgJsonFilePath);
55
+ const isDebug = 'rsbuild' === process.env.DEBUG || '*' === process.env.DEBUG;
41
56
  const PACKAGE_JSON = 'package.json';
42
57
  const DEBUG_LOG_TITLE = '[Rsbuild Workspace Dev Plugin]: ';
43
58
  const RSLIB_READY_MESSAGE = 'build complete, watching for changes';
@@ -76,6 +91,7 @@ class Logger {
76
91
  this.stdout = startBanner + this.stdout;
77
92
  }
78
93
  flushStdout() {
94
+ if (isDebug) return;
79
95
  this.setBanner(this.name);
80
96
  this.emitLog("stdout");
81
97
  }
@@ -94,9 +110,8 @@ class Logger {
94
110
  this.logTitle = DEBUG_LOG_TITLE;
95
111
  }
96
112
  }
97
- const debugLog = (msg)=>{
98
- const isDebug = 'rsbuild' === process.env.DEBUG || '*' === process.env.DEBUG;
99
- if (isDebug) console.log(DEBUG_LOG_TITLE + msg);
113
+ const debugLog = (msg, prefix = DEBUG_LOG_TITLE)=>{
114
+ if (isDebug) console.log(prefix + msg);
100
115
  };
101
116
  const get_packages_namespaceObject = require("@manypkg/get-packages");
102
117
  const external_child_process_namespaceObject = require("child_process");
@@ -104,20 +119,6 @@ const external_graphlib_namespaceObject = require("graphlib");
104
119
  var external_graphlib_default = /*#__PURE__*/ __webpack_require__.n(external_graphlib_namespaceObject);
105
120
  const external_path_namespaceObject = require("path");
106
121
  var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_namespaceObject);
107
- const external_fs_namespaceObject = require("fs");
108
- var external_fs_default = /*#__PURE__*/ __webpack_require__.n(external_fs_namespaceObject);
109
- const external_json5_namespaceObject = require("json5");
110
- var external_json5_default = /*#__PURE__*/ __webpack_require__.n(external_json5_namespaceObject);
111
- async function pathExists(path) {
112
- return external_fs_default().promises.access(path).then(()=>true).catch(()=>false);
113
- }
114
- const readJson = async (jsonFileAbsPath)=>{
115
- if (!await pathExists(jsonFileAbsPath)) return {};
116
- const content = await external_fs_default().promises.readFile(jsonFileAbsPath, 'utf-8');
117
- const json = external_json5_default().parse(content);
118
- return json;
119
- };
120
- const readPackageJson = async (pkgJsonFilePath)=>readJson(pkgJsonFilePath);
121
122
  function workspace_dev_define_property(obj, key, value) {
122
123
  if (key in obj) Object.defineProperty(obj, key, {
123
124
  value: value,
@@ -206,7 +207,6 @@ class WorkspaceDevRunner {
206
207
  this.visited[node] = true;
207
208
  this.visiting[node] = false;
208
209
  debugLog(`Skip visit node: ${node}`);
209
- logger.flushStdout();
210
210
  logger.emitLogOnce('stdout', `skip visit node: ${name}`);
211
211
  return this.start().then(()=>resolve());
212
212
  }
@@ -224,11 +224,9 @@ class WorkspaceDevRunner {
224
224
  });
225
225
  child.stdout.on('data', async (data)=>{
226
226
  const stdout = data.toString();
227
- if (this.matched[node]) {
228
- const content = data.toString().replace(/\n$/, '');
229
- logger.emitLogOnce('stdout', content);
230
- return;
231
- }
227
+ const content = data.toString().replace(/\n$/, '');
228
+ if (this.matched[node]) return void logger.emitLogOnce('stdout', content);
229
+ debugLog(content, `${name}: `);
232
230
  logger.appendLog('stdout', stdout);
233
231
  const match = config?.match;
234
232
  const matchResult = match ? match(stdout) : stdout.match(RSLIB_READY_MESSAGE) || stdout.match(MODERN_MODULE_READY_MESSAGE) || stdout.match(TSUP_READY_MESSAGE);
package/dist/index.js CHANGED
@@ -1,10 +1,21 @@
1
1
  import chalk from "chalk";
2
+ import fs from "fs";
3
+ import json5 from "json5";
2
4
  import { getPackagesSync } from "@manypkg/get-packages";
3
5
  import { spawn } from "child_process";
4
6
  import graphlib, { Graph } from "graphlib";
5
7
  import path_0 from "path";
6
- import fs from "fs";
7
- import json5 from "json5";
8
+ async function pathExists(path) {
9
+ return fs.promises.access(path).then(()=>true).catch(()=>false);
10
+ }
11
+ const readJson = async (jsonFileAbsPath)=>{
12
+ if (!await pathExists(jsonFileAbsPath)) return {};
13
+ const content = await fs.promises.readFile(jsonFileAbsPath, 'utf-8');
14
+ const json = json5.parse(content);
15
+ return json;
16
+ };
17
+ const readPackageJson = async (pkgJsonFilePath)=>readJson(pkgJsonFilePath);
18
+ const isDebug = 'rsbuild' === process.env.DEBUG || '*' === process.env.DEBUG;
8
19
  const PACKAGE_JSON = 'package.json';
9
20
  const DEBUG_LOG_TITLE = '[Rsbuild Workspace Dev Plugin]: ';
10
21
  const RSLIB_READY_MESSAGE = 'build complete, watching for changes';
@@ -43,6 +54,7 @@ class Logger {
43
54
  this.stdout = startBanner + this.stdout;
44
55
  }
45
56
  flushStdout() {
57
+ if (isDebug) return;
46
58
  this.setBanner(this.name);
47
59
  this.emitLog("stdout");
48
60
  }
@@ -61,20 +73,9 @@ class Logger {
61
73
  this.logTitle = DEBUG_LOG_TITLE;
62
74
  }
63
75
  }
64
- const debugLog = (msg)=>{
65
- const isDebug = 'rsbuild' === process.env.DEBUG || '*' === process.env.DEBUG;
66
- if (isDebug) console.log(DEBUG_LOG_TITLE + msg);
67
- };
68
- async function pathExists(path) {
69
- return fs.promises.access(path).then(()=>true).catch(()=>false);
70
- }
71
- const readJson = async (jsonFileAbsPath)=>{
72
- if (!await pathExists(jsonFileAbsPath)) return {};
73
- const content = await fs.promises.readFile(jsonFileAbsPath, 'utf-8');
74
- const json = json5.parse(content);
75
- return json;
76
+ const debugLog = (msg, prefix = DEBUG_LOG_TITLE)=>{
77
+ if (isDebug) console.log(prefix + msg);
76
78
  };
77
- const readPackageJson = async (pkgJsonFilePath)=>readJson(pkgJsonFilePath);
78
79
  function workspace_dev_define_property(obj, key, value) {
79
80
  if (key in obj) Object.defineProperty(obj, key, {
80
81
  value: value,
@@ -163,7 +164,6 @@ class WorkspaceDevRunner {
163
164
  this.visited[node] = true;
164
165
  this.visiting[node] = false;
165
166
  debugLog(`Skip visit node: ${node}`);
166
- logger.flushStdout();
167
167
  logger.emitLogOnce('stdout', `skip visit node: ${name}`);
168
168
  return this.start().then(()=>resolve());
169
169
  }
@@ -181,11 +181,9 @@ class WorkspaceDevRunner {
181
181
  });
182
182
  child.stdout.on('data', async (data)=>{
183
183
  const stdout = data.toString();
184
- if (this.matched[node]) {
185
- const content = data.toString().replace(/\n$/, '');
186
- logger.emitLogOnce('stdout', content);
187
- return;
188
- }
184
+ const content = data.toString().replace(/\n$/, '');
185
+ if (this.matched[node]) return void logger.emitLogOnce('stdout', content);
186
+ debugLog(content, `${name}: `);
189
187
  logger.appendLog('stdout', stdout);
190
188
  const match = config?.match;
191
189
  const matchResult = match ? match(stdout) : stdout.match(RSLIB_READY_MESSAGE) || stdout.match(MODERN_MODULE_READY_MESSAGE) || stdout.match(TSUP_READY_MESSAGE);
package/dist/logger.d.ts CHANGED
@@ -14,4 +14,4 @@ export declare class Logger {
14
14
  flushStdout(): void;
15
15
  static setEndBanner(): void;
16
16
  }
17
- export declare const debugLog: (msg: string) => void;
17
+ export declare const debugLog: (msg: string, prefix?: string) => void;
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import type { Package } from '@manypkg/get-packages';
2
2
  export declare const readJson: <T>(jsonFileAbsPath: string) => Promise<T>;
3
3
  export declare const readPackageJson: (pkgJsonFilePath: string) => Promise<Package["packageJson"]>;
4
+ export declare const isDebug: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rsbuild-plugin-workspace-dev",
3
- "version": "0.0.1-beta.0",
3
+ "version": "0.0.1-beta.1",
4
4
  "description": "An Rsbuild plugin to provides workspace recursive dev functionality for Monorepo topologies.",
5
5
  "repository": "https://github.com/rspack-contrib/rsbuild-plugin-workspace-dev",
6
6
  "license": "MIT",