/
hotReload.ts
98 lines (83 loc) · 3.46 KB
/
hotReload.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
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { env } from 'vs/base/common/process';
export function isHotReloadEnabled(): boolean {
return env && !!env['VSCODE_DEV'];
}
export function registerHotReloadHandler(handler: HotReloadHandler): IDisposable {
if (!isHotReloadEnabled()) {
return { dispose() { } };
} else {
const handlers = registerGlobalHotReloadHandler();
handlers.add(handler);
return {
dispose() { handlers.delete(handler); }
};
}
}
/**
* Takes the old exports of the module to reload and returns a function to apply the new exports.
* If `undefined` is returned, this handler is not able to handle the module.
*
* If no handler can apply the new exports, the module will not be reloaded.
*/
export type HotReloadHandler = (args: { oldExports: Record<string, unknown>; newSrc: string; config: IHotReloadConfig }) => AcceptNewExportsHandler | undefined;
export type AcceptNewExportsHandler = (newExports: Record<string, unknown>) => boolean;
export type IHotReloadConfig = HotReloadConfig;
function registerGlobalHotReloadHandler() {
if (!hotReloadHandlers) {
hotReloadHandlers = new Set();
}
const g = globalThis as unknown as GlobalThisAddition;
if (!g.$hotReload_applyNewExports) {
g.$hotReload_applyNewExports = args => {
const args2 = { config: { mode: undefined }, ...args };
for (const h of hotReloadHandlers!) {
const result = h(args2);
if (result) { return result; }
}
return undefined;
};
}
return hotReloadHandlers;
}
let hotReloadHandlers: Set<(args: { oldExports: Record<string, unknown>; newSrc: string; config: HotReloadConfig }) => AcceptNewExportsFn | undefined> | undefined = undefined;
interface HotReloadConfig {
mode?: 'patch-prototype' | undefined;
}
interface GlobalThisAddition {
$hotReload_applyNewExports?(args: { oldExports: Record<string, unknown>; newSrc: string; config?: HotReloadConfig }): AcceptNewExportsFn | undefined;
}
type AcceptNewExportsFn = (newExports: Record<string, unknown>) => boolean;
if (isHotReloadEnabled()) {
// This code does not run in production.
registerHotReloadHandler(({ oldExports, newSrc, config }) => {
if (config.mode !== 'patch-prototype') {
return undefined;
}
return newExports => {
for (const key in newExports) {
const exportedItem = newExports[key];
console.log(`[hot-reload] Patching prototype methods of '${key}'`, { exportedItem });
if (typeof exportedItem === 'function' && exportedItem.prototype) {
const oldExportedItem = oldExports[key];
if (oldExportedItem) {
for (const prop of Object.getOwnPropertyNames(exportedItem.prototype)) {
const descriptor = Object.getOwnPropertyDescriptor(exportedItem.prototype, prop)!;
const oldDescriptor = Object.getOwnPropertyDescriptor((oldExportedItem as any).prototype, prop);
if (descriptor?.value?.toString() !== oldDescriptor?.value?.toString()) {
console.log(`[hot-reload] Patching prototype method '${key}.${prop}'`);
}
Object.defineProperty((oldExportedItem as any).prototype, prop, descriptor);
}
newExports[key] = oldExportedItem;
}
}
}
return true;
};
});
}