Skip to content

Commit 21bcddc

Browse files
committed
feat: handle hot-hook process messages
1 parent 0548b3f commit 21bcddc

File tree

4 files changed

+133
-123
lines changed

4 files changed

+133
-123
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@
4242
"@japa/snapshot": "^2.0.8",
4343
"@poppinss/ts-exec": "^1.2.1",
4444
"@release-it/conventional-changelog": "^10.0.1",
45-
"@types/node": "^22.15.23",
45+
"@types/node": "^22.15.29",
4646
"@types/picomatch": "^4.0.0",
4747
"@types/pretty-hrtime": "^1.0.3",
4848
"c8": "^10.1.3",
4949
"del-cli": "^6.0.0",
50-
"eslint": "^9.27.0",
51-
"hot-hook": "^0.4.0",
50+
"eslint": "^9.28.0",
51+
"hot-hook": "^0.4.1-next.0",
5252
"p-event": "^6.0.1",
5353
"prettier": "^3.5.3",
54-
"release-it": "^19.0.2",
54+
"release-it": "^19.0.3",
5555
"tsup": "^8.5.0",
5656
"typescript": "^5.8.3"
5757
},

src/dev_server.ts

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,9 @@ export class DevServer {
171171
* Inspect if child process message is coming from hot-hook
172172
*/
173173
#isHotHookMessage(message: unknown): message is {
174-
type: string
174+
type: 'hot-hook:file-changed' | 'hot-hook:invalidated' | 'hot-hook:full-reload'
175175
path: string
176+
action?: 'add' | 'change' | 'unlink'
176177
paths?: string[]
177178
} {
178179
return (
@@ -196,19 +197,25 @@ export class DevServer {
196197
/**
197198
* Handles file change event
198199
*/
199-
#handleFileChange(filePath: string, action: string) {
200+
#handleFileChange(filePath: string, action: string, hotReplaced?: boolean) {
200201
const file = this.#fileSystem.inspect(filePath)
201202
if (!file) {
202203
return
203204
}
204205

206+
if (hotReplaced) {
207+
this.ui.logger.log(`${this.ui.colors.green('invalidated')} ${filePath}`)
208+
return
209+
}
210+
205211
if (file.reloadServer) {
206212
this.#clearScreen()
207213
this.ui.logger.log(`${this.ui.colors.green(action)} ${filePath}`)
208214
this.#restartHTTPServer()
209-
} else {
210-
this.ui.logger.log(`${this.ui.colors.green(action)} ${filePath}`)
215+
return
211216
}
217+
218+
this.ui.logger.log(`${this.ui.colors.green(action)} ${filePath}`)
212219
}
213220

214221
/**
@@ -217,7 +224,9 @@ export class DevServer {
217224
*/
218225
#registerServerRestartHooks() {
219226
this.#hooks.add('fileAdded', (filePath) => this.#handleFileChange(filePath, 'add'))
220-
this.#hooks.add('fileChanged', (filePath) => this.#handleFileChange(filePath, 'update'))
227+
this.#hooks.add('fileChanged', (filePath, hotReplaced) =>
228+
this.#handleFileChange(filePath, 'update', hotReplaced)
229+
)
221230
this.#hooks.add('fileRemoved', (filePath) => this.#handleFileChange(filePath, 'delete'))
222231
}
223232

@@ -248,7 +257,23 @@ export class DevServer {
248257
if (this.#isAdonisJSReadyMessage(message)) {
249258
await this.#postServerReady(message)
250259
resolve()
251-
} else if (this.#isHotHookMessage(message)) {
260+
} else if (this.#mode === 'hmr' && this.#isHotHookMessage(message)) {
261+
if (message.type === 'hot-hook:file-changed') {
262+
switch (message.action) {
263+
case 'add':
264+
this.#hooks.runner('fileAdded').run(string.toUnixSlash(message.path), this)
265+
break
266+
case 'change':
267+
this.#hooks.runner('fileChanged').run(string.toUnixSlash(message.path), false, this)
268+
break
269+
case 'unlink':
270+
this.#hooks.runner('fileRemoved').run(string.toUnixSlash(message.path), this)
271+
}
272+
} else if (message.type === 'hot-hook:full-reload') {
273+
this.#hooks.runner('fileChanged').run(string.toUnixSlash(message.path), false, this)
274+
} else if (message.type === 'hot-hook:invalidated') {
275+
this.#hooks.runner('fileChanged').run(string.toUnixSlash(message.path), true, this)
276+
}
252277
}
253278
})
254279

@@ -267,7 +292,10 @@ export class DevServer {
267292
this.ui.logger.info('Underlying HTTP server died. Still watching for changes')
268293
}
269294
})
270-
.finally(() => resolve())
295+
.finally(() => {
296+
console.log('ere>>')
297+
resolve()
298+
})
271299
})
272300
}
273301

