-
Notifications
You must be signed in to change notification settings - Fork 169
/
hmr.ts
executable file
·115 lines (99 loc) · 3.15 KB
/
hmr.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import runtime from 'https://esm.sh/react-refresh/runtime?dev'
import events from './events.ts'
import util, { hashShort } from './util.ts'
interface Callback {
(...args: any[]): void
}
// react-refresh
// @link https://github.com/facebook/react/issues/16604#issuecomment-528663101
runtime.injectIntoGlobalHook(window)
Object.assign(window, {
$RefreshReg$: () => { },
$RefreshSig$: () => (type: any) => type
})
export const performReactRefresh = util.debounce(runtime.performReactRefresh, 30)
export const RefreshRuntime = runtime
class Module {
#id: string
#isLocked: boolean = false
#isAccepted: boolean = false
#acceptCallbacks: Callback[] = []
get id() {
return this.#id
}
constructor(id: string) {
this.#id = id
}
lock(): void {
this.#isLocked = true
}
accept(callback?: () => void): void {
if (this.#isLocked) {
return
}
if (!this.#isAccepted) {
sendMessage({ id: this.id, type: 'hotAccept' })
this.#isAccepted = true
}
if (callback) {
this.#acceptCallbacks.push(callback)
}
}
async applyUpdate(updateUrl: string) {
try {
const module = await import(updateUrl + '?t=' + Date.now())
this.#acceptCallbacks.forEach(cb => cb(module))
} catch (e) {
location.reload()
}
}
}
const { location } = window as any
const { protocol, host } = location
const modules: Map<string, Module> = new Map()
const messageQueue: any[] = []
const socket = new WebSocket((protocol === 'https:' ? 'wss' : 'ws') + '://' + host + '/_hmr', /* 'aleph-hmr' */)
socket.addEventListener('open', () => {
messageQueue.forEach(msg => socket.send(JSON.stringify(msg)))
messageQueue.splice(0, messageQueue.length)
})
socket.addEventListener('close', () => {
location.reload()
})
socket.addEventListener('message', ({ data: rawData }: { data?: string }) => {
if (rawData) {
try {
const { type, moduleId, hash, updateUrl } = JSON.parse(rawData)
if (type) {
console.log(`[HMR]${hash ? ' [' + hash.slice(0, hashShort) + ']' : ''} ${type} module '${moduleId}'`)
if (type === 'add') {
events.emit('add-module', { moduleId, hash })
} else if (type === 'update' && modules.has(moduleId)) {
const mod = modules.get(moduleId)!
mod.applyUpdate(updateUrl)
} else if (type === 'remove' && modules.has(moduleId)) {
modules.delete(moduleId)
events.emit('remove-module', moduleId)
}
}
} catch (e) { }
}
})
function sendMessage(msg: any) {
if (socket.readyState !== socket.OPEN) {
messageQueue.push(msg)
} else {
socket.send(JSON.stringify(msg))
}
}
export function createHotContext(id: string) {
if (modules.has(id)) {
const mod = modules.get(id)!
mod.lock()
return mod
}
const mod = new Module(id)
modules.set(id, mod)
return mod
}
console.log('[HMR] listening for file changes...')