Skip to content

Commit ee14bd0

Browse files
authored
Merge pull request #13 from parallelV2/feat/source
feat: support click to jump and add file to source panel
2 parents de902d6 + 3219b9d commit ee14bd0

File tree

6 files changed

+244
-56
lines changed

6 files changed

+244
-56
lines changed

apps/web/docs/preview.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
# 🎉 Preview
22

33
## Demo(GIF)
4-
Lines changed: 47 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,74 @@
1-
import { getStackFrames, initiatorStackPipe } from "./utils/stack";
1+
import { fileURLToPath } from 'url'
2+
import { getStackFrames, initiatorStackPipe } from './utils/stack'
3+
import { dirname } from 'path'
24

35
export interface CDPCallFrame {
4-
columnNumber: number;
5-
functionName: string;
6-
lineNumber: number;
7-
url: string;
8-
scriptId: string;
6+
columnNumber: number
7+
functionName: string
8+
lineNumber: number
9+
url: string
10+
scriptId?: string
911
}
1012

1113
export class RequestDetail {
12-
id: string;
14+
id: string
1315
constructor() {
14-
this.id = Math.random().toString(36).slice(2);
15-
this.responseInfo = {};
16+
this.id = Math.random().toString(36).slice(2)
17+
this.responseInfo = {}
1618

17-
const frames = initiatorStackPipe(getStackFrames());
19+
const frames = initiatorStackPipe(getStackFrames())
1820

1921
const callFrames = frames.map((frame) => {
20-
const scriptId = Math.random().toString(36).slice(2);
21-
const fileName = frame.fileName || "";
22+
const fileName = frame.fileName || ''
2223
return {
2324
columnNumber: frame.columnNumber || 0,
24-
functionName: frame.functionName || "",
25+
functionName: frame.functionName || '',
2526
lineNumber: frame.lineNumber || 0,
26-
url: fileName.startsWith("/") ? `file://${fileName}` : fileName,
27-
scriptId,
28-
};
29-
});
27+
url: fileName.startsWith('/') ? `file://${fileName}` : fileName
28+
}
29+
})
3030

3131
if (callFrames.length > 0) {
3232
this.initiator = {
33-
type: "script",
33+
type: 'script',
3434
stack: {
35-
callFrames,
36-
},
37-
};
35+
callFrames
36+
}
37+
}
3838
}
3939
}
4040

41-
url?: string;
42-
method?: string;
43-
cookies: any;
41+
url?: string
42+
method?: string
43+
cookies: any
4444

45-
requestHeaders: any;
46-
requestData: any;
45+
requestHeaders: any
46+
requestData: any
4747

48-
responseData: any;
49-
responseStatusCode?: number;
50-
responseHeaders: any;
48+
responseData: any
49+
responseStatusCode?: number
50+
responseHeaders: any
5151
responseInfo: Partial<{
52-
encodedDataLength: number;
53-
dataLength: number;
54-
}>;
52+
encodedDataLength: number
53+
dataLength: number
54+
}>
5555

56-
requestStartTime?: number;
57-
requestEndTime?: number;
56+
requestStartTime?: number
57+
requestEndTime?: number
5858

5959
initiator?: {
60-
type: string;
60+
type: string
6161
stack: {
62-
callFrames: CDPCallFrame[];
63-
};
64-
};
62+
callFrames: CDPCallFrame[]
63+
}
64+
}
6565
}
66-
export const LOCK_FILE = "request-center.lock";
67-
export const PORT = Number(process.env.NETWORK_PORT || 5270);
68-
export const SERVER_PORT = Number(process.env.NETWORK_SERVER_PORT || 5271);
69-
export const REMOTE_DEBUGGER_PORT = Number(
70-
process.env.REMOTE_DEBUGGER_PORT || 9333
71-
);
72-
export const IS_DEV_MODE = process.env.NETWORK_DEBUG_MODE === "true";
73-
export const READY_MESSAGE = "ready";
66+
export const LOCK_FILE = 'request-center.lock'
67+
export const PORT = Number(process.env.NETWORK_PORT || 5270)
68+
export const SERVER_PORT = Number(process.env.NETWORK_SERVER_PORT || 5271)
69+
export const REMOTE_DEBUGGER_PORT = Number(process.env.REMOTE_DEBUGGER_PORT || 9333)
70+
export const IS_DEV_MODE = process.env.NETWORK_DEBUG_MODE === 'true'
71+
export const READY_MESSAGE = 'ready'
72+
73+
export const __filename = fileURLToPath(import.meta.url)
74+
export const __dirname = dirname(__filename)

packages/network-debugger/src/core/fork.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,8 @@ import { type IncomingMessage } from 'http'
33
import WebSocket from 'ws'
44
import { fork } from 'child_process'
55
import fs from 'fs'
6-
import { LOCK_FILE } from '../common'
7-
import { resolve, dirname } from 'path'
8-
import { fileURLToPath } from 'url'
9-
10-
const __filename = fileURLToPath(import.meta.url)
11-
const __dirname = dirname(__filename)
6+
import { LOCK_FILE, __dirname } from '../common'
7+
import { resolve } from 'path'
128

139
export class MainProcess {
1410
private ws: Promise<WebSocket>
@@ -125,3 +121,4 @@ export class MainProcess {
125121
})
126122
}
127123
}
124+
export { __dirname }
Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,39 @@
11
import { createPlugin, useContext, useHandler } from '../common'
22

3+
export interface ISciprtParsed {
4+
url: string
5+
scriptLanguage: string
6+
embedderName: string
7+
scriptId: string
8+
sourceMapURL: string
9+
hasSourceURL: boolean
10+
}
11+
export interface ScriptSourceData {
12+
scriptId: string
13+
}
14+
315
export const useStore = () => {
416
const { devtool } = useContext()
517
devtool.send({})
618
}
719

8-
export const debuggerPlugin = createPlugin(({ devtool }) => {
9-
useHandler('Debugger.getSciptSource', ({ id, request }) => {
10-
console.log('Debugger.getSciptSource', devtool, id, request)
20+
export const debuggerPlugin = createPlugin(({ devtool, core }) => {
21+
useHandler<ScriptSourceData>('Debugger.getScriptSource', ({ id, data }) => {
22+
const { scriptId } = data
23+
const scriptSource = core.resourceService.getScriptSource(scriptId)
24+
devtool.send({
25+
id: id,
26+
method: 'Debugger.getScriptSourceResponse',
27+
result: {
28+
scriptSource
29+
}
30+
})
31+
})
32+
const scriptList = core.resourceService.getLocalScriptList()
33+
scriptList.forEach((script) => {
34+
devtool.send({
35+
method: 'Debugger.scriptParsed',
36+
params: script
37+
})
1138
})
1239
})

packages/network-debugger/src/fork/request-center.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import zlib from 'zlib'
44
import { Server } from 'ws'
55
import { RequestHeaderPipe } from './pipe'
66
import { log } from '../utils'
7+
import { ResourceService } from './resource-service'
78
import { EffectCleaner, PluginInstance } from './module/common'
9+
import { pathToFileURL } from 'url'
810

911
export interface RequestCenterInitOptions {
1012
port?: number
@@ -22,6 +24,7 @@ export interface DevtoolMessageListener<T = any> {
2224

2325
export class RequestCenter {
2426
public requests: Record<string, RequestDetail>
27+
public resourceService: ResourceService
2528
private devtool: DevtoolServer
2629
private server: Server
2730
private effects: Array<EffectCleaner> = []
@@ -31,6 +34,7 @@ export class RequestCenter {
3134
this.devtool = new DevtoolServer({
3235
port
3336
})
37+
this.resourceService = new ResourceService()
3438
this.devtool.on((error, message) => {
3539
if (error) {
3640
log(error)
@@ -100,6 +104,18 @@ export class RequestCenter {
100104
}
101105

102106
public registerRequest(request: RequestDetail) {
107+
// replace callFrames' scriptId
108+
if (request.initiator) {
109+
request.initiator.stack.callFrames.forEach((frame) => {
110+
const fileUrl = pathToFileURL(frame.url)
111+
const scriptId =
112+
this.resourceService.getScriptIdByUrl(fileUrl.href) ??
113+
this.resourceService.getScriptIdByUrl(frame.url)
114+
if (scriptId) {
115+
frame.scriptId = scriptId
116+
}
117+
})
118+
}
103119
this.requests[request.id] = request
104120
this.devtool.requestWillBeSent(request)
105121
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import { fileURLToPath, pathToFileURL } from 'url'
4+
import { __dirname } from '../common'
5+
6+
function getScriptLangByFileName(fileName: string) {
7+
const extension = fileName.split('.').pop()?.toLowerCase()
8+
switch (extension) {
9+
case 'js':
10+
case 'mjs':
11+
case 'cjs':
12+
return 'JavaScript'
13+
case 'ts':
14+
return 'TypeScript'
15+
case 'jsx':
16+
return 'JSX'
17+
case 'tsx':
18+
return 'TSX'
19+
case 'html':
20+
case 'htm':
21+
return 'HTML'
22+
case 'css':
23+
return 'CSS'
24+
case 'vue':
25+
return 'Vue'
26+
case 'json':
27+
return 'JSON'
28+
case 'yaml':
29+
case 'yml':
30+
return 'YAML'
31+
case 'xml':
32+
return 'XML'
33+
default:
34+
return 'Unknown'
35+
}
36+
}
37+
38+
export class ScriptMap {
39+
private urlToScriptId: Map<string, string>
40+
private scriptIdToUrl: Map<string, string>
41+
42+
constructor() {
43+
this.urlToScriptId = new Map<string, string>()
44+
this.scriptIdToUrl = new Map<string, string>()
45+
}
46+
47+
public addMapping(filePath: string, scriptId: string) {
48+
this.urlToScriptId.set(filePath, scriptId)
49+
this.scriptIdToUrl.set(scriptId, filePath)
50+
}
51+
52+
public getUrlByScriptId(scriptId: string) {
53+
return this.scriptIdToUrl.get(scriptId)
54+
}
55+
56+
public getScriptIdByUrl(url: string) {
57+
return this.urlToScriptId.get(url)
58+
}
59+
}
60+
61+
export class ResourceService {
62+
private scriptMap: ScriptMap
63+
private scriptIdCounter: number
64+
65+
constructor() {
66+
this.scriptMap = new ScriptMap()
67+
this.scriptIdCounter = 0
68+
}
69+
70+
public getScriptIdByUrl(url: string) {
71+
return this.scriptMap.getScriptIdByUrl(url)
72+
}
73+
74+
public getUrlByScriptId(scriptId: string) {
75+
return this.scriptMap.getUrlByScriptId(scriptId)
76+
}
77+
78+
public getScriptSource(scriptId: string) {
79+
const fileUrl = this.scriptMap.getUrlByScriptId(scriptId)
80+
if (!fileUrl) {
81+
console.error(`No file path found for script ID: ${scriptId}`)
82+
return null
83+
}
84+
85+
const filePath = fileURLToPath(fileUrl)
86+
try {
87+
return fs.readFileSync(filePath, 'utf-8')
88+
} catch (err) {
89+
console.error('Error reading file:', err)
90+
return null
91+
}
92+
}
93+
94+
private traverseDirToMap(directoryPath: string, ignoreList: string[] = ['node_modules']) {
95+
const scriptList = []
96+
const stack = [directoryPath]
97+
let scriptId = this.scriptIdCounter
98+
99+
while (stack.length > 0) {
100+
const currentPath = stack.pop()!
101+
const items = fs.readdirSync(currentPath)
102+
103+
for (const item of items) {
104+
if (ignoreList.includes(item)) {
105+
continue
106+
}
107+
108+
const fullPath = path.join(currentPath, item)
109+
const stats = fs.statSync(fullPath)
110+
111+
if (stats.isDirectory()) {
112+
stack.push(fullPath)
113+
} else {
114+
const resolvedPath = path.resolve(fullPath)
115+
const fileUrl = pathToFileURL(resolvedPath)
116+
const scriptIdStr = `${++scriptId}`
117+
scriptList.push({
118+
url: fileUrl.href,
119+
scriptLanguage: getScriptLangByFileName(fileUrl.href),
120+
embedderName: fileUrl.href,
121+
scriptId: scriptIdStr,
122+
// TODO: SourceMap?
123+
sourceMapURL: '',
124+
hasSourceURL: false
125+
// TODO: is useful?
126+
// startColumn: 0,
127+
// startLine: 0,
128+
// endColumn: 231,
129+
// endLine: 145,
130+
// isModule: false,
131+
// length: 63559,
132+
// isLiveEdit: false
133+
})
134+
this.scriptMap.addMapping(fileUrl.href, scriptIdStr)
135+
}
136+
}
137+
}
138+
this.scriptIdCounter += scriptList.length
139+
return scriptList
140+
}
141+
142+
public getLocalScriptList() {
143+
const projectScripts = this.traverseDirToMap(process.cwd())
144+
const coreScripts = this.traverseDirToMap(__dirname)
145+
146+
return [...projectScripts, ...coreScripts]
147+
}
148+
}

0 commit comments

Comments
 (0)