@@ -325,9 +353,9 @@ export class DevServer {
325353
this.options.nodeArgs = this.options.nodeArgs.concat('--import=hot-hook/register')
326354
this.options.env = {
327355
...this.options.env,
328-
HOT_HOOK_INCLUDES: this.#fileSystem.includes.join(','),
329-
HOT_HOOK_EXCLUDES: this.#fileSystem.excludes.join(','),
330-
HOT_HOOK_REGISTER: (this.options.metaFiles ?? []).map(({ pattern }) => pattern).join(','),
356+
HOT_HOOK_INCLUDE: this.#fileSystem.includes.join(','),
357+
HOT_HOOK_IGNORE: this.#fileSystem.excludes.join(','),
358+
HOT_HOOK_RESTART: (this.options.metaFiles ?? []).map(({ pattern }) => pattern).join(','),
331359
}
332360
}
333361

@@ -401,7 +429,7 @@ export class DevServer {
401429
this.#hooks.runner('fileAdded').run(string.toUnixSlash(filePath), this)
402430
)
403431
this.#watcher.on('change', (filePath) =>
404-
this.#hooks.runner('fileChanged').run(string.toUnixSlash(filePath), this)
432+
this.#hooks.runner('fileChanged').run(string.toUnixSlash(filePath), false, this)
405433
)
406434
this.#watcher.on('unlink', (filePath) =>
407435
this.#hooks.runner('fileRemoved').run(string.toUnixSlash(filePath), this)

