Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 58f1c54

Browse files
Fix issues surrounding initial web server load.
- Clean up watcher behaviors.
1 parent 211badf commit 58f1c54

File tree

5 files changed

+280
-131
lines changed

5 files changed

+280
-131
lines changed

‎ci/dev/watch.ts‎

Lines changed: 169 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,204 @@
1-
import * as cp from "child_process"
1+
import { spawn, fork, ChildProcess } from "child_process"
2+
import del from "del"
3+
import { promises as fs } from "fs"
24
import * as path from "path"
3-
import { onLine } from "../../src/node/util"
4-
5-
async function main(): Promise<void> {
6-
try {
7-
const watcher = new Watcher()
8-
await watcher.watch()
9-
} catch (error: any) {
10-
console.error(error.message)
11-
process.exit(1)
12-
}
5+
import { CompilationStats, onLine, OnLineCallback, VSCodeCompileStatus } from "../../src/node/util"
6+
7+
interface DevelopmentCompilers {
8+
[key: string]: ChildProcess | undefined
9+
vscode: ChildProcess
10+
vscodeWebExtensions: ChildProcess
11+
codeServer: ChildProcess
12+
plugins: ChildProcess | undefined
1313
}
1414

1515
class Watcher {
16-
private readonly rootPath = path.resolve(__dirname, "../..")
17-
private readonly vscodeSourcePath = path.join(this.rootPath, "vendor/modules/code-oss-dev")
16+
private rootPath = path.resolve(process.cwd())
17+
private readonly paths = {
18+
/** Path to uncompiled VS Code source. */
19+
vscodeDir: path.join(this.rootPath, "vendor", "modules", "code-oss-dev"),
20+
compilationStatsFile: path.join(this.rootPath, "out", "watcher.json"),
21+
pluginDir: process.env.PLUGIN_DIR,
22+
}
1823

19-
private static log(message: string, skipNewline = false): void {
20-
process.stdout.write(message)
21-
if (!skipNewline) {
22-
process.stdout.write("\n")
24+
//#region Web Server
25+
26+
/** Development web server. */
27+
private webServer: ChildProcess | undefined
28+
29+
private reloadWebServer = (): void => {
30+
if (this.webServer) {
31+
this.webServer.kill()
2332
}
33+
34+
this.webServer = fork(path.join(this.rootPath, "out/node/entry.js"), process.argv.slice(2))
35+
const { pid } = this.webServer
36+
37+
this.webServer.on("exit", () => console.log("[Code Server]", `Web process ${pid} exited`))
38+
39+
console.log("\n[Code Server]", `Spawned web server process ${pid}`)
2440
}
2541

26-
public async watch(): Promise<void> {
27-
let server: cp.ChildProcess | undefined
28-
const restartServer = (): void => {
29-
if (server) {
30-
server.kill()
31-
}
32-
const s = cp.fork(path.join(this.rootPath, "out/node/entry.js"), process.argv.slice(2))
33-
console.log(`[server] spawned process ${s.pid}`)
34-
s.on("exit", () => console.log(`[server] process ${s.pid} exited`))
35-
server = s
36-
}
42+
//#endregion
3743

38-
constvscode=cp.spawn("yarn",["watch"],{cwd: this.vscodeSourcePath})
44+
//#region Compilers
3945

40-
const vscodeWebExtensions = cp.spawn("yarn", ["watch-web"], { cwd: this.vscodeSourcePath })
46+
private readonly compilers: DevelopmentCompilers = {
47+
codeServer: spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath }),
48+
vscode: spawn("yarn", ["watch"], { cwd: this.paths.vscodeDir }),
49+
vscodeWebExtensions: spawn("yarn", ["watch-web"], { cwd: this.paths.vscodeDir }),
50+
plugins: this.paths.pluginDir ? spawn("yarn", ["build", "--watch"], { cwd: this.paths.pluginDir }) : undefined,
51+
}
4152

42-
const tsc = cp.spawn("tsc", ["--watch", "--pretty", "--preserveWatchOutput"], { cwd: this.rootPath })
43-
const plugin = process.env.PLUGIN_DIR
44-
? cp.spawn("yarn", ["build", "--watch"], { cwd: process.env.PLUGIN_DIR })
45-
: undefined
53+
private vscodeCompileStatus = VSCodeCompileStatus.Loading
4654

47-
const cleanup = (code?: number | null): void => {
48-
Watcher.log("killing vs code watcher")
49-
vscode.removeAllListeners()
50-
vscode.kill()
55+
public async initialize(): Promise<void> {
56+
for (const event of ["SIGINT", "SIGTERM"]) {
57+
process.on(event, () => this.dispose(0))
58+
}
59+
60+
if (!this.hasVerboseLogging) {
61+
console.log("\n[Watcher]", "Compiler logs will be minimal. Pass --log to show all output.")
62+
}
5163

52-
Watcher.log("killing vs code web extension watcher")
53-
vscodeWebExtensions.removeAllListeners()
54-
vscodeWebExtensions.kill()
64+
this.cleanFiles()
5565

56-
Watcher.log("killing tsc")
57-
tsc.removeAllListeners()
58-
tsc.kill()
66+
for (const [processName, devProcess] of Object.entries(this.compilers)) {
67+
if (!devProcess) continue
5968

60-
if (plugin) {
61-
Watcher.log("killing plugin")
62-
plugin.removeAllListeners()
63-
plugin.kill()
64-
}
69+
devProcess.on("exit", (code) => {
70+
this.log(`[${processName}]`, "Terminated unexpectedly")
71+
this.dispose(code)
72+
})
6573

66-
if (server) {
67-
Watcher.log("killing server")
68-
server.removeAllListeners()
69-
server.kill()
74+
if (devProcess.stderr) {
75+
devProcess.stderr.on("data", (d: string | Uint8Array) => process.stderr.write(d))
7076
}
77+
}
7178

72-
Watcher.log("killing watch")
73-
process.exit(code || 0)
79+
onLine(this.compilers.vscode, this.parseVSCodeLine)
80+
onLine(this.compilers.codeServer, this.parseCodeServerLine)
81+
82+
if (this.compilers.plugins) {
83+
onLine(this.compilers.plugins, this.parsePluginLine)
7484
}
85+
}
7586

76-
process.on("SIGINT", () => cleanup())
77-
process.on("SIGTERM", () => cleanup())
87+
//#endregion
7888

79-
vscode.on("exit", (code) => {
80-
Watcher.log("vs code watcher terminated unexpectedly")
81-
cleanup(code)
82-
})
89+
//#region Line Parsers
8390

84-
vscodeWebExtensions.on("exit", (code) => {
85-
Watcher.log("vs code extension watcher terminated unexpectedly")
86-
cleanup(code)
87-
})
91+
private parseVSCodeLine: OnLineCallback = (strippedLine, originalLine) => {
92+
if (!strippedLine.includes("watch-extensions") || this.hasVerboseLogging) {
93+
console.log("[VS Code]", originalLine)
94+
}
95+
96+
switch (this.vscodeCompileStatus) {
97+
case VSCodeCompileStatus.Loading:
98+
// Wait for watch-client since "Finished compilation" will appear multiple
99+
// times before the client starts building.
100+
if (strippedLine.includes("Starting 'watch-client'")) {
101+
console.log("[VS Code] 🚧 Compiling 🚧", "(This may take a moment!)")
102+
this.vscodeCompileStatus = VSCodeCompileStatus.Compiling
103+
}
104+
break
105+
case VSCodeCompileStatus.Compiling:
106+
if (strippedLine.includes("Finished compilation")) {
107+
console.log("[VS Code] ✨ Finished compiling! ✨", "(Refresh your web browser ♻️)")
108+
this.vscodeCompileStatus = VSCodeCompileStatus.Compiled
109+
110+
this.emitCompilationStats()
111+
this.reloadWebServer()
112+
}
113+
break
114+
case VSCodeCompileStatus.Compiled:
115+
console.log("[VS Code] 🔔 Finished recompiling! 🔔", "(Refresh your web browser ♻️)")
116+
this.emitCompilationStats()
117+
this.reloadWebServer()
118+
break
119+
}
120+
}
88121

89-
tsc.on("exit", (code) => {
90-
Watcher.log("tsc terminated unexpectedly")
91-
cleanup(code)
92-
})
122+
private parseCodeServerLine: OnLineCallback = (strippedLine, originalLine) => {
123+
if (!strippedLine.length) return
93124

94-
if (plugin) {
95-
plugin.on("exit", (code) => {
96-
Watcher.log("plugin terminated unexpectedly")
97-
cleanup(code)
98-
})
125+
console.log("[Compiler][Code Server]", originalLine)
126+
127+
if (strippedLine.includes("Watching for file changes")) {
128+
console.log("[Compiler][Code Server]", "Finished compiling!", "(Refresh your web browser ♻️)")
129+
130+
this.reloadWebServer()
99131
}
132+
}
100133

101-
vscodeWebExtensions.stderr.on("data", (d) => process.stderr.write(d))
102-
vscode.stderr.on("data", (d) => process.stderr.write(d))
103-
tsc.stderr.on("data", (d) => process.stderr.write(d))
134+
private parsePluginLine: OnLineCallback = (strippedLine, originalLine) => {
135+
if (!strippedLine.length) return
104136

105-
if (plugin) {
106-
plugin.stderr.on("data", (d) => process.stderr.write(d))
137+
console.log("[Compiler][Plugin]", originalLine)
138+
139+
if (strippedLine.includes("Watching for file changes...")) {
140+
this.reloadWebServer()
107141
}
142+
}
108143

109-
let startingVscode = false
110-
let startedVscode = false
111-
onLine(vscode, (line, original) => {
112-
console.log("[vscode]", original)
113-
// Wait for watch-client since "Finished compilation" will appear multiple
114-
// times before the client starts building.
115-
if (!startingVscode && line.includes("Starting watch-client")) {
116-
startingVscode = true
117-
} else if (startingVscode && line.includes("Finished compilation")) {
118-
if (startedVscode) {
119-
restartServer()
120-
}
121-
startedVscode = true
122-
}
123-
})
144+
//#endregion
124145

125-
onLine(tsc, (line, original) => {
126-
// tsc outputs blank lines; skip them.
127-
if (line !== "") {
128-
console.log("[tsc]", original)
129-
}
130-
if (line.includes("Watching for file changes")) {
131-
restartServer()
132-
}
133-
})
146+
//#region Utilities
134147

135-
if (plugin) {
136-
onLine(plugin, (line, original) => {
137-
// tsc outputs blank lines; skip them.
138-
if (line !== "") {
139-
console.log("[plugin]", original)
140-
}
141-
if (line.includes("Watching for file changes")) {
142-
restartServer()
143-
}
144-
})
148+
/**
149+
* Cleans files from previous builds.
150+
*/
151+
private cleanFiles(): Promise<string[]> {
152+
console.log("[Watcher]", "Cleaning files from previous builds...")
153+
154+
return del([
155+
"out/**/*",
156+
// Included because the cache can sometimes enter bad state when debugging compiled files.
157+
".cache/**/*",
158+
])
159+
}
160+
161+
/**
162+
* Emits a file containing compilation data.
163+
* This is especially useful when Express needs to determine if VS Code is still compiling.
164+
*/
165+
private emitCompilationStats(): Promise<void> {
166+
const stats: CompilationStats = {
167+
status: this.vscodeCompileStatus,
168+
lastCompiledAt: new Date(),
169+
}
170+
171+
this.log("Writing watcher stats...")
172+
return fs.writeFile(this.paths.compilationStatsFile, JSON.stringify(stats, null, 2))
173+
}
174+
175+
private log(...entries: string[]) {
176+
process.stdout.write(entries.join(" "))
177+
}
178+
179+
private dispose(code: number | null): void {
180+
for (const [processName, devProcess] of Object.entries(this.compilers)) {
181+
this.log(`[${processName}]`, "Killing...\n")
182+
devProcess?.removeAllListeners()
183+
devProcess?.kill()
145184
}
185+
process.exit(typeof code === "number" ? code : 0)
186+
}
187+
188+
private get hasVerboseLogging() {
189+
return process.argv.includes("--log")
190+
}
191+
192+
//#endregion
193+
}
194+
195+
async function main(): Promise<void> {
196+
try {
197+
const watcher = new Watcher()
198+
await watcher.initialize()
199+
} catch (error: any) {
200+
console.error(error.message)
201+
process.exit(1)
146202
}
147203
}
148204

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"@typescript-eslint/parser": "^5.0.0",
5353
"audit-ci": "^5.0.0",
5454
"codecov": "^3.8.3",
55+
"del": "^6.0.0",
5556
"doctoc": "^2.0.0",
5657
"eslint": "^7.7.0",
5758
"eslint-config-prettier": "^8.1.0",

‎src/node/routes/vscode.ts‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { WebsocketRequest } from "../../../typings/pluginapi"
44
import { logError } from "../../common/util"
55
import { isDevMode } from "../constants"
66
import { ensureAuthenticated, authenticated, redirect } from "../http"
7-
import { loadAMDModule } from "../util"
7+
import { loadAMDModule,readCompilationStats } from "../util"
88
import { Router as WsRouter } from "../wsRouter"
99
import { errorHandler } from "./errors"
1010

@@ -40,7 +40,6 @@ export class CodeServerRouteWrapper {
4040
if (error instanceof Error && ["EntryNotFound", "FileNotFound", "HttpError"].includes(error.message)) {
4141
next()
4242
}
43-
4443
errorHandler(error, req, res, next)
4544
}
4645

@@ -62,9 +61,21 @@ export class CodeServerRouteWrapper {
6261
*/
6362
private ensureCodeServerLoaded: express.Handler = async (req, _res, next) => {
6463
if (this._codeServerMain) {
64+
// Already loaded...
6565
return next()
6666
}
6767

68+
if (isDevMode) {
69+
// Is the development mode file watcher still busy?
70+
const compileStats = await readCompilationStats()
71+
72+
if (!compileStats || !compileStats.lastCompiledAt) {
73+
return next(new Error("VS Code may still be compiling..."))
74+
}
75+
}
76+
77+
// Create the server...
78+
6879
const { args } = req
6980

7081
/**
@@ -84,10 +95,7 @@ export class CodeServerRouteWrapper {
8495
})
8596
} catch (createServerError) {
8697
logError(logger, "CodeServerRouteWrapper", createServerError)
87-
88-
const loggedError = isDevMode ? new Error("VS Code may still be compiling...") : createServerError
89-
90-
return next(loggedError)
98+
return next(createServerError)
9199
}
92100

93101
return next()

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /