react-dep-galaxy 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 [scyprodigy]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # 🌌 React Galaxy
2
+
3
+ > A React component dependency visualizer that turns your codebase into an interactive galaxy.
4
+
5
+ [🚀 Live Demo](https://scyprodigy.github.io/react-galaxy/)
6
+
7
+ ---
8
+
9
+ ## 🖼️ Preview
10
+
11
+ > Live demo: 12 components, node size represents line count, edges represent dependency direction.
12
+
13
+ ---
14
+
15
+ ## ✨ Features
16
+
17
+ - 🧠 **Inspired by LLM Neuroanatomy**
18
+ Visualize your component architecture as a neural network — spot shared components and dependency chains at a glance.
19
+
20
+ - 📐 **Orthogonal Metrics (lines vs. dependencies)**
21
+ Node size = line count; edge count = number of dependencies. Understand both scale and complexity in one view.
22
+
23
+ - ⚡ **Real-time Interaction**
24
+ Drag, zoom, hover to inspect component details (path, line count, dependency list), and click to trace dependency chains.
25
+
26
+ ---
27
+
28
+ ## 🚀 Quick Start
29
+
30
+ ### Using npx (no install required)
31
+
32
+ ```bash
33
+ npx react-dep-galaxy scan ./src
34
+ npx react-dep-galaxy view
35
+ ```
36
+
37
+ ### Global install
38
+
39
+ ```bash
40
+ npm install -g react-dep-galaxy
41
+ galaxy scan ./src
42
+ galaxy view
43
+ ```
44
+
45
+ Open your browser at `http://localhost:3000`
46
+
47
+ > 💡 Running `scan` generates a `galaxy.json` file. `view` starts a local server that reads it.
48
+
49
+ ---
50
+
51
+ ## 📦 How It Works
52
+
53
+ ### 1️⃣ Scan Phase
54
+
55
+ - Parses your React project (supports `.jsx`, `.tsx`, `.js`)
56
+ - Detects function components and class components
57
+ - Builds a component dependency graph from imports and JSX tag usage
58
+ - Outputs `galaxy.json`
59
+
60
+ ### 2️⃣ View Phase
61
+
62
+ - Starts a local HTTP server
63
+ - Renders a force-directed graph using `vis-network`
64
+ - Node size and color reflect line count and dependency depth
65
+
66
+ ---
67
+
68
+ ## 📁 Output Example
69
+
70
+ ```json
71
+ [
72
+ {
73
+ "name": "App",
74
+ "path": "src/App.jsx",
75
+ "lines": 24,
76
+ "dependencies": ["Header", "MainContent", "Footer"]
77
+ }
78
+ ]
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 🛠️ Development
84
+
85
+ ```bash
86
+ git clone https://github.com/<your-username>/react-galaxy.git
87
+ cd react-galaxy
88
+ npm install
89
+ npm link # Links the `galaxy` command globally
90
+ ```
91
+
92
+ ---
93
+
94
+ ## 🚀 Deploy to GitHub Pages
95
+
96
+ Static demo files are located in the `docs/` directory. To publish:
97
+
98
+ 1. Go to your repo → **Settings → Pages**
99
+ 2. Set Branch: `main`, Folder: `/docs`
100
+ 3. Click **Save**
101
+
102
+ Your demo will be live at `https://[yourName].github.io/react-galaxy/` within a minute.
103
+
104
+ ---
105
+
106
+ ## 🤝 Contributing
107
+
108
+ All contributions are welcome 🙌
109
+
110
+ 1. Fork the repo
111
+ 2. Create a branch: `git checkout -b feature/amazing-feature`
112
+ 3. Commit your changes: `git commit -m "feat: add amazing feature"`
113
+ 4. Push the branch: `git push origin feature/amazing-feature`
114
+ 5. Open a Pull Request
115
+
116
+ ---
117
+
118
+ ## 🧭 Roadmap
119
+
120
+ - [ ] Precise AST parsing (TypeScript, HOC support)
121
+ - [ ] Plugin system (custom metrics and output formats)
122
+ - [ ] AI-assisted analysis (LLM-powered component complexity detection)
123
+ - [ ] Remote project analysis (upload code or link a GitHub repo)
124
+
125
+ ---
126
+
127
+ ## 📄 License
128
+
129
+ MIT License © 2026 [scyprodigy]
@@ -0,0 +1,130 @@
1
+ # 🌌 React Galaxy
2
+
3
+ > A React component dependency visualizer that turns your codebase into an interactive galaxy.
4
+ > 一個將 React 元件依賴關係轉化為「星系圖」的可視化工具。
5
+
6
+ [🚀 Live Demo](https://scyprodigy.github.io/react-galaxy/)
7
+
8
+ ---
9
+
10
+ ## 🖼️ Preview
11
+
12
+ > 實際展示:12 個元件的依賴關係,節點大小代表程式碼行數,連線表示依賴方向。
13
+
14
+ ---
15
+
16
+ ## ✨ Features
17
+
18
+ - 🧠 **Inspired by LLM Neuroanatomy**
19
+ 將元件架構視覺化為神經網絡般的結構,一眼看出共用元件與依賴鏈。
20
+
21
+ - 📐 **Orthogonal Metrics(行數 vs 依賴數)**
22
+ 節點大小 = 程式碼行數;連線數 = 依賴數量。同時掌握規模與複雜度。
23
+
24
+ - ⚡ **Real-time Interaction**
25
+ 拖曳、縮放;滑鼠懸停查看元件資訊(路徑、行數、依賴列表);點擊追蹤依賴關係。
26
+
27
+ ---
28
+
29
+ ## 🚀 Quick Start
30
+
31
+ ### 使用 npx(無需安裝)
32
+
33
+ ```bash
34
+ npx react-galaxy scan ./src
35
+ npx react-galaxy view
36
+ ```
37
+
38
+ ### 本地安裝後使用
39
+
40
+ ```bash
41
+ npm install -g react-galaxy
42
+ galaxy scan ./src
43
+ galaxy view
44
+ ```
45
+
46
+ 打開瀏覽器:`http://localhost:3000`
47
+
48
+ > 💡 第一次執行 `scan` 會產生 `galaxy.json`,`view` 會啟動伺服器讀取該檔案。
49
+
50
+ ---
51
+
52
+ ## 📦 How It Works
53
+
54
+ ### 1️⃣ Scan Phase
55
+
56
+ - 解析 React 專案(支援 `.jsx`、`.tsx`、`.js`)
57
+ - 識別函式元件與類別元件
58
+ - 建立元件依賴圖(基於 import 與 JSX 標籤)
59
+ - 輸出 `galaxy.json`
60
+
61
+ ### 2️⃣ View Phase
62
+
63
+ - 啟動本地 HTTP 伺服器
64
+ - 使用 `vis-network` 渲染力導向圖
65
+ - 節點大小、顏色對應行數與依賴數量
66
+
67
+ ---
68
+
69
+ ## 📁 Output Example
70
+
71
+ ```json
72
+ [
73
+ {
74
+ "name": "App",
75
+ "path": "src/App.jsx",
76
+ "lines": 24,
77
+ "dependencies": ["Header", "MainContent", "Footer"]
78
+ }
79
+ ]
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 🛠️ Development
85
+
86
+ ```bash
87
+ git clone https://github.com/<你的用戶名>/react-galaxy.git
88
+ cd react-galaxy
89
+ npm install
90
+ npm link # 將指令 `galaxy` 連結到全域
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 🚀 Deploy to GitHub Pages
96
+
97
+ 靜態展示檔案已放在 `docs/` 目錄,只需在倉庫設定中開啟 Pages:
98
+
99
+ 1. 進入倉庫 → **Settings → Pages**
100
+ 2. Branch: `main`,Folder: `/docs`
101
+ 3. 點擊 **Save**
102
+
103
+ 稍候一分鐘,即可透過 `https://[yourName].github.io/react-galaxy/` 訪問線上展示。
104
+
105
+ ---
106
+
107
+ ## 🤝 Contributing
108
+
109
+ 歡迎任何形式的貢獻 🙌
110
+
111
+ 1. Fork 本倉庫
112
+ 2. 建立新分支:`git checkout -b feature/amazing-feature`
113
+ 3. 提交修改:`git commit -m "feat: add amazing feature"`
114
+ 4. 推送分支:`git push origin feature/amazing-feature`
115
+ 5. 開啟 Pull Request
116
+
117
+ ---
118
+
119
+ ## 🧭 Roadmap
120
+
121
+ - [ ] AST 精準解析(支援 TypeScript、HOC)
122
+ - [ ] 插件系統(自訂指標、輸出格式)
123
+ - [ ] AI 輔助分析(LLM 自動識別複雜元件)
124
+ - [ ] 遠端專案分析(上傳程式碼或 GitHub 連結)
125
+
126
+ ---
127
+
128
+ ## 📄 License
129
+
130
+ MIT License © 2026 [scyprodigy]
@@ -0,0 +1,261 @@
1
+ /**
2
+ * analyze-components.mjs
3
+ *
4
+ * 使用 Babel 解析 React 專案中的 .jsx / .tsx 檔案,
5
+ * 輸出每個元件的名稱、路徑、行數與依賴列表。
6
+ *
7
+ * 用法:
8
+ * node analyze-components.mjs [srcDir] [outputFile]
9
+ *
10
+ * 範例:
11
+ * node analyze-components.mjs ./src galaxy.json
12
+ *
13
+ * 安裝依賴:
14
+ * npm install @babel/parser @babel/traverse glob
15
+ */
16
+
17
+ import fs from "fs";
18
+ import path from "path";
19
+ import { createRequire } from "module";
20
+ import { glob } from "glob";
21
+
22
+ // @babel/traverse 只有 CJS export,用 createRequire 載入
23
+ const require = createRequire(import.meta.url);
24
+ const parser = require("@babel/parser");
25
+ const traverseModule = require("@babel/traverse");
26
+ const traverse = traverseModule.default ?? traverseModule;
27
+
28
+ // ─── 常數 ────────────────────────────────────────────────────────────────────
29
+
30
+ const BABEL_PLUGINS = [
31
+ "jsx",
32
+ "typescript",
33
+ "classProperties",
34
+ "decorators-legacy",
35
+ "dynamicImport",
36
+ "optionalChaining",
37
+ "nullishCoalescingOperator",
38
+ "exportDefaultFrom",
39
+ "importAssertions",
40
+ ];
41
+
42
+ // 只追蹤大寫開頭的標籤(React 元件慣例)
43
+ const isComponentName = (name) => /^[A-Z]/.test(name);
44
+
45
+ // ─── 解析單一檔案 ─────────────────────────────────────────────────────────────
46
+
47
+ /**
48
+ * @param {string} filePath 絕對路徑
49
+ * @param {string} rootDir 專案根目錄(用於計算相對路徑)
50
+ * @returns {Array<{name, path, lines, dependencies}>}
51
+ */
52
+ function analyzeFile(filePath, rootDir) {
53
+ const source = fs.readFileSync(filePath, "utf-8");
54
+ const relativePath = path.relative(rootDir, filePath).replace(/\\/g, "/");
55
+ const totalLines = source.split("\n").length;
56
+
57
+ let ast;
58
+ try {
59
+ ast = parser.parse(source, {
60
+ sourceType: "module",
61
+ plugins: BABEL_PLUGINS,
62
+ errorRecovery: true,
63
+ });
64
+ } catch (err) {
65
+ console.warn(`⚠️ 無法解析 ${relativePath}:${err.message}`);
66
+ return [];
67
+ }
68
+
69
+ // ── 1. 收集所有 import 資訊 ──────────────────────────────────────────────
70
+ // importedNames: Set<string> — 此檔案 import 的所有識別符
71
+ // importMap: Map<localName, source>
72
+ const importedNames = new Set();
73
+ const importMap = new Map(); // localName → import source
74
+
75
+ traverse(ast, {
76
+ ImportDeclaration({ node }) {
77
+ for (const specifier of node.specifiers) {
78
+ const local = specifier.local.name;
79
+ importedNames.add(local);
80
+ importMap.set(local, node.source.value);
81
+ }
82
+ },
83
+ });
84
+
85
+ // ── 2. 識別元件定義 ───────────────────────────────────────────────────────
86
+ // 收集每個元件:{ name, startLine, endLine, jsxTags: Set<string> }
87
+ const componentMap = new Map(); // name → component info
88
+
89
+ const registerComponent = (name, startLine, endLine) => {
90
+ if (!name || !isComponentName(name)) return;
91
+ if (!componentMap.has(name)) {
92
+ componentMap.set(name, {
93
+ name,
94
+ startLine,
95
+ endLine,
96
+ jsxTags: new Set(),
97
+ });
98
+ } else {
99
+ // 同名多次定義時,取最後一個(override / HOC pattern)
100
+ const existing = componentMap.get(name);
101
+ existing.startLine = startLine;
102
+ existing.endLine = endLine;
103
+ }
104
+ };
105
+
106
+ traverse(ast, {
107
+ // function Component() {} / export default function Component() {}
108
+ FunctionDeclaration({ node }) {
109
+ if (node.id) {
110
+ registerComponent(
111
+ node.id.name,
112
+ node.loc?.start.line,
113
+ node.loc?.end.line
114
+ );
115
+ }
116
+ },
117
+
118
+ // const Component = () => {} / const Component = function() {}
119
+ VariableDeclarator({ node }) {
120
+ if (
121
+ node.id?.type === "Identifier" &&
122
+ isComponentName(node.id.name) &&
123
+ (node.init?.type === "ArrowFunctionExpression" ||
124
+ node.init?.type === "FunctionExpression")
125
+ ) {
126
+ registerComponent(
127
+ node.id.name,
128
+ node.loc?.start.line,
129
+ node.loc?.end.line
130
+ );
131
+ }
132
+ },
133
+
134
+ // class Component extends React.Component / Component
135
+ ClassDeclaration({ node }) {
136
+ if (node.id) {
137
+ registerComponent(
138
+ node.id.name,
139
+ node.loc?.start.line,
140
+ node.loc?.end.line
141
+ );
142
+ }
143
+ },
144
+
145
+ // export default class / export default function
146
+ ExportDefaultDeclaration({ node }) {
147
+ const decl = node.declaration;
148
+ if (
149
+ (decl.type === "FunctionDeclaration" ||
150
+ decl.type === "ClassDeclaration") &&
151
+ decl.id
152
+ ) {
153
+ registerComponent(
154
+ decl.id.name,
155
+ decl.loc?.start.line,
156
+ decl.loc?.end.line
157
+ );
158
+ }
159
+ },
160
+ });
161
+
162
+ // ── 3. 收集 JSX 中使用的元件標籤 ─────────────────────────────────────────
163
+ // 策略:找出每個 JSXOpeningElement,判斷它屬於哪個元件定義的範圍
164
+ traverse(ast, {
165
+ JSXOpeningElement({ node }) {
166
+ let tagName = null;
167
+ if (node.name.type === "JSXIdentifier") {
168
+ tagName = node.name.name;
169
+ } else if (node.name.type === "JSXMemberExpression") {
170
+ // e.g. <UI.Button> → 取最外層物件名
171
+ let obj = node.name;
172
+ while (obj.type === "JSXMemberExpression") obj = obj.object;
173
+ tagName = obj.name;
174
+ }
175
+
176
+ if (!tagName || !isComponentName(tagName)) return;
177
+
178
+ const line = node.loc?.start.line ?? 0;
179
+
180
+ // 找最近包圍此 JSX 的元件
181
+ let bestComp = null;
182
+ for (const comp of componentMap.values()) {
183
+ if (
184
+ comp.startLine <= line &&
185
+ line <= comp.endLine &&
186
+ (!bestComp ||
187
+ comp.endLine - comp.startLine <
188
+ bestComp.endLine - bestComp.startLine)
189
+ ) {
190
+ bestComp = comp;
191
+ }
192
+ }
193
+
194
+ if (bestComp && tagName !== bestComp.name) {
195
+ bestComp.jsxTags.add(tagName);
196
+ }
197
+ },
198
+ });
199
+
200
+ // ── 4. 組合輸出 ───────────────────────────────────────────────────────────
201
+ const results = [];
202
+
203
+ for (const comp of componentMap.values()) {
204
+ // 依賴 = JSX 中使用且在 importedNames 裡的元件名稱
205
+ // (過濾掉同檔案內的輔助元件)
206
+ const dependencies = [...comp.jsxTags].filter((tag) =>
207
+ importedNames.has(tag)
208
+ );
209
+
210
+ results.push({
211
+ name: comp.name,
212
+ path: relativePath,
213
+ lines: (comp.endLine ?? totalLines) - (comp.startLine ?? 1) + 1,
214
+ dependencies: [...new Set(dependencies)].sort(),
215
+ });
216
+ }
217
+
218
+ return results;
219
+ }
220
+
221
+ // ─── 掃描目錄 ─────────────────────────────────────────────────────────────────
222
+
223
+ async function analyzeProject(srcDir, outputFile) {
224
+ const absoluteSrc = path.resolve(srcDir);
225
+
226
+ console.log(`🔍 掃描目錄:${absoluteSrc}`);
227
+
228
+ const files = await glob("**/*.{js,jsx,ts,tsx}", {
229
+ cwd: absoluteSrc,
230
+ absolute: true,
231
+ ignore: ["**/node_modules/**", "**/__tests__/**", "**/*.test.*", "**/*.spec.*"],
232
+ });
233
+
234
+ console.log(`📄 找到 ${files.length} 個檔案`);
235
+
236
+ const allComponents = [];
237
+
238
+ for (const file of files) {
239
+ const components = analyzeFile(file, path.resolve("."));
240
+ allComponents.push(...components);
241
+ }
242
+
243
+ // 按元件名稱排序,方便閱讀
244
+ allComponents.sort((a, b) => a.name.localeCompare(b.name));
245
+
246
+ const json = JSON.stringify(allComponents, null, 2);
247
+
248
+ if (outputFile) {
249
+ fs.writeFileSync(outputFile, json, "utf-8");
250
+ console.log(`✅ 輸出至 ${outputFile}(共 ${allComponents.length} 個元件)`);
251
+ } else {
252
+ console.log(json);
253
+ }
254
+
255
+ return allComponents;
256
+ }
257
+
258
+ // ─── CLI 入口 ─────────────────────────────────────────────────────────────────
259
+
260
+ const [, , srcArg = "./src", outArg = "galaxy.json"] = process.argv;
261
+ await analyzeProject(srcArg, outArg);
package/bin/galaxy.js ADDED
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import http from 'http';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { spawn } from 'child_process';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const projectRoot = path.resolve(__dirname, '..');
13
+
14
+ const program = new Command();
15
+
16
+ program
17
+ .name('galaxy')
18
+ .description('React component dependency visualizer')
19
+ .version('1.0.0');
20
+
21
+ // ====== scan 指令 ======
22
+ function scanProject(dir, outputFile = 'galaxy.json') {
23
+ const scriptPath = path.join(projectRoot, 'analyze-components.mjs');
24
+ const args = [scriptPath, dir, outputFile];
25
+
26
+ console.log(`🔍 掃描目錄:${dir} → 輸出 ${outputFile}`);
27
+
28
+ const child = spawn('node', args, { stdio: 'inherit' });
29
+
30
+ child.on('error', (err) => {
31
+ console.error(`❌ 無法執行掃描: ${err.message}`);
32
+ });
33
+
34
+ child.on('exit', (code) => {
35
+ if (code === 0) {
36
+ console.log(`✅ 掃描完成,結果已寫入 ${outputFile}`);
37
+ } else {
38
+ console.error(`❌ 掃描失敗,退出碼 ${code}`);
39
+ }
40
+ });
41
+ }
42
+
43
+ program
44
+ .command('scan <directory>')
45
+ .description('Scan a React project directory and generate galaxy.json')
46
+ .option('-o, --output <file>', 'output file name', 'galaxy.json')
47
+ .action((directory, options) => {
48
+ scanProject(directory, options.output);
49
+ });
50
+
51
+ // ====== view 指令(可視化伺服器) ======
52
+ program
53
+ .command('view')
54
+ .description('Start local viewer server')
55
+ .action(() => {
56
+ const port = 3000;
57
+ const galaxyJsonPath = path.join(process.cwd(), 'galaxy.json');
58
+
59
+ const server = http.createServer((req, res) => {
60
+ if (req.url === '/galaxy.json') {
61
+ // 提供 JSON 數據
62
+ fs.readFile(galaxyJsonPath, 'utf8', (err, data) => {
63
+ if (err) {
64
+ res.writeHead(404, { 'Content-Type': 'application/json' });
65
+ res.end(JSON.stringify({ error: 'galaxy.json not found. Run `galaxy scan` first.' }));
66
+ } else {
67
+ res.writeHead(200, { 'Content-Type': 'application/json' });
68
+ res.end(data);
69
+ }
70
+ });
71
+ } else {
72
+ // 提供 HTML 頁面
73
+ res.writeHead(200, { 'Content-Type': 'text/html' });
74
+ res.end(`
75
+ <!DOCTYPE html>
76
+ <html>
77
+ <head>
78
+ <title>React Galaxy - Component Dependency</title>
79
+ <meta charset="utf-8">
80
+ <style>
81
+ body { margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
82
+ #network { width: 100vw; height: 100vh; background: #0a0a1a; }
83
+ .info {
84
+ position: absolute;
85
+ bottom: 10px;
86
+ left: 10px;
87
+ background: rgba(0,0,0,0.7);
88
+ color: #ccc;
89
+ padding: 5px 10px;
90
+ border-radius: 5px;
91
+ font-size: 12px;
92
+ pointer-events: none;
93
+ z-index: 10;
94
+ }
95
+ </style>
96
+ <script type="text/javascript" src="https://unpkg.com/vis-network@9.1.2/dist/vis-network.min.js"></script>
97
+ </head>
98
+ <body>
99
+ <div id="network"></div>
100
+ <div class="info">
101
+ 🌌 React Galaxy | 節點大小代表元件行數 | 邊表示依賴關係
102
+ </div>
103
+ <script>
104
+ fetch('/galaxy.json')
105
+ .then(res => res.json())
106
+ .then(data => {
107
+ // 構建節點和邊
108
+ const nodes = [];
109
+ const edges = [];
110
+ const nodeMap = new Map();
111
+
112
+ data.forEach(comp => {
113
+ const id = comp.name;
114
+ nodeMap.set(id, {
115
+ id: id,
116
+ label: comp.name,
117
+ title: \`路徑: \${comp.path}\\n行數: \${comp.lines}\\n依賴: \${comp.dependencies.join(', ') || '無'}\`,
118
+ value: comp.lines, // 用行數決定節點大小
119
+ });
120
+ });
121
+
122
+ // 添加節點
123
+ for (let node of nodeMap.values()) {
124
+ nodes.push(node);
125
+ }
126
+
127
+ // 添加邊
128
+ data.forEach(comp => {
129
+ const from = comp.name;
130
+ comp.dependencies.forEach(to => {
131
+ if (nodeMap.has(to)) {
132
+ edges.push({ from, to, arrows: 'to', smooth: { type: 'curvedCW' } });
133
+ }
134
+ });
135
+ });
136
+
137
+ const container = document.getElementById('network');
138
+ const options = {
139
+ nodes: {
140
+ shape: 'dot',
141
+ scaling: {
142
+ min: 10,
143
+ max: 60,
144
+ label: { enabled: true, min: 12, max: 30 }
145
+ },
146
+ font: { color: '#ffffff', size: 14, face: 'monospace' },
147
+ borderWidth: 2,
148
+ shadow: true
149
+ },
150
+ edges: {
151
+ color: { color: '#88aaff', highlight: '#ffaa88' },
152
+ width: 2,
153
+ smooth: { type: 'continuous', roundness: 0.5 },
154
+ arrows: { to: { enabled: true, scaleFactor: 0.8 } }
155
+ },
156
+ physics: {
157
+ stabilization: { iterations: 200 },
158
+ barnesHut: { gravitationalConstant: -8000, centralGravity: 0.3, springLength: 150 }
159
+ },
160
+ interaction: {
161
+ hover: true,
162
+ tooltipDelay: 200,
163
+ zoomView: true,
164
+ dragView: true
165
+ }
166
+ };
167
+ const network = new vis.Network(container, { nodes, edges }, options);
168
+ })
169
+ .catch(err => {
170
+ console.error(err);
171
+ document.body.innerHTML = '<h2 style="color:red;padding:20px">❌ 無法載入 galaxy.json,請先執行 <code>galaxy scan ./src</code></h2>';
172
+ });
173
+ </script>
174
+ </body>
175
+ </html>
176
+ `);
177
+ }
178
+ });
179
+
180
+ server.listen(port, () => {
181
+ console.log(`🚀 Galaxy Viewer 啟動:http://localhost:${port}`);
182
+ console.log(`📄 讀取數據:${galaxyJsonPath}`);
183
+ if (!fs.existsSync(galaxyJsonPath)) {
184
+ console.warn(`⚠️ 找不到 galaxy.json,請先執行掃描。`);
185
+ }
186
+ });
187
+ });
188
+
189
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "react-dep-galaxy",
3
+ "version": "1.0.0",
4
+ "description": "Visualize React component dependencies as an interactive galaxy",
5
+ "type": "module",
6
+ "bin": {
7
+ "galaxy": "./bin/galaxy.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "analyze-components.mjs",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "dependencies": {
16
+ "@babel/parser": "^7.23.0",
17
+ "@babel/traverse": "^7.23.0",
18
+ "commander": "^12.0.0",
19
+ "glob": "^10.3.10"
20
+ }
21
+ }