src/types/hooks.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ export type WatcherHooks = {
2424
/**
2525
* The hook is executed after a file has been changed in the watch mode.
2626
*/
27-
fileChanged: LazyImport<(filePath: string, server: DevServer | TestRunner) => AsyncOrSync<void>>[]
27+
fileChanged: LazyImport<
28+
(filePath: string, hotReplaced: boolean, server: DevServer | TestRunner) => AsyncOrSync<void>
29+
>[]
2830

2931
/**
3032
* The hook is executed after a file has been added.

tests/dev_server.spec.ts

Lines changed: 87 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -212,111 +212,91 @@ test.group('DevServer', () => {
212212
])
213213
}).timeout(8 * 1000)
214214

215-
// test('should restart server if receive hot-hook message', async ({ assert, fs }) => {
216-
// await fs.createJson('tsconfig.json', { include: ['**/*'], exclude: [] })
217-
// await fs.create(
218-
// 'bin/server.js',
219-
// `process.send({ type: 'hot-hook:full-reload', path: '/foo' });`
220-
// )
221-
// await fs.create('.env', 'PORT=3334')
222-
223-
// const { logger } = cliui({ mode: 'raw' })
224-
// const devServer = new DevServer(fs.baseUrl, {
225-
// hmr: true,
226-
// nodeArgs: [],
227-
// scriptArgs: [],
228-
// }).setLogger(logger)
229-
230-
// await devServer.start()
231-
// await sleep(1000)
232-
// await devServer.close()
233-
234-
// const logMessages = logger.getLogs().map(({ message }) => message)
235-
// assert.isAtLeast(logMessages.filter((message) => message.includes('full-reload')).length, 1)
236-
// })
237-
238-
// test('trigger onDevServerStarted and onSourceFileChanged when hot-hook message is received', async ({
239-
// assert,
240-
// fs,
241-
// }) => {
242-
// let onDevServerStartedCalled = false
243-
// let onSourceFileChangedCalled = false
244-
245-
// await fs.createJson('tsconfig.json', { include: ['**/*'], exclude: [] })
246-
// await fs.create(
247-
// 'bin/server.js',
248-
// `process.send({ type: 'hot-hook:full-reload', path: '/foo' });`
249-
// )
250-
// await fs.create('.env', 'PORT=3334')
251-
252-
// const { logger } = cliui({ mode: 'raw' })
253-
// const devServer = new DevServer(fs.baseUrl, {
254-
// hmr: true,
255-
// nodeArgs: [],
256-
// scriptArgs: [],
257-
// hooks: {
258-
// onDevServerStarted: [
259-
// async () => ({
260-
// default: () => {
261-
// onDevServerStartedCalled = true
262-
// },
263-
// }),
264-
// ],
265-
// onSourceFileChanged: [
266-
// async () => ({
267-
// default: () => {
268-
// onSourceFileChangedCalled = true
269-
// },
270-
// }),
271-
// ],
272-
// },
273-
// }).setLogger(logger)
274-
275-
// await devServer.start()
276-
// await sleep(1000)
277-
// await devServer.close()
278-
279-
// assert.isTrue(onDevServerStartedCalled)
280-
// assert.isTrue(onSourceFileChangedCalled)
281-
// })
282-
283-
// test('should correctly display a relative path when a hot-hook message is received', async ({
284-
// assert,
285-
// fs,
286-
// }) => {
287-
// await fs.createJson('tsconfig.json', { include: ['**/*'], exclude: [] })
288-
// await fs.createJson('package.json', { type: 'module', hotHook: { boundaries: ['./app/**'] } })
289-
// await fs.create('app/controllers/app_controller.ts', 'console.log("foo")')
290-
// await fs.create(
291-
// 'bin/server.js',
292-
// `
293-
// import { resolve } from 'path';
294-
// import '../app/controllers/app_controller.js';
295-
// `
296-
// )
297-
// await fs.create('.env', 'PORT=3334')
298-
299-
// const { logger } = cliui({ mode: 'raw' })
300-
// const devServer = new DevServer(fs.baseUrl, {
301-
// hmr: true,
302-
// nodeArgs: [],
303-
// scriptArgs: [],
304-
// }).setLogger(logger)
305-
306-
// await devServer.start()
307-
// await sleep(2000)
308-
// await fs.create('app/controllers/app_controller.ts', 'console.log("bar")')
309-
// await sleep(2000)
310-
// await devServer.close()
311-
312-
// const logMessages = logger.getLogs().map(({ message }) => message)
313-
314-
// const relativePath = relative(
315-
// fs.basePath,
316-
// resolve(fs.basePath, 'app/controllers/app_controller.ts')
317-
// )
318-
319-
// const expectedMessage = `green(full-reload) ${relativePath}`
320-
// assert.isAtLeast(logMessages.filter((message) => message.includes(expectedMessage)).length, 1)
321-
// }).timeout(10_000)
215+
test('restart server if hot-hook:full-reload message is received', async ({ assert, fs }) => {
216+
await fs.createJson('tsconfig.json', { include: ['**/*'], exclude: [] })
217+
await fs.create(
218+
'bin/server.ts',
219+
`process.send({ type: 'hot-hook:full-reload', path: 'start/routes.ts' });`
220+
)
221+
await fs.create('start/routes.ts', ``)
222+
await fs.create('.env', 'PORT=3334')
223+
224+
const devServer = new DevServer(fs.baseUrl, {
225+
hmr: true,
226+
nodeArgs: [],
227+
scriptArgs: [],
228+
})
229+
230+
devServer.ui.switchMode('raw')
231+
devServer.start(ts)
232+
await sleep(1000)
233+
234+
await devServer.close()
235+
236+
const logMessages = devServer.ui.logger.getLogs().map(({ message }) => message)
237+
assert.isAtLeast(
238+
logMessages.filter((message) => message.includes('green(update) start/routes.ts')).length,
239+
1
240+
)
241+
}).disableTimeout()
242+
243+
test('do not restart server when hot-hook:invalidated message is received', async ({
244+
assert,
245+
fs,
246+
}) => {
247+
await fs.createJson('tsconfig.json', { include: ['**/*'], exclude: [] })
248+
await fs.create(
249+
'bin/server.ts',
250+
`process.send({ type: 'hot-hook:invalidated', path: 'start/routes.ts' });`
251+
)
252+
await fs.create('start/routes.ts', ``)
253+
await fs.create('.env', 'PORT=3334')
254+
255+
const devServer = new DevServer(fs.baseUrl, {
256+
hmr: true,
257+
nodeArgs: [],
258+
scriptArgs: [],
259+
})
260+
261+
devServer.ui.switchMode('raw')
262+
devServer.start(ts)
263+
await sleep(1000)
264+
265+
await devServer.close()
266+
267+
const logMessages = devServer.ui.logger.getLogs().map(({ message }) => message)
268+
assert.isAtLeast(
269+
logMessages.filter((message) => message.includes('green(invalidated) start/routes.ts'))
270+
.length,
271+
1
272+
)
273+
}).disableTimeout()
274+
275+
test('handle hot-hook:file-changed event', async ({ assert, fs }) => {
276+
await fs.createJson('tsconfig.json', { include: ['**/*'], exclude: [] })
277+
await fs.create(
278+
'bin/server.ts',
279+
`process.send({ type: 'hot-hook:file-changed', action: 'change', path: 'start/routes.ts' });`
280+
)
281+
await fs.create('start/routes.ts', ``)
282+
await fs.create('.env', 'PORT=3334')
283+
284+
const devServer = new DevServer(fs.baseUrl, {
285+
hmr: true,
286+
nodeArgs: [],
287+
scriptArgs: [],
288+
})
289+
290+
devServer.ui.switchMode('raw')
291+
devServer.start(ts)
292+
await sleep(1000)
293+
294+
await devServer.close()
295+
296+
const logMessages = devServer.ui.logger.getLogs().map(({ message }) => message)
297+
assert.isAtLeast(
298+
logMessages.filter((message) => message.includes('green(update) start/routes.ts')).length,
299+
1
300+
)
301+
}).disableTimeout()
322302
})

0 commit comments

Comments
 (0)