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 3157a40

Browse files
author
Teffen
authored
Fix issues surrounding initial web server load. (#4509)
- Clean up watcher behaviors.
1 parent 5fe16be commit 3157a40

File tree

5 files changed

+282
-122
lines changed

5 files changed

+282
-122
lines changed

‎ci/dev/watch.ts‎

Lines changed: 171 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,206 @@
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+
}
23+
24+
//#region Web Server
1825

19-
private static log(message: string, skipNewline = false): void {
20-
process.stdout.write(message)
21-
if (!skipNewline) {
22-
process.stdout.write("\n")
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+
// Pass CLI args, save for `node` and the initial script name.
35+
const args = process.argv.slice(2)
36+
this.webServer = fork(path.join(this.rootPath, "out/node/entry.js"), args)
37+
const { pid } = this.webServer
38+
39+
this.webServer.on("exit", () => console.log("[Code Server]", `Web process ${pid} exited`))
40+
41+
console.log("\n[Code Server]", `Spawned web server process ${pid}`)
2442
}
2543

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-
}
44+
//#endregion
3745

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

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

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
55+
private vscodeCompileStatus = VSCodeCompileStatus.Loading
4656

47-
constcleanup=(code?: number|null): void=> {
48-
Watcher.log("killing vs code watcher")
49-
vscode.removeAllListeners()
50-
vscode.kill()
57+
publicasyncinitialize(): Promise<void> {
58+
for(consteventof["SIGINT","SIGTERM"]){
59+
process.on(event,()=>this.dispose(0))
60+
}
5161

52-
Watcher.log("killing vs code web extension watcher")
53-
vscodeWebExtensions.removeAllListeners()
54-
vscodeWebExtensions.kill()
62+
if(!this.hasVerboseLogging){
63+
console.log("\n[Watcher]","Compiler logs will be minimal. Pass --log to show all output.")
64+
}
5565

56-
Watcher.log("killing tsc")
57-
tsc.removeAllListeners()
58-
tsc.kill()
66+
this.cleanFiles()
5967

60-
if (plugin) {
61-
Watcher.log("killing plugin")
62-
plugin.removeAllListeners()
63-
plugin.kill()
64-
}
68+
for (const [processName, devProcess] of Object.entries(this.compilers)) {
69+
if (!devProcess) continue
6570

66-
if (server) {
67-
Watcher.log("killing server")
68-
server.removeAllListeners()
69-
server.kill()
71+
devProcess.on("exit", (code) => {
72+
this.log(`[${processName}]`, "Terminated unexpectedly")
73+
this.dispose(code)
74+
})
75+
76+
if (devProcess.stderr) {
77+
devProcess.stderr.on("data", (d: string | Uint8Array) => process.stderr.write(d))
7078
}
79+
}
80+
81+
onLine(this.compilers.vscode, this.parseVSCodeLine)
82+
onLine(this.compilers.codeServer, this.parseCodeServerLine)
7183

72-
Watcher.log("killing watch")
73-
process.exit(code||0)
84+
if(this.compilers.plugins){
85+
onLine(this.compilers.plugins,this.parsePluginLine)
7486
}
87+
}
88+
89+
//#endregion
7590

76-
process.on("SIGINT", () => cleanup())
77-
process.on("SIGTERM", () => cleanup())
91+
//#region Line Parsers
7892

79-
vscode.on("exit",(code) => {
80-
Watcher.log("vs code watcher terminated unexpectedly")
81-
cleanup(code)
82-
})
93+
privateparseVSCodeLine: OnLineCallback=(strippedLine,originalLine) => {
94+
if(!strippedLine.includes("watch-extensions")||this.hasVerboseLogging){
95+
console.log("[VS Code]",originalLine)
96+
}
8397

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

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

94-
if (plugin) {
95-
plugin.on("exit", (code) => {
96-
Watcher.log("plugin terminated unexpectedly")
97-
cleanup(code)
98-
})
127+
console.log("[Compiler][Code Server]", originalLine)
128+
129+
if (strippedLine.includes("Watching for file changes")) {
130+
console.log("[Compiler][Code Server]", "Finished compiling!", "(Refresh your web browser ♻️)")
131+
132+
this.reloadWebServer()
99133
}
134+
}
135+
136+
private parsePluginLine: OnLineCallback = (strippedLine, originalLine) => {
137+
if (!strippedLine.length) return
100138

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))
139+
console.log("[Compiler][Plugin]", originalLine)
104140

105-
if (plugin) {
106-
plugin.stderr.on("data",(d)=>process.stderr.write(d))
141+
if (strippedLine.includes("Watching for file changes...")) {
142+
this.reloadWebServer()
107143
}
144+
}
108145

109-
onLine(vscode, (line, original) => {
110-
console.log("[vscode]", original)
111-
if (line.includes("Finished compilation")) {
112-
restartServer()
113-
}
114-
})
146+
//#endregion
115147

116-
onLine(tsc, (line, original) => {
117-
// tsc outputs blank lines; skip them.
118-
if (line !== "") {
119-
console.log("[tsc]", original)
120-
}
121-
if (line.includes("Watching for file changes")) {
122-
restartServer()
123-
}
124-
})
148+
//#region Utilities
125149

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

‎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 によって変換されたページ (->オリジナル) /