@remix-run/node-hmr 0.0.0 → 0.1.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 +21 -0
- package/README.md +306 -2
- package/dist/index.d.ts +128 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +107 -0
- package/dist/lib/browser-events.d.ts +99 -0
- package/dist/lib/browser-events.d.ts.map +1 -0
- package/dist/lib/browser-events.js +11 -0
- package/dist/lib/events.d.ts +29 -0
- package/dist/lib/events.d.ts.map +1 -0
- package/dist/lib/events.js +32 -0
- package/dist/lib/hmr-analysis.d.ts +17 -0
- package/dist/lib/hmr-analysis.d.ts.map +1 -0
- package/dist/lib/hmr-analysis.js +130 -0
- package/dist/lib/module-store.d.ts +27 -0
- package/dist/lib/module-store.d.ts.map +1 -0
- package/dist/lib/module-store.js +161 -0
- package/dist/lib/process-state.d.ts +3 -0
- package/dist/lib/process-state.d.ts.map +1 -0
- package/dist/lib/process-state.js +7 -0
- package/dist/lib/runner.d.ts +62 -0
- package/dist/lib/runner.d.ts.map +1 -0
- package/dist/lib/runner.js +1046 -0
- package/dist/lib/runtime-api.d.ts +7 -0
- package/dist/lib/runtime-api.d.ts.map +1 -0
- package/dist/lib/runtime-api.js +1 -0
- package/dist/lib/runtime.d.ts +46 -0
- package/dist/lib/runtime.d.ts.map +1 -0
- package/dist/lib/runtime.js +374 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +317 -0
- package/dist/runtime.d.ts +26 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +32 -0
- package/dist/runtime.node-hmr.d.ts +27 -0
- package/dist/runtime.node-hmr.d.ts.map +1 -0
- package/dist/runtime.node-hmr.js +33 -0
- package/dist/types.d.ts +36 -0
- package/package.json +55 -5
- package/src/index.ts +244 -0
- package/src/lib/browser-events.ts +123 -0
- package/src/lib/events.ts +61 -0
- package/src/lib/hmr-analysis.ts +178 -0
- package/src/lib/module-store.ts +228 -0
- package/src/lib/process-state.ts +9 -0
- package/src/lib/runner.ts +1427 -0
- package/src/lib/runtime-api.ts +9 -0
- package/src/lib/runtime.ts +534 -0
- package/src/register.ts +401 -0
- package/src/runtime.node-hmr.ts +40 -0
- package/src/runtime.ts +40 -0
- package/src/types.d.ts +36 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BrowserHmrChannel } from './browser-events.ts'
|
|
2
|
+
|
|
3
|
+
export interface NodeHmrRuntimeApi {
|
|
4
|
+
createBrowserHmrChannel(): Promise<BrowserHmrChannel>
|
|
5
|
+
emitServerReady(): void
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const nodeHmrRuntimeUnavailableError =
|
|
9
|
+
'The node-hmr/runtime API is only available when running inside node-hmr'
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type BrowserHmrChannel,
|
|
5
|
+
type BrowserHmrEvent,
|
|
6
|
+
type BrowserHmrFileEvent,
|
|
7
|
+
type BrowserHmrFileEventHandler,
|
|
8
|
+
type BrowserHmrWatchedFileDelta,
|
|
9
|
+
sendHmrEventPayload,
|
|
10
|
+
} from './browser-events.ts'
|
|
11
|
+
import { emitServerHmrEvent, emitServerHmrUpdate } from './events.ts'
|
|
12
|
+
import { hasNodeHmrParentProcess } from './process-state.ts'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Hot module context available at `import.meta.hot`.
|
|
16
|
+
*/
|
|
17
|
+
export interface ImportMetaHot {
|
|
18
|
+
/** Mutable state preserved for this module across accepted updates and passed to dispose handlers. */
|
|
19
|
+
readonly data: Record<string, unknown>
|
|
20
|
+
/** Accepts updates to this module, optionally receiving its newly evaluated namespace. */
|
|
21
|
+
accept(callback?: (module: HotModule) => HotCallbackResult): void
|
|
22
|
+
/** Accepts updates from one dependency, optionally receiving its newly evaluated namespace. */
|
|
23
|
+
accept(dep: string, callback?: (module: HotModule) => HotCallbackResult): void
|
|
24
|
+
/**
|
|
25
|
+
* Accepts updates from multiple dependencies. The callback array preserves `deps` order and
|
|
26
|
+
* contains the updated namespace only at the position of the dependency that changed.
|
|
27
|
+
*/
|
|
28
|
+
accept(
|
|
29
|
+
deps: readonly string[],
|
|
30
|
+
callback?: (modules: Array<HotModule | undefined>) => HotCallbackResult,
|
|
31
|
+
): void
|
|
32
|
+
/** Registers cleanup that runs before this module is re-evaluated or the runtime is disposed. */
|
|
33
|
+
dispose(callback: (data: Record<string, unknown>) => HotCallbackResult): void
|
|
34
|
+
/** Declines the current update and asks the runner to restart the child process. */
|
|
35
|
+
invalidate(message?: string): void
|
|
36
|
+
/** Registers a custom-event listener for API compatibility. Server modules receive no events. */
|
|
37
|
+
on(event: string, callback: (data: unknown) => void | Promise<void>): void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type HotModule = Readonly<Record<string, unknown>> & {
|
|
41
|
+
readonly [Symbol.toStringTag]: 'Module'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RemixNodeHmrRuntime {
|
|
45
|
+
createBrowserHmrChannel(): Promise<BrowserHmrChannel>
|
|
46
|
+
createHotContext(url: string, resolveDependency?: (specifier: string) => string): ImportMetaHot
|
|
47
|
+
disposeAll(): Promise<void>
|
|
48
|
+
emitServerReady(): void
|
|
49
|
+
handleBrowserHmrFileEvents(requestId: number, events: readonly BrowserHmrFileEvent[]): void
|
|
50
|
+
reportAcceptedDependencies(url: string, acceptedDeps: string[]): void
|
|
51
|
+
update(url: string, timestamp: number, acceptedUrl?: string): Promise<void>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type RuntimeGlobal = typeof globalThis & {
|
|
55
|
+
__remixNodeHmr?: RemixNodeHmrRuntime
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type HotCallbackResult = void | Promise<void>
|
|
59
|
+
type HotCallback = (module: HotModule) => HotCallbackResult
|
|
60
|
+
type HotDependencyCallbackFunction = (module: HotModule) => HotCallbackResult
|
|
61
|
+
type HotDependencyArrayUpdateCallbackFunction = (
|
|
62
|
+
modules: Array<HotModule | undefined>,
|
|
63
|
+
) => HotCallbackResult
|
|
64
|
+
type HotDependencyUpdateCallback = (module: HotModule, acceptedUrl: string) => HotCallbackResult
|
|
65
|
+
type HotDependencyCallback = {
|
|
66
|
+
callback: HotDependencyUpdateCallback
|
|
67
|
+
deps: string[]
|
|
68
|
+
}
|
|
69
|
+
type DisposeCallback = (data: Record<string, unknown>) => HotCallbackResult
|
|
70
|
+
type HotUpdateResult = 'accepted' | 'invalidated' | 'restart-requested'
|
|
71
|
+
const browserHmrChannelRequestTimeoutMs = 5_000
|
|
72
|
+
|
|
73
|
+
class NodeHotContext implements ImportMetaHot {
|
|
74
|
+
readonly data: Record<string, unknown>
|
|
75
|
+
readonly url: string
|
|
76
|
+
|
|
77
|
+
#acceptCallbacks: Array<HotCallback> = []
|
|
78
|
+
#acceptDependencyCallbacks: Array<HotDependencyCallback> = []
|
|
79
|
+
#disposeCallbacks: Array<DisposeCallback> = []
|
|
80
|
+
#invalidated = false
|
|
81
|
+
#invalidationMessage: string | undefined
|
|
82
|
+
#isUpdating = false
|
|
83
|
+
#disposeSelf: () => Promise<void>
|
|
84
|
+
#disposeDependency: (url: string) => Promise<void>
|
|
85
|
+
#resolveDependency: (specifier: string) => string
|
|
86
|
+
|
|
87
|
+
constructor(
|
|
88
|
+
url: string,
|
|
89
|
+
data: Record<string, unknown>,
|
|
90
|
+
disposeSelf: () => Promise<void>,
|
|
91
|
+
disposeDependency: (url: string) => Promise<void>,
|
|
92
|
+
resolveDependency: (specifier: string) => string,
|
|
93
|
+
) {
|
|
94
|
+
this.data = data
|
|
95
|
+
this.url = url
|
|
96
|
+
this.#disposeSelf = disposeSelf
|
|
97
|
+
this.#disposeDependency = disposeDependency
|
|
98
|
+
this.#resolveDependency = resolveDependency
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
accept(callback?: (module: HotModule) => HotCallbackResult): void
|
|
102
|
+
accept(dep: string, callback?: (module: HotModule) => HotCallbackResult): void
|
|
103
|
+
accept(
|
|
104
|
+
deps: readonly string[],
|
|
105
|
+
callback?: (modules: Array<HotModule | undefined>) => HotCallbackResult,
|
|
106
|
+
): void
|
|
107
|
+
accept(
|
|
108
|
+
deps?: string | readonly string[] | HotCallback,
|
|
109
|
+
callback: HotDependencyCallbackFunction | HotDependencyArrayUpdateCallbackFunction = () => {},
|
|
110
|
+
) {
|
|
111
|
+
if (typeof deps === 'string') {
|
|
112
|
+
let normalizedDeps = [this.#normalizeAcceptedDependency(deps)]
|
|
113
|
+
let dependencyCallback = callback as HotDependencyCallbackFunction
|
|
114
|
+
this.#acceptDependencyCallbacks.push({
|
|
115
|
+
callback(module) {
|
|
116
|
+
return dependencyCallback(module)
|
|
117
|
+
},
|
|
118
|
+
deps: normalizedDeps,
|
|
119
|
+
})
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (isDependencyArray(deps)) {
|
|
124
|
+
let normalizedDeps = deps.map((dep) => this.#normalizeAcceptedDependency(dep))
|
|
125
|
+
let dependencyCallback = callback as HotDependencyArrayUpdateCallbackFunction
|
|
126
|
+
this.#acceptDependencyCallbacks.push({
|
|
127
|
+
callback(module, acceptedUrl) {
|
|
128
|
+
return dependencyCallback(
|
|
129
|
+
normalizedDeps.map((dep) => (dep === acceptedUrl ? module : undefined)),
|
|
130
|
+
)
|
|
131
|
+
},
|
|
132
|
+
deps: normalizedDeps,
|
|
133
|
+
})
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.#acceptCallbacks.push(deps ?? (() => {}))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
dispose(callback: DisposeCallback) {
|
|
141
|
+
this.#disposeCallbacks.push(callback)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
invalidate(message?: string) {
|
|
145
|
+
this.#invalidated = true
|
|
146
|
+
this.#invalidationMessage = message
|
|
147
|
+
if (this.#isUpdating) {
|
|
148
|
+
if (message !== undefined) console.warn(message)
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
requestRestart(message)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
on(_event: string, _callback: (data: unknown) => void | Promise<void>) {
|
|
155
|
+
void _event
|
|
156
|
+
void _callback
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async disposeAll() {
|
|
160
|
+
for (let callback of this.#disposeCallbacks) {
|
|
161
|
+
await callback(this.data)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async update(timestamp: number, acceptedUrl: string): Promise<HotUpdateResult> {
|
|
166
|
+
this.#invalidated = false
|
|
167
|
+
this.#invalidationMessage = undefined
|
|
168
|
+
|
|
169
|
+
if (acceptedUrl !== this.url) {
|
|
170
|
+
return await this.updateDependency(timestamp, acceptedUrl)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (this.#acceptCallbacks.length === 0) {
|
|
174
|
+
requestRestart(`No HMR accept handler found for ${this.url}`)
|
|
175
|
+
return 'restart-requested'
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await this.#disposeSelf()
|
|
179
|
+
|
|
180
|
+
this.#isUpdating = true
|
|
181
|
+
try {
|
|
182
|
+
let updatedModule = await import(`${this.url}?t=${timestamp}`)
|
|
183
|
+
for (let callback of this.#acceptCallbacks) {
|
|
184
|
+
await callback(updatedModule)
|
|
185
|
+
}
|
|
186
|
+
} finally {
|
|
187
|
+
this.#isUpdating = false
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return this.#invalidated ? 'invalidated' : 'accepted'
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
get invalidationMessage(): string | undefined {
|
|
194
|
+
return this.#invalidationMessage
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async updateDependency(timestamp: number, acceptedUrl: string): Promise<HotUpdateResult> {
|
|
198
|
+
let callbacks = this.#acceptDependencyCallbacks.filter((callback) =>
|
|
199
|
+
callback.deps.includes(acceptedUrl),
|
|
200
|
+
)
|
|
201
|
+
if (callbacks.length === 0) {
|
|
202
|
+
requestRestart(`No HMR accept handler found for ${acceptedUrl} via ${this.url}`)
|
|
203
|
+
return 'restart-requested'
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.#isUpdating = true
|
|
207
|
+
try {
|
|
208
|
+
await this.#disposeDependency(acceptedUrl)
|
|
209
|
+
let updatedModule = await import(`${acceptedUrl}?t=${timestamp}`)
|
|
210
|
+
for (let { callback } of callbacks) {
|
|
211
|
+
await callback(updatedModule, acceptedUrl)
|
|
212
|
+
}
|
|
213
|
+
} finally {
|
|
214
|
+
this.#isUpdating = false
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return this.#invalidated ? 'invalidated' : 'accepted'
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
#normalizeAcceptedDependency(dep: string): string {
|
|
221
|
+
let resolved = this.#resolveDependency(dep)
|
|
222
|
+
let url = new URL(resolved)
|
|
223
|
+
url.search = ''
|
|
224
|
+
url.hash = ''
|
|
225
|
+
return url.href
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isDependencyArray(
|
|
230
|
+
deps: string | readonly string[] | HotCallback | undefined,
|
|
231
|
+
): deps is readonly string[] {
|
|
232
|
+
return Array.isArray(deps)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function getNodeHmrRuntime(): RemixNodeHmrRuntime | undefined {
|
|
236
|
+
let runtimeGlobal = globalThis as RuntimeGlobal
|
|
237
|
+
return runtimeGlobal.__remixNodeHmr
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Notifies the parent process that the child server is ready.
|
|
242
|
+
*/
|
|
243
|
+
export function emitServerReady(): void {
|
|
244
|
+
getNodeHmrRuntime()?.emitServerReady()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function installNodeHmrRuntime(
|
|
248
|
+
options: {
|
|
249
|
+
browserEventUrl?: string
|
|
250
|
+
} = {},
|
|
251
|
+
): RemixNodeHmrRuntime {
|
|
252
|
+
let runtimeGlobal = globalThis as RuntimeGlobal
|
|
253
|
+
if (runtimeGlobal.__remixNodeHmr) return runtimeGlobal.__remixNodeHmr
|
|
254
|
+
|
|
255
|
+
let dataByUrl = new Map<string, Record<string, unknown>>()
|
|
256
|
+
let updateContextsByUrl = new Map<string, NodeHotContext>()
|
|
257
|
+
let latestContextsByUrl = new Map<string, NodeHotContext>()
|
|
258
|
+
let browserEventUrl = options.browserEventUrl
|
|
259
|
+
let browserHmrChannelId = 0
|
|
260
|
+
let browserHmrChannelRequestId = 0
|
|
261
|
+
let pendingBrowserHmrChannelRequests = new Map<
|
|
262
|
+
number,
|
|
263
|
+
{
|
|
264
|
+
resolve(url: string | undefined): void
|
|
265
|
+
timer: ReturnType<typeof setTimeout>
|
|
266
|
+
}
|
|
267
|
+
>()
|
|
268
|
+
let browserHmrChannels = new Map<
|
|
269
|
+
number,
|
|
270
|
+
{
|
|
271
|
+
handleFileEvents(events: readonly BrowserHmrFileEvent[]): Promise<readonly BrowserHmrEvent[]>
|
|
272
|
+
}
|
|
273
|
+
>()
|
|
274
|
+
|
|
275
|
+
process.on('message', (message: unknown) => {
|
|
276
|
+
if (!isBrowserHmrChannelMessage(message)) return
|
|
277
|
+
|
|
278
|
+
let request = pendingBrowserHmrChannelRequests.get(message.requestId)
|
|
279
|
+
if (request === undefined) return
|
|
280
|
+
|
|
281
|
+
clearTimeout(request.timer)
|
|
282
|
+
pendingBrowserHmrChannelRequests.delete(message.requestId)
|
|
283
|
+
request.resolve(message.url)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
let runtime: RemixNodeHmrRuntime = {
|
|
287
|
+
async createBrowserHmrChannel() {
|
|
288
|
+
browserEventUrl ??= await requestBrowserHmrChannelUrl()
|
|
289
|
+
if (browserEventUrl === undefined) {
|
|
290
|
+
throw new Error('Browser HMR is disabled for this node-hmr runtime')
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let id = browserHmrChannelId++
|
|
294
|
+
let watchedFiles = new Set<string>()
|
|
295
|
+
let handlers = new Set<BrowserHmrFileEventHandler>()
|
|
296
|
+
let closed = false
|
|
297
|
+
browserHmrChannels.set(id, {
|
|
298
|
+
async handleFileEvents(events) {
|
|
299
|
+
let eventGroups = await Promise.all([...handlers].map((handler) => handler(events)))
|
|
300
|
+
return eventGroups.flat()
|
|
301
|
+
},
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
function updateWatchedFiles(delta: BrowserHmrWatchedFileDelta): void {
|
|
305
|
+
if (closed) return
|
|
306
|
+
|
|
307
|
+
for (let file of delta.add) {
|
|
308
|
+
watchedFiles.add(file)
|
|
309
|
+
}
|
|
310
|
+
for (let file of delta.remove) {
|
|
311
|
+
watchedFiles.delete(file)
|
|
312
|
+
}
|
|
313
|
+
process.send?.({
|
|
314
|
+
id,
|
|
315
|
+
delta,
|
|
316
|
+
type: 'node-hmr:child:browser-hmr-watch-files-changed',
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
close() {
|
|
322
|
+
if (closed) return
|
|
323
|
+
closed = true
|
|
324
|
+
|
|
325
|
+
browserHmrChannels.delete(id)
|
|
326
|
+
handlers.clear()
|
|
327
|
+
let remove = [...watchedFiles]
|
|
328
|
+
watchedFiles.clear()
|
|
329
|
+
process.send?.({
|
|
330
|
+
id,
|
|
331
|
+
delta: { add: [], remove },
|
|
332
|
+
type: 'node-hmr:child:browser-hmr-watch-files-changed',
|
|
333
|
+
})
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
onFileEvents(handler) {
|
|
337
|
+
if (closed) return () => {}
|
|
338
|
+
|
|
339
|
+
handlers.add(handler)
|
|
340
|
+
return () => {
|
|
341
|
+
handlers.delete(handler)
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
updateWatchedFiles,
|
|
346
|
+
|
|
347
|
+
url: browserEventUrl,
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
createHotContext(url, resolveDependency = (specifier) => new URL(specifier, url).href) {
|
|
352
|
+
let data = dataByUrl.get(url)
|
|
353
|
+
if (data === undefined) {
|
|
354
|
+
data = {}
|
|
355
|
+
dataByUrl.set(url, data)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let context = new NodeHotContext(
|
|
359
|
+
url,
|
|
360
|
+
data,
|
|
361
|
+
async () => {
|
|
362
|
+
await latestContextsByUrl.get(url)?.disposeAll()
|
|
363
|
+
},
|
|
364
|
+
async (dependencyUrl) => {
|
|
365
|
+
await latestContextsByUrl.get(dependencyUrl)?.disposeAll()
|
|
366
|
+
},
|
|
367
|
+
resolveDependency,
|
|
368
|
+
)
|
|
369
|
+
if (!updateContextsByUrl.has(url)) {
|
|
370
|
+
updateContextsByUrl.set(url, context)
|
|
371
|
+
}
|
|
372
|
+
latestContextsByUrl.set(url, context)
|
|
373
|
+
return context
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
emitServerReady() {
|
|
377
|
+
if (!hasNodeHmrParentProcess()) return
|
|
378
|
+
|
|
379
|
+
process.send?.({
|
|
380
|
+
type: 'node-hmr:child:server-ready',
|
|
381
|
+
})
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
handleBrowserHmrFileEvents(requestId, events) {
|
|
385
|
+
if (!hasNodeHmrParentProcess()) return
|
|
386
|
+
|
|
387
|
+
Promise.all(
|
|
388
|
+
[...browserHmrChannels.values()].map((browserHmrChannel) =>
|
|
389
|
+
browserHmrChannel.handleFileEvents(events),
|
|
390
|
+
),
|
|
391
|
+
)
|
|
392
|
+
.then((eventGroups) => {
|
|
393
|
+
process.send?.({
|
|
394
|
+
events: eventGroups.flat(),
|
|
395
|
+
requestId,
|
|
396
|
+
type: 'node-hmr:child:browser-hmr-file-events-handled',
|
|
397
|
+
})
|
|
398
|
+
})
|
|
399
|
+
.catch((error: unknown) => {
|
|
400
|
+
process.send?.({
|
|
401
|
+
error: formatUnknownError(error),
|
|
402
|
+
events: [],
|
|
403
|
+
requestId,
|
|
404
|
+
type: 'node-hmr:child:browser-hmr-file-events-handled',
|
|
405
|
+
})
|
|
406
|
+
})
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
reportAcceptedDependencies(url, acceptedDeps) {
|
|
410
|
+
if (!hasNodeHmrParentProcess()) return
|
|
411
|
+
|
|
412
|
+
process.send?.({
|
|
413
|
+
type: 'node-hmr:child:accepted-deps-resolved',
|
|
414
|
+
url,
|
|
415
|
+
acceptedDeps,
|
|
416
|
+
})
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
async disposeAll() {
|
|
420
|
+
for (let context of latestContextsByUrl.values()) {
|
|
421
|
+
await context.disposeAll()
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
updateContextsByUrl.clear()
|
|
425
|
+
latestContextsByUrl.clear()
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
async update(url, timestamp, acceptedUrl = url) {
|
|
429
|
+
let context = updateContextsByUrl.get(url)
|
|
430
|
+
if (context === undefined) {
|
|
431
|
+
requestRestart(`No HMR context found for ${url}`)
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
let updateResult = await context.update(timestamp, acceptedUrl)
|
|
437
|
+
if (updateResult === 'invalidated') {
|
|
438
|
+
process.send?.({
|
|
439
|
+
acceptedUrl,
|
|
440
|
+
message: context.invalidationMessage,
|
|
441
|
+
timestamp,
|
|
442
|
+
type: 'node-hmr:child:hot-module-invalidated',
|
|
443
|
+
url,
|
|
444
|
+
})
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
if (updateResult === 'restart-requested') return
|
|
448
|
+
|
|
449
|
+
emitServerHmrUpdate({
|
|
450
|
+
...(acceptedUrl === url ? {} : { acceptedUrl }),
|
|
451
|
+
filePath: acceptedUrl.startsWith('file:') ? fileURLToPath(acceptedUrl) : acceptedUrl,
|
|
452
|
+
timestamp,
|
|
453
|
+
type: 'update',
|
|
454
|
+
url,
|
|
455
|
+
})
|
|
456
|
+
} catch (error) {
|
|
457
|
+
requestRestart(`Failed to hot update ${url}: ${formatUnknownError(error)}`)
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
runtimeGlobal.__remixNodeHmr = runtime
|
|
463
|
+
return runtime
|
|
464
|
+
|
|
465
|
+
function requestBrowserHmrChannelUrl(): Promise<string | undefined> {
|
|
466
|
+
if (!hasNodeHmrParentProcess()) return Promise.resolve(undefined)
|
|
467
|
+
|
|
468
|
+
let requestId = browserHmrChannelRequestId++
|
|
469
|
+
return new Promise((resolvePromise) => {
|
|
470
|
+
let timer = setTimeout(() => {
|
|
471
|
+
pendingBrowserHmrChannelRequests.delete(requestId)
|
|
472
|
+
resolvePromise(undefined)
|
|
473
|
+
}, browserHmrChannelRequestTimeoutMs)
|
|
474
|
+
|
|
475
|
+
pendingBrowserHmrChannelRequests.set(requestId, {
|
|
476
|
+
resolve: resolvePromise,
|
|
477
|
+
timer,
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
if (
|
|
481
|
+
!process.send?.({
|
|
482
|
+
requestId,
|
|
483
|
+
type: 'node-hmr:child:browser-hmr-channel-requested',
|
|
484
|
+
})
|
|
485
|
+
) {
|
|
486
|
+
clearTimeout(timer)
|
|
487
|
+
pendingBrowserHmrChannelRequests.delete(requestId)
|
|
488
|
+
resolvePromise(undefined)
|
|
489
|
+
}
|
|
490
|
+
})
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function isBrowserHmrChannelMessage(message: unknown): message is {
|
|
495
|
+
requestId: number
|
|
496
|
+
type: 'node-hmr:parent:browser-hmr-channel'
|
|
497
|
+
url?: string
|
|
498
|
+
} {
|
|
499
|
+
return (
|
|
500
|
+
typeof message === 'object' &&
|
|
501
|
+
message !== null &&
|
|
502
|
+
'type' in message &&
|
|
503
|
+
message.type === 'node-hmr:parent:browser-hmr-channel' &&
|
|
504
|
+
'requestId' in message &&
|
|
505
|
+
typeof message.requestId === 'number' &&
|
|
506
|
+
(!('url' in message) || typeof message.url === 'string')
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function requestRestart(message?: string): void {
|
|
511
|
+
if (message !== undefined) {
|
|
512
|
+
console.warn(message)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
emitServerHmrEvent({
|
|
516
|
+
reason: message,
|
|
517
|
+
timestamp: Date.now(),
|
|
518
|
+
type: 'restart',
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
if (process.send) {
|
|
522
|
+
process.send({
|
|
523
|
+
type: 'node-hmr:child:restart-requested',
|
|
524
|
+
message,
|
|
525
|
+
})
|
|
526
|
+
return
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
process.kill(process.pid, 'SIGTERM')
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function formatUnknownError(error: unknown): string {
|
|
533
|
+
return error instanceof Error ? error.message : String(error)
|
|
534
|
+
}
|