mono-mjs 0.1.0
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 +313 -0
- package/README.zh-CN.md +313 -0
- package/bin/mono.mjs +235 -0
- package/package.json +40 -0
- package/src/hooks.mjs +247 -0
- package/src/loader.mjs +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# mono
|
|
4
|
+
|
|
5
|
+
**🚀 Zero-intrusion Monorepo Development Tool**
|
|
6
|
+
|
|
7
|
+
**Use TypeScript source code directly, no build required, no project restructuring needed**
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/mono)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](https://nodejs.org)
|
|
12
|
+
|
|
13
|
+
[English](./README.md) · [简体中文](./README.zh-CN.md)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## � Why mono?
|
|
20
|
+
|
|
21
|
+
### Zero Intrusion - Keep Your Project Clean
|
|
22
|
+
|
|
23
|
+
**Are you frustrated with:**
|
|
24
|
+
- ❌ Converting your npm project to pnpm workspace
|
|
25
|
+
- ❌ Dealing with `workspace:*` protocol complexities
|
|
26
|
+
- ❌ Creating workspace configuration files
|
|
27
|
+
- ❌ Restructuring your project for monorepo tools
|
|
28
|
+
|
|
29
|
+
**With mono:**
|
|
30
|
+
- ✅ **Zero Configuration** - No new files, works with any npm project
|
|
31
|
+
- ✅ **Non-Invasive** - Your project stays a clean npm project
|
|
32
|
+
- ✅ **Drop-in Replacement** - Use like `node` or `tsx`: `mono xxx.ts`
|
|
33
|
+
- ✅ **Auto Discovery** - Automatically finds local packages
|
|
34
|
+
- ✅ **Simple Config** - Optional `local` field in `package.json`, defaults to `./src/index.ts`
|
|
35
|
+
|
|
36
|
+
### Traditional vs mono
|
|
37
|
+
|
|
38
|
+
| Approach | Steps Required |
|
|
39
|
+
|----------|---------------|
|
|
40
|
+
| **pnpm workspace** | 1. Convert to pnpm<br/>2. Create `pnpm-workspace.yaml`<br/>3. Use `workspace:*` protocol<br/>4. Restructure project<br/>5. Build packages |
|
|
41
|
+
| **npm link** | 1. Link each package manually<br/>2. Build packages<br/>3. Rebuild on every change |
|
|
42
|
+
| **mono** ✨ | `mono ./src/index.ts` - **that's it!** |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## ✨ Key Features
|
|
47
|
+
|
|
48
|
+
- 🎯 **Zero Intrusion** - No project restructuring, no configuration files
|
|
49
|
+
- 🔍 **Auto Discovery** - Recursively scans and finds all local packages
|
|
50
|
+
- 📦 **Package Manager Agnostic** - Works with npm, yarn, pnpm, bun
|
|
51
|
+
- ⚡️ **Instant Reload** - Changes take effect immediately
|
|
52
|
+
- 🛠️ **Zero Config** - Default `./src/index.ts`, optional `local` field for custom paths
|
|
53
|
+
- 🌐 **ESM Only** - Built for modern JavaScript (`type: "module"`)
|
|
54
|
+
- 📝 **Config File** - Auto-generates `.mono/monoConfig.json` for debugging
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 📦 Installation
|
|
59
|
+
|
|
60
|
+
### Global Installation (Recommended)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install -g mono
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Requirements
|
|
67
|
+
|
|
68
|
+
- **Node.js** >= 18.19.0
|
|
69
|
+
- **ESM Projects** - Your `package.json` must have `"type": "module"`
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 🚀 Quick Start
|
|
74
|
+
|
|
75
|
+
### Basic Usage - Just Like `node` or `tsx`
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Run TypeScript file directly
|
|
79
|
+
mono ./src/index.ts
|
|
80
|
+
|
|
81
|
+
# Run with parameters
|
|
82
|
+
mono ./src/index.ts --port 3000
|
|
83
|
+
|
|
84
|
+
# Enable debug mode
|
|
85
|
+
mono ./src/index.ts debug
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Use in package.json
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"type": "module",
|
|
93
|
+
"scripts": {
|
|
94
|
+
"dev": "mono ./node_modules/vite/bin/vite.js",
|
|
95
|
+
"start": "mono ./src/index.ts"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**That's it!** No workspace configuration, no project restructuring.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 📚 How It Works
|
|
105
|
+
|
|
106
|
+
### 1. Auto Package Discovery
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Find farthest project root upward (.idea/.vscode/.git/package.json)
|
|
110
|
+
└── Recursive Scan
|
|
111
|
+
└── Find all package.json
|
|
112
|
+
└── Register by "name" field
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 2. Import Interception
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
// Your code
|
|
119
|
+
import { utils } from 'my-utils'
|
|
120
|
+
|
|
121
|
+
// mono automatically redirects to source
|
|
122
|
+
// → /path/to/my-utils/src/index.ts
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 3. Default Convention
|
|
126
|
+
|
|
127
|
+
- **Default Entry**: `./src/index.ts` for all packages
|
|
128
|
+
- **No Config Needed**: Works out of the box
|
|
129
|
+
|
|
130
|
+
### 4. Optional Custom Entry
|
|
131
|
+
|
|
132
|
+
If you need a different entry point, add `local` field:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"name": "my-package",
|
|
137
|
+
"local": "./src/main.ts"
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## ⚙️ Configuration
|
|
144
|
+
|
|
145
|
+
### Zero Config (Recommended)
|
|
146
|
+
|
|
147
|
+
Just use default `./src/index.ts`:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"name": "my-package",
|
|
152
|
+
"type": "module"
|
|
153
|
+
// No extra config needed!
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Custom Entry Point (Optional)
|
|
158
|
+
|
|
159
|
+
Add `local` field for custom paths:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"name": "@my-org/utils",
|
|
164
|
+
"type": "module",
|
|
165
|
+
"local": "./src/custom-entry.ts"
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**That's all the configuration you need!** Your project remains a standard npm project.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 🐛 Debugging
|
|
174
|
+
|
|
175
|
+
Five debug formats supported, parameter can be anywhere:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# Before file
|
|
179
|
+
mono --debug ./src/index.ts
|
|
180
|
+
mono -debug ./src/index.ts
|
|
181
|
+
mono -d ./src/index.ts
|
|
182
|
+
|
|
183
|
+
# After file (Recommended)
|
|
184
|
+
mono ./src/index.ts debug
|
|
185
|
+
mono ./src/index.ts d
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Or use environment variable:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Linux/macOS
|
|
192
|
+
MONO_DEBUG=1 mono ./src/index.ts
|
|
193
|
+
|
|
194
|
+
# Windows PowerShell
|
|
195
|
+
$env:MONO_DEBUG='1'; mono ./src/index.ts
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Debug logs output to:
|
|
199
|
+
- Console
|
|
200
|
+
- `mono-debug.log` file
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 📋 Common Use Cases
|
|
205
|
+
|
|
206
|
+
### With Vite
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"type": "module",
|
|
211
|
+
"scripts": {
|
|
212
|
+
"dev": "mono ./node_modules/vite/bin/vite.js"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### With Webpack
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{
|
|
221
|
+
"type": "module",
|
|
222
|
+
"scripts": {
|
|
223
|
+
"dev": "mono ./node_modules/webpack/bin/webpack.js serve"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Custom Build Scripts
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"type": "module",
|
|
233
|
+
"scripts": {
|
|
234
|
+
"build": "mono ./scripts/build.ts",
|
|
235
|
+
"codegen": "mono ./scripts/codegen.ts"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## ❓ FAQ
|
|
243
|
+
|
|
244
|
+
### Q: Why not use pnpm workspace?
|
|
245
|
+
|
|
246
|
+
**A:** pnpm workspace requires:
|
|
247
|
+
- Converting to pnpm
|
|
248
|
+
- Creating `pnpm-workspace.yaml`
|
|
249
|
+
- Using `workspace:*` protocol
|
|
250
|
+
- Restructuring project
|
|
251
|
+
|
|
252
|
+
**mono** requires: nothing! Keep your npm project as-is.
|
|
253
|
+
|
|
254
|
+
### Q: Why not use npm link?
|
|
255
|
+
|
|
256
|
+
**A:**
|
|
257
|
+
| Feature | mono | npm link |
|
|
258
|
+
|---------|------|----------|
|
|
259
|
+
| Setup | ✅ None | ❌ Manual per package |
|
|
260
|
+
| TypeScript | ✅ Direct source | ❌ Requires build |
|
|
261
|
+
| Hot Reload | ✅ Instant | ❌ Rebuild needed |
|
|
262
|
+
|
|
263
|
+
### Q: Does it modify my package.json?
|
|
264
|
+
|
|
265
|
+
**A:** No! The `local` field is optional. Your package.json stays clean.
|
|
266
|
+
|
|
267
|
+
### Q: What if I don't add `local` field?
|
|
268
|
+
|
|
269
|
+
**A:** It defaults to `./src/index.ts`. No configuration needed!
|
|
270
|
+
|
|
271
|
+
### Q: Will it affect production builds?
|
|
272
|
+
|
|
273
|
+
**A:** No. `mono` is for development only. Production uses `node_modules`.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 🔧 Technical Details
|
|
278
|
+
|
|
279
|
+
- **ESM Loader Hooks** - Uses Node.js native module resolution API
|
|
280
|
+
- **TypeScript Compiler** - Powered by `tsx` for latest syntax support
|
|
281
|
+
- **Zero Dependencies** - Only 2 runtime deps: `tsx` and `cross-spawn`
|
|
282
|
+
|
|
283
|
+
### ESM Only
|
|
284
|
+
|
|
285
|
+
`mono` requires `"type": "module"` in your `package.json`:
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"type": "module"
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## 📄 License
|
|
296
|
+
|
|
297
|
+
MIT © [alamhubb](https://github.com/alamhubb)
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 🔗 Related
|
|
302
|
+
|
|
303
|
+
- [vite-plugin-mono](../vite-plugin-mono) - Vite plugin for browser-side source development
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
<div align="center">
|
|
308
|
+
|
|
309
|
+
Made with ❤️ by [alamhubb](https://github.com/alamhubb)
|
|
310
|
+
|
|
311
|
+
[Report Bug](https://github.com/alamhubb/mono/issues) · [Request Feature](https://github.com/alamhubb/mono/issues)
|
|
312
|
+
|
|
313
|
+
</div>
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# mono
|
|
4
|
+
|
|
5
|
+
**🚀 零侵入式 Monorepo 开发工具**
|
|
6
|
+
|
|
7
|
+
**直接使用 TypeScript 源码开发,无需构建,无需改造项目**
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/mono)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](https://nodejs.org)
|
|
12
|
+
|
|
13
|
+
[English](./README.md) · [简体中文](./README.zh-CN.md)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 💡 为什么选择 mono?
|
|
20
|
+
|
|
21
|
+
### 零侵入 - 保持项目干净
|
|
22
|
+
|
|
23
|
+
**你是否曾为这些烦恼:**
|
|
24
|
+
- ❌ 需要将 npm 项目转换为 pnpm workspace
|
|
25
|
+
- ❌ 处理 `workspace:*` 协议的复杂性
|
|
26
|
+
- ❌ 创建各种 workspace 配置文件
|
|
27
|
+
- ❌ 为了 monorepo 工具而重构项目结构
|
|
28
|
+
|
|
29
|
+
**使用 mono:**
|
|
30
|
+
- ✅ **零配置** - 不创建任何新文件,兼容任何 npm 项目
|
|
31
|
+
- ✅ **无侵入** - 你的项目依然是一个干净的 npm 项目
|
|
32
|
+
- ✅ **即插即用** - 像使用 `node` 或 `tsx` 一样使用:`mono xxx.ts`
|
|
33
|
+
- ✅ **自动发现** - 自动查找本地包
|
|
34
|
+
- ✅ **简单配置** - 可选的 `local` 字段,默认 `./src/index.ts`
|
|
35
|
+
|
|
36
|
+
### 传统方式 vs mono
|
|
37
|
+
|
|
38
|
+
| 方式 | 需要的步骤 |
|
|
39
|
+
|------|-----------|
|
|
40
|
+
| **pnpm workspace** | 1. 转换为 pnpm<br/>2. 创建 `pnpm-workspace.yaml`<br/>3. 使用 `workspace:*` 协议<br/>4. 重构项目结构<br/>5. 构建包 |
|
|
41
|
+
| **npm link** | 1. 手动链接每个包<br/>2. 构建包<br/>3. 每次修改都要重新构建 |
|
|
42
|
+
| **mono** ✨ | `mono ./src/index.ts` - **就这么简单!** |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## ✨ 核心特性
|
|
47
|
+
|
|
48
|
+
- 🎯 **零侵入** - 无需重构项目,无需配置文件
|
|
49
|
+
- 🔍 **自动发现** - 递归扫描并找到所有本地包
|
|
50
|
+
- 📦 **包管理器无关** - 兼容 npm、yarn、pnpm、bun
|
|
51
|
+
- ⚡️ **即时加载** - 修改立即生效
|
|
52
|
+
- 🛠️ **零配置** - 默认 `./src/index.ts`,可选 `local` 字段自定义路径
|
|
53
|
+
- 🌐 **仅支持 ESM** - 为现代 JavaScript 设计(`type: "module"`)
|
|
54
|
+
- 📝 **配置文件** - 自动生成 `.mono/monoConfig.json` 用于调试
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 📦 安装
|
|
59
|
+
|
|
60
|
+
### 全局安装(推荐)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install -g mono
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 环境要求
|
|
67
|
+
|
|
68
|
+
- **Node.js** >= 18.19.0
|
|
69
|
+
- **ESM 项目** - 你的 `package.json` 必须包含 `"type": "module"`
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 🚀 快速开始
|
|
74
|
+
|
|
75
|
+
### 基本用法 - 就像 `node` 或 `tsx` 一样
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# 直接运行 TypeScript 文件
|
|
79
|
+
mono ./src/index.ts
|
|
80
|
+
|
|
81
|
+
# 带参数运行
|
|
82
|
+
mono ./src/index.ts --port 3000
|
|
83
|
+
|
|
84
|
+
# 开启调试模式
|
|
85
|
+
mono ./src/index.ts debug
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 在 package.json 中使用
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"type": "module",
|
|
93
|
+
"scripts": {
|
|
94
|
+
"dev": "mono ./node_modules/vite/bin/vite.js",
|
|
95
|
+
"start": "mono ./src/index.ts"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**就这样!** 无需 workspace 配置,无需重构项目。
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 📚 工作原理
|
|
105
|
+
|
|
106
|
+
### 1. 自动包发现
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
直线向上查找距离最远的项目根目录 (.idea/.vscode/.git/package.json)
|
|
110
|
+
└── 递归扫描
|
|
111
|
+
└── 查找所有 package.json
|
|
112
|
+
└── 根据 "name" 字段注册
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 2. 导入拦截
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
// 你的代码
|
|
119
|
+
import { utils } from 'my-utils'
|
|
120
|
+
|
|
121
|
+
// mono 自动重定向到源码
|
|
122
|
+
// → /path/to/my-utils/src/index.ts
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 3. 默认约定
|
|
126
|
+
|
|
127
|
+
- **默认入口**:所有包默认 `./src/index.ts`
|
|
128
|
+
- **无需配置**:开箱即用
|
|
129
|
+
|
|
130
|
+
### 4. 可选的自定义入口
|
|
131
|
+
|
|
132
|
+
如果需要不同的入口点,添加 `local` 字段:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"name": "my-package",
|
|
137
|
+
"local": "./src/main.ts"
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## ⚙️ 配置
|
|
144
|
+
|
|
145
|
+
### 零配置(推荐)
|
|
146
|
+
|
|
147
|
+
直接使用默认的 `./src/index.ts`:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"name": "my-package",
|
|
152
|
+
"type": "module"
|
|
153
|
+
// 无需额外配置!
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 自定义入口(可选)
|
|
158
|
+
|
|
159
|
+
添加 `local` 字段指定自定义路径:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"name": "@my-org/utils",
|
|
164
|
+
"type": "module",
|
|
165
|
+
"local": "./src/custom-entry.ts"
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**这就是你需要的全部配置!** 你的项目依然是标准的 npm 项目。
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 🐛 调试
|
|
174
|
+
|
|
175
|
+
支持 5 种调试格式,参数可以放在任意位置:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# 参数在文件前面
|
|
179
|
+
mono --debug ./src/index.ts
|
|
180
|
+
mono -debug ./src/index.ts
|
|
181
|
+
mono -d ./src/index.ts
|
|
182
|
+
|
|
183
|
+
# 参数在文件后面(推荐)
|
|
184
|
+
mono ./src/index.ts debug
|
|
185
|
+
mono ./src/index.ts d
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
或使用环境变量:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Linux/macOS
|
|
192
|
+
MONO_DEBUG=1 mono ./src/index.ts
|
|
193
|
+
|
|
194
|
+
# Windows PowerShell
|
|
195
|
+
$env:MONO_DEBUG='1'; mono ./src/index.ts
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
调试日志输出到:
|
|
199
|
+
- 控制台
|
|
200
|
+
- `mono-debug.log` 文件
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 📋 常见使用场景
|
|
205
|
+
|
|
206
|
+
### 配合 Vite
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"type": "module",
|
|
211
|
+
"scripts": {
|
|
212
|
+
"dev": "mono ./node_modules/vite/bin/vite.js"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 配合 Webpack
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{
|
|
221
|
+
"type": "module",
|
|
222
|
+
"scripts": {
|
|
223
|
+
"dev": "mono ./node_modules/webpack/bin/webpack.js serve"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 自定义构建脚本
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"type": "module",
|
|
233
|
+
"scripts": {
|
|
234
|
+
"build": "mono ./scripts/build.ts",
|
|
235
|
+
"codegen": "mono ./scripts/codegen.ts"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## ❓ 常见问题
|
|
243
|
+
|
|
244
|
+
### Q: 为什么不用 pnpm workspace?
|
|
245
|
+
|
|
246
|
+
**A:** pnpm workspace 需要:
|
|
247
|
+
- 转换为 pnpm
|
|
248
|
+
- 创建 `pnpm-workspace.yaml`
|
|
249
|
+
- 使用 `workspace:*` 协议
|
|
250
|
+
- 重构项目结构
|
|
251
|
+
|
|
252
|
+
**mono** 需要:什么都不需要!保持你的 npm 项目原样。
|
|
253
|
+
|
|
254
|
+
### Q: 为什么不用 npm link?
|
|
255
|
+
|
|
256
|
+
**A:**
|
|
257
|
+
| 特性 | mono | npm link |
|
|
258
|
+
|------|------|----------|
|
|
259
|
+
| 设置 | ✅ 无需设置 | ❌ 每个包都要手动链接 |
|
|
260
|
+
| TypeScript | ✅ 直接使用源码 | ❌ 需要构建 |
|
|
261
|
+
| 热更新 | ✅ 即时生效 | ❌ 需要重新构建 |
|
|
262
|
+
|
|
263
|
+
### Q: 它会修改我的 package.json 吗?
|
|
264
|
+
|
|
265
|
+
**A:** 不会!`local` 字段是可选的。你的 package.json 保持干净。
|
|
266
|
+
|
|
267
|
+
### Q: 如果我不添加 `local` 字段呢?
|
|
268
|
+
|
|
269
|
+
**A:** 默认使用 `./src/index.ts`。无需任何配置!
|
|
270
|
+
|
|
271
|
+
### Q: 会影响生产构建吗?
|
|
272
|
+
|
|
273
|
+
**A:** 不会。`mono` 仅用于开发。生产环境使用 `node_modules`。
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 🔧 技术细节
|
|
278
|
+
|
|
279
|
+
- **ESM Loader Hooks** - 使用 Node.js 原生模块解析 API
|
|
280
|
+
- **TypeScript 编译器** - 基于 `tsx`,支持最新语法
|
|
281
|
+
- **零依赖** - 仅 2 个运行时依赖:`tsx` 和 `cross-spawn`
|
|
282
|
+
|
|
283
|
+
### 仅支持 ESM
|
|
284
|
+
|
|
285
|
+
`mono` 要求 `package.json` 中包含 `"type": "module"`:
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"type": "module"
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## 📄 License
|
|
296
|
+
|
|
297
|
+
MIT © [alamhubb](https://github.com/alamhubb)
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 🔗 相关项目
|
|
302
|
+
|
|
303
|
+
- [vite-plugin-mono](../vite-plugin-mono) - 用于浏览器端源码开发的 Vite 插件
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
<div align="center">
|
|
308
|
+
|
|
309
|
+
Made with ❤️ by [alamhubb](https://github.com/alamhubb)
|
|
310
|
+
|
|
311
|
+
[报告 Bug](https://github.com/alamhubb/mono/issues) · [请求功能](https://github.com/alamhubb/mono/issues)
|
|
312
|
+
|
|
313
|
+
</div>
|
package/bin/mono.mjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* mono CLI 入口
|
|
5
|
+
*
|
|
6
|
+
* 命令:
|
|
7
|
+
* - mono <file> 运行 TypeScript 文件,注入自定义 loader
|
|
8
|
+
* - mono --debug <file> 开启调试模式运行
|
|
9
|
+
* - mono i 递归安装所有嵌套 monorepo 的依赖
|
|
10
|
+
*
|
|
11
|
+
* loader.mjs 会自动:
|
|
12
|
+
* 1. 注册 tsx ESM loader(TypeScript 支持)
|
|
13
|
+
* 2. 注册 mono resolve hooks(包名拦截)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import spawn from 'cross-spawn';
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
18
|
+
import { dirname, join, resolve } from 'node:path';
|
|
19
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
20
|
+
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = dirname(__filename);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 获取 loader 文件路径(file:// URL 格式)
|
|
26
|
+
*/
|
|
27
|
+
function getLoaderUrl() {
|
|
28
|
+
const loaderPath = join(__dirname, '..', 'src', 'loader.mjs');
|
|
29
|
+
return pathToFileURL(loaderPath).href;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 递归查找所有嵌套的 monorepo 目录
|
|
34
|
+
*/
|
|
35
|
+
function findAllMonorepos(startDir) {
|
|
36
|
+
const monorepos = [];
|
|
37
|
+
|
|
38
|
+
function scanDir(dir) {
|
|
39
|
+
const pkgPath = join(dir, 'package.json');
|
|
40
|
+
|
|
41
|
+
if (existsSync(pkgPath)) {
|
|
42
|
+
try {
|
|
43
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
44
|
+
|
|
45
|
+
// 如果有 workspaces 配置,这是一个 monorepo
|
|
46
|
+
if (pkg.workspaces) {
|
|
47
|
+
monorepos.push(dir);
|
|
48
|
+
|
|
49
|
+
// 继续查找嵌套的 workspace
|
|
50
|
+
let patterns = [];
|
|
51
|
+
if (Array.isArray(pkg.workspaces)) {
|
|
52
|
+
patterns = pkg.workspaces;
|
|
53
|
+
} else if (pkg.workspaces?.packages) {
|
|
54
|
+
patterns = pkg.workspaces.packages;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const pattern of patterns) {
|
|
58
|
+
const dirs = findMatchingDirs(dir, pattern);
|
|
59
|
+
for (const subDir of dirs) {
|
|
60
|
+
scanDir(subDir);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// 忽略解析错误
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
scanDir(startDir);
|
|
71
|
+
return monorepos;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 查找匹配 pattern 的目录
|
|
76
|
+
*/
|
|
77
|
+
function findMatchingDirs(root, pattern) {
|
|
78
|
+
const dirs = [];
|
|
79
|
+
|
|
80
|
+
if (pattern.endsWith('/*')) {
|
|
81
|
+
const baseDir = join(root, pattern.slice(0, -2));
|
|
82
|
+
if (existsSync(baseDir)) {
|
|
83
|
+
const entries = readdirSync(baseDir, { withFileTypes: true });
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (entry.isDirectory() && entry.name !== 'node_modules') {
|
|
86
|
+
dirs.push(join(baseDir, entry.name));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} else if (pattern.endsWith('/**')) {
|
|
91
|
+
const baseDir = join(root, pattern.slice(0, -3));
|
|
92
|
+
findDirsRecursive(baseDir, dirs);
|
|
93
|
+
} else {
|
|
94
|
+
const dir = join(root, pattern);
|
|
95
|
+
if (existsSync(dir)) {
|
|
96
|
+
dirs.push(dir);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return dirs;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 递归查找目录
|
|
105
|
+
*/
|
|
106
|
+
function findDirsRecursive(dir, result) {
|
|
107
|
+
if (!existsSync(dir)) return;
|
|
108
|
+
|
|
109
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
if (entry.isDirectory() && entry.name !== 'node_modules') {
|
|
112
|
+
const subDir = join(dir, entry.name);
|
|
113
|
+
result.push(subDir);
|
|
114
|
+
findDirsRecursive(subDir, result);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 在指定目录执行 npm install
|
|
121
|
+
*/
|
|
122
|
+
async function runNpmInstall(dir) {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
console.log(`\n[mono i] Installing in: ${dir}`);
|
|
125
|
+
|
|
126
|
+
const child = spawn('npm', ['install'], {
|
|
127
|
+
cwd: dir,
|
|
128
|
+
stdio: 'inherit'
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
child.on('close', (code) => {
|
|
132
|
+
if (code === 0) {
|
|
133
|
+
resolve();
|
|
134
|
+
} else {
|
|
135
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
child.on('error', reject);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* mono i - 递归安装所有嵌套 monorepo 的依赖
|
|
145
|
+
*/
|
|
146
|
+
async function installCommand() {
|
|
147
|
+
const cwd = process.cwd();
|
|
148
|
+
console.log('[mono i] 扫描嵌套 monorepo...');
|
|
149
|
+
|
|
150
|
+
const monorepos = findAllMonorepos(cwd);
|
|
151
|
+
|
|
152
|
+
if (monorepos.length === 0) {
|
|
153
|
+
console.log('[mono i] 未找到任何 monorepo');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(`[mono i] 找到 ${monorepos.length} 个 monorepo:`);
|
|
158
|
+
for (const dir of monorepos) {
|
|
159
|
+
console.log(` - ${dir}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 从外到内安装(父级先安装)
|
|
163
|
+
for (const dir of monorepos) {
|
|
164
|
+
try {
|
|
165
|
+
await runNpmInstall(dir);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error(`[mono i] 安装失败: ${dir}`, err.message);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log('\n[mono i] ✅ 所有 monorepo 安装完成!');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 运行 TypeScript 文件
|
|
177
|
+
*/
|
|
178
|
+
function runCommand(args, debug = false) {
|
|
179
|
+
const loaderUrl = getLoaderUrl();
|
|
180
|
+
|
|
181
|
+
const nodeArgs = [
|
|
182
|
+
`--import=${loaderUrl}`,
|
|
183
|
+
...args
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
// 如果开启 debug,设置环境变量
|
|
187
|
+
const env = { ...process.env };
|
|
188
|
+
if (debug) {
|
|
189
|
+
env.MONO_DEBUG = '1';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const child = spawn('node', nodeArgs, {
|
|
193
|
+
stdio: 'inherit',
|
|
194
|
+
env
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
child.on('close', (code) => {
|
|
198
|
+
process.exit(code ?? 0);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
child.on('error', (err) => {
|
|
202
|
+
console.error('mono: 无法启动 node 进程', err.message);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 主函数
|
|
209
|
+
*/
|
|
210
|
+
async function main() {
|
|
211
|
+
const args = process.argv.slice(2);
|
|
212
|
+
|
|
213
|
+
// 检查调试参数(支持多种格式,可在任意位置)
|
|
214
|
+
let debug = false;
|
|
215
|
+
const filteredArgs = [];
|
|
216
|
+
|
|
217
|
+
for (const arg of args) {
|
|
218
|
+
if (arg === '--debug' || arg === '-debug' || arg === '-d' || arg === 'debug' || arg === 'd') {
|
|
219
|
+
debug = true;
|
|
220
|
+
} else {
|
|
221
|
+
filteredArgs.push(arg);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 检查是否是 install 命令
|
|
226
|
+
if (filteredArgs[0] === 'i' || filteredArgs[0] === 'install') {
|
|
227
|
+
await installCommand();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 否则运行 TypeScript 文件
|
|
232
|
+
runCommand(filteredArgs, debug);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mono-mjs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool for monorepo - auto-discover local packages and use source code in Node.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mono": "./bin/mono.mjs"
|
|
8
|
+
},
|
|
9
|
+
"main": "./src/hooks.mjs",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/hooks.mjs",
|
|
12
|
+
"./hooks": "./src/hooks.mjs",
|
|
13
|
+
"./loader": "./src/loader.mjs"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin",
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node test/test.mjs"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"monorepo",
|
|
24
|
+
"cli",
|
|
25
|
+
"node",
|
|
26
|
+
"loader",
|
|
27
|
+
"workspace",
|
|
28
|
+
"esm",
|
|
29
|
+
"typescript"
|
|
30
|
+
],
|
|
31
|
+
"author": "alamhubb",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.19.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"cross-spawn": "^7.0.6",
|
|
38
|
+
"tsx": "^4.21.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/hooks.mjs
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESM Loader Hooks - 被 register() 加载的 hooks 模块
|
|
3
|
+
* 拦截模块解析,对项目中的本地包应用源码入口
|
|
4
|
+
*
|
|
5
|
+
* 新逻辑:
|
|
6
|
+
* - 向上查找最顶层的包含 .idea / .vscode / package.json 的目录作为根目录
|
|
7
|
+
* - 从根目录递归向下查找所有有 package.json 的项目
|
|
8
|
+
* - 根据 package.json 的 name 字段注册
|
|
9
|
+
* - 默认使用 src/index.ts,如果配置了 local 字段则使用配置的
|
|
10
|
+
*
|
|
11
|
+
* 注意:此文件使用纯 JavaScript,以便 Node.js 原生加载
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, existsSync, readdirSync, appendFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
15
|
+
import { resolve as pathResolve, dirname, join } from 'node:path';
|
|
16
|
+
import { pathToFileURL } from 'node:url';
|
|
17
|
+
|
|
18
|
+
// 根目录标识符列表
|
|
19
|
+
const ROOT_MARKERS = ['.idea', '.vscode', '.git', 'package.json'];
|
|
20
|
+
|
|
21
|
+
// 默认源码入口
|
|
22
|
+
const DEFAULT_ENTRY = './src/index.ts';
|
|
23
|
+
|
|
24
|
+
// 调试日志文件
|
|
25
|
+
const DEBUG_LOG = join(process.cwd(), 'mono-debug.log');
|
|
26
|
+
|
|
27
|
+
// 是否启用调试日志(设置环境变量 MONO_DEBUG=1 开启)
|
|
28
|
+
const DEBUG_ENABLED = process.env.MONO_DEBUG === '1';
|
|
29
|
+
|
|
30
|
+
function debugLog(msg) {
|
|
31
|
+
if (!DEBUG_ENABLED) return;
|
|
32
|
+
// 同时输出到控制台和文件
|
|
33
|
+
console.log(msg);
|
|
34
|
+
try {
|
|
35
|
+
appendFileSync(DEBUG_LOG, `${new Date().toISOString()} ${msg}\n`);
|
|
36
|
+
} catch { }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (DEBUG_ENABLED) {
|
|
40
|
+
debugLog('[mono hooks] hooks.mjs 已加载');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 缓存:workspace 包信息
|
|
44
|
+
let workspacePackages = null;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 检查目录是否包含根目录标识
|
|
48
|
+
*/
|
|
49
|
+
function hasRootMarker(dir) {
|
|
50
|
+
for (const marker of ROOT_MARKERS) {
|
|
51
|
+
if (existsSync(join(dir, marker))) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 向上查找最顶层的根目录
|
|
60
|
+
* 找到最顶层的包含 .idea / .vscode / package.json 的目录
|
|
61
|
+
*/
|
|
62
|
+
function findProjectRoot(startDir) {
|
|
63
|
+
let currentDir = pathResolve(startDir);
|
|
64
|
+
let topMostRoot = null;
|
|
65
|
+
|
|
66
|
+
while (currentDir !== dirname(currentDir)) {
|
|
67
|
+
if (hasRootMarker(currentDir)) {
|
|
68
|
+
topMostRoot = currentDir;
|
|
69
|
+
}
|
|
70
|
+
currentDir = dirname(currentDir);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return topMostRoot;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 递归向下查找所有有 package.json 的项目
|
|
78
|
+
*/
|
|
79
|
+
function findAllPackages(rootDir, packages) {
|
|
80
|
+
// 跳过路径中包含 node_modules 的目录
|
|
81
|
+
if (rootDir.includes('node_modules')) {
|
|
82
|
+
debugLog(`[mono] 跳过 node_modules: ${rootDir}`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const pkgPath = join(rootDir, 'package.json');
|
|
87
|
+
|
|
88
|
+
if (existsSync(pkgPath)) {
|
|
89
|
+
try {
|
|
90
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
91
|
+
|
|
92
|
+
if (pkg.name) {
|
|
93
|
+
// 确定入口:优先使用 local 配置,否则使用默认值
|
|
94
|
+
const entry = typeof pkg.local === 'string' ? pkg.local : DEFAULT_ENTRY;
|
|
95
|
+
|
|
96
|
+
packages.set(pkg.name, {
|
|
97
|
+
name: pkg.name,
|
|
98
|
+
dir: rootDir,
|
|
99
|
+
localEntry: entry
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
debugLog(`[mono] 发现包: ${pkg.name} -> ${entry}`);
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// 忽略解析错误
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 递归查找子目录
|
|
110
|
+
try {
|
|
111
|
+
const entries = readdirSync(rootDir, { withFileTypes: true });
|
|
112
|
+
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
115
|
+
findAllPackages(join(rootDir, entry.name), packages);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// 忽略读取错误
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 初始化 workspace 信息
|
|
125
|
+
* 从根目录递归向下收集所有包
|
|
126
|
+
*/
|
|
127
|
+
function initWorkspace() {
|
|
128
|
+
if (workspacePackages !== null) return;
|
|
129
|
+
|
|
130
|
+
workspacePackages = new Map();
|
|
131
|
+
|
|
132
|
+
// 从当前工作目录开始,向上找最顶层根目录
|
|
133
|
+
const cwd = process.cwd();
|
|
134
|
+
const projectRoot = findProjectRoot(cwd);
|
|
135
|
+
|
|
136
|
+
if (!projectRoot) {
|
|
137
|
+
debugLog('[mono] 未找到项目根目录');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
debugLog(`[mono] 项目根目录: ${projectRoot}`);
|
|
142
|
+
|
|
143
|
+
// 从根目录递归向下查找所有包
|
|
144
|
+
findAllPackages(projectRoot, workspacePackages);
|
|
145
|
+
|
|
146
|
+
debugLog(`[mono] 共发现 ${workspacePackages.size} 个包`);
|
|
147
|
+
|
|
148
|
+
// 生成配置文件
|
|
149
|
+
writeMonoConfig(cwd);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 生成 monoConfig.json 配置文件
|
|
154
|
+
* 记录包名与文件地址的映射关系
|
|
155
|
+
*
|
|
156
|
+
* @param {string} cwd 当前工作目录
|
|
157
|
+
*/
|
|
158
|
+
function writeMonoConfig(cwd) {
|
|
159
|
+
const monoDir = join(cwd, '.mono');
|
|
160
|
+
const configPath = join(monoDir, 'monoConfig.json');
|
|
161
|
+
|
|
162
|
+
// 确保 mono 目录存在
|
|
163
|
+
if (!existsSync(monoDir)) {
|
|
164
|
+
try {
|
|
165
|
+
mkdirSync(monoDir, { recursive: true });
|
|
166
|
+
} catch (err) {
|
|
167
|
+
debugLog(`[mono] 创建目录失败: ${err.message}`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 构建包映射
|
|
173
|
+
const config = {};
|
|
174
|
+
for (const [name, pkg] of workspacePackages) {
|
|
175
|
+
const entryPath = join(pkg.dir, pkg.localEntry);
|
|
176
|
+
config[name] = entryPath;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 写入配置文件
|
|
180
|
+
try {
|
|
181
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
182
|
+
debugLog(`[mono] 已生成配置文件: ${configPath}`);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
debugLog(`[mono] 写入配置文件失败: ${err.message}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 判断是否是包的主入口导入(不是子路径导入)
|
|
190
|
+
*/
|
|
191
|
+
function isMainEntryImport(specifier) {
|
|
192
|
+
// 跳过相对路径和绝对路径
|
|
193
|
+
if (specifier.startsWith('.') || specifier.startsWith('/')) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 跳过 node: 和其他协议
|
|
198
|
+
if (specifier.includes(':')) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 处理 scoped 包名 @scope/name
|
|
203
|
+
if (specifier.startsWith('@')) {
|
|
204
|
+
const parts = specifier.split('/');
|
|
205
|
+
// @scope/name 正好两部分是主入口
|
|
206
|
+
// @scope/name/sub 三部分及以上是子路径
|
|
207
|
+
return parts.length === 2;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 普通包名 name
|
|
211
|
+
// name 是主入口,name/sub 是子路径
|
|
212
|
+
return !specifier.includes('/');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* ESM Loader: resolve hook
|
|
217
|
+
* 拦截模块解析
|
|
218
|
+
*/
|
|
219
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
220
|
+
// 初始化 workspace 信息(只初始化一次)
|
|
221
|
+
initWorkspace();
|
|
222
|
+
|
|
223
|
+
// 只处理主入口导入
|
|
224
|
+
if (!isMainEntryImport(specifier)) {
|
|
225
|
+
return nextResolve(specifier, context);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 检查是否是本地包
|
|
229
|
+
const pkg = workspacePackages?.get(specifier);
|
|
230
|
+
|
|
231
|
+
if (!pkg) {
|
|
232
|
+
// 不是本地包,使用默认解析
|
|
233
|
+
return nextResolve(specifier, context);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 构造新的入口路径
|
|
237
|
+
const newEntry = join(pkg.dir, pkg.localEntry);
|
|
238
|
+
const newUrl = pathToFileURL(newEntry).href;
|
|
239
|
+
|
|
240
|
+
// 日志:显示拦截的包
|
|
241
|
+
debugLog(`[mono] 拦截: ${specifier} -> ${pkg.localEntry}`);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
url: newUrl,
|
|
245
|
+
shortCircuit: true
|
|
246
|
+
};
|
|
247
|
+
}
|
package/src/loader.mjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESM Loader 入口
|
|
3
|
+
* 使用 register() API 注册自定义 hooks
|
|
4
|
+
*
|
|
5
|
+
* 用法: node --import=./src/loader.mjs app.ts
|
|
6
|
+
*
|
|
7
|
+
* 功能:
|
|
8
|
+
* 1. 注册 tsx 的 ESM loader 以支持 TypeScript 编译
|
|
9
|
+
* 2. 注册 mono 的 resolve hooks 以拦截包名解析到源码
|
|
10
|
+
*
|
|
11
|
+
* 注册顺序说明:
|
|
12
|
+
* Node.js loader hooks 使用洋葱模型,后注册的先被调用。
|
|
13
|
+
* 我们希望 mono 先拦截包名,再由 tsx 编译 .ts 文件。
|
|
14
|
+
* 所以先注册 tsx,再注册 mono(后注册先调用)。
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { register } from 'node:module';
|
|
18
|
+
import { pathToFileURL } from 'node:url';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = dirname(__filename);
|
|
24
|
+
|
|
25
|
+
// 是否启用调试日志(设置环境变量 MONO_DEBUG=1 开启)
|
|
26
|
+
const DEBUG_ENABLED = process.env.MONO_DEBUG === '1';
|
|
27
|
+
|
|
28
|
+
if (DEBUG_ENABLED) console.log('[mono] 加载 loader.mjs');
|
|
29
|
+
|
|
30
|
+
// 1. 先注册 tsx 的 ESM loader(TypeScript 编译)
|
|
31
|
+
if (DEBUG_ENABLED) console.log('[mono] 注册 tsx ESM loader...');
|
|
32
|
+
await import('tsx/esm');
|
|
33
|
+
|
|
34
|
+
// 2. 再注册 mono 的 resolve hooks(包名拦截)
|
|
35
|
+
// 后注册先调用,所以 mono 会先于 tsx 处理 resolve
|
|
36
|
+
const hooksPath = join(__dirname, 'hooks.mjs');
|
|
37
|
+
if (DEBUG_ENABLED) console.log('[mono] 注册 mono hooks:', hooksPath);
|
|
38
|
+
register(pathToFileURL(hooksPath).href, import.meta.url);
|
|
39
|
+
|