Implements prototype patching hot-reload
parent
acff02431f
commit
48bd510125
|
@ -15,8 +15,8 @@ interface IDisposable {
|
|||
dispose(): void;
|
||||
}
|
||||
|
||||
interface GlobalThisAddition extends globalThis {
|
||||
$hotReload_applyNewExports?(oldExports: Record<string, unknown>): AcceptNewExportsFn | undefined;
|
||||
interface GlobalThisAddition {
|
||||
$hotReload_applyNewExports?(args: { oldExports: Record<string, unknown>; newSrc: string }): AcceptNewExportsFn | undefined;
|
||||
}
|
||||
|
||||
type AcceptNewExportsFn = (newExports: Record<string, unknown>) => boolean;
|
||||
|
|
|
@ -85,7 +85,7 @@ module.exports.run = async function (debugSession) {
|
|||
|
||||
// A frozen copy of the previous exports
|
||||
const oldExports = Object.freeze({ ...oldModule.exports });
|
||||
const reloadFn = g.$hotReload_applyNewExports?.(oldExports);
|
||||
const reloadFn = g.$hotReload_applyNewExports?.({ oldExports, newSrc });
|
||||
|
||||
if (!reloadFn) {
|
||||
console.log(debugSessionName, 'ignoring js change, as module does not support hot-reload', relativePath);
|
||||
|
@ -94,7 +94,11 @@ module.exports.run = async function (debugSession) {
|
|||
return;
|
||||
}
|
||||
|
||||
const newScript = new Function('define', newSrc); // CodeQL [SM01632] This code is only executed during development. It is required for the hot-reload functionality.
|
||||
// Eval maintains source maps
|
||||
function newScript(/* this parameter is used by newSrc */ define) {
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(newSrc); // CodeQL [SM01632] This code is only executed during development. It is required for the hot-reload functionality.
|
||||
}
|
||||
|
||||
newScript(/* define */ function (deps, callback) {
|
||||
// Evaluating the new code was successful.
|
||||
|
|
|
@ -14,7 +14,6 @@ export function registerHotReloadHandler(handler: HotReloadHandler): IDisposable
|
|||
return { dispose() { } };
|
||||
} else {
|
||||
const handlers = registerGlobalHotReloadHandler();
|
||||
|
||||
handlers.add(handler);
|
||||
return {
|
||||
dispose() { handlers.delete(handler); }
|
||||
|
@ -28,7 +27,7 @@ export function registerHotReloadHandler(handler: HotReloadHandler): IDisposable
|
|||
*
|
||||
* If no handler can apply the new exports, the module will not be reloaded.
|
||||
*/
|
||||
export type HotReloadHandler = (oldExports: Record<string, unknown>) => AcceptNewExportsHandler | undefined;
|
||||
export type HotReloadHandler = (args: { oldExports: Record<string, unknown>; newSrc: string }) => AcceptNewExportsHandler | undefined;
|
||||
export type AcceptNewExportsHandler = (newExports: Record<string, unknown>) => boolean;
|
||||
|
||||
function registerGlobalHotReloadHandler() {
|
||||
|
@ -50,10 +49,44 @@ function registerGlobalHotReloadHandler() {
|
|||
return hotReloadHandlers;
|
||||
}
|
||||
|
||||
let hotReloadHandlers: Set<(oldExports: Record<string, unknown>) => AcceptNewExportsFn | undefined> | undefined = undefined;
|
||||
let hotReloadHandlers: Set<(args: { oldExports: Record<string, unknown>; newSrc: string }) => AcceptNewExportsFn | undefined> | undefined = undefined;
|
||||
|
||||
interface GlobalThisAddition {
|
||||
$hotReload_applyNewExports?(oldExports: Record<string, unknown>): AcceptNewExportsFn | undefined;
|
||||
$hotReload_applyNewExports?(args: { oldExports: Record<string, unknown>; newSrc: string }): AcceptNewExportsFn | undefined;
|
||||
}
|
||||
|
||||
|
||||
type AcceptNewExportsFn = (newExports: Record<string, unknown>) => boolean;
|
||||
|
||||
if (isHotReloadEnabled()) {
|
||||
// This code does not run in production.
|
||||
registerHotReloadHandler(({ oldExports, newSrc }) => {
|
||||
// Don't match its own source code
|
||||
if (newSrc.indexOf('/* ' + 'hot-reload:patch-prototype-methods */') === -1) {
|
||||
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;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ import { BracketInfo, BracketPairInfo, BracketPairWithMinIndentationInfo, IBrack
|
|||
import { IModelContentChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents';
|
||||
import { LineTokens } from 'vs/editor/common/tokens/lineTokens';
|
||||
|
||||
/* hot-reload:patch-prototype-methods */
|
||||
|
||||
export class BracketPairsTextModelPart extends Disposable implements IBracketPairsTextModelPart {
|
||||
private readonly bracketPairsTree = this._register(new MutableDisposable<IReference<BracketPairsTree>>());
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@ import { LineTokens } from 'vs/editor/common/tokens/lineTokens';
|
|||
import { SparseMultilineTokens } from 'vs/editor/common/tokens/sparseMultilineTokens';
|
||||
import { SparseTokensStore } from 'vs/editor/common/tokens/sparseTokensStore';
|
||||
|
||||
/* hot-reload:patch-prototype-methods */
|
||||
|
||||
export class TokenizationTextModelPart extends TextModelPart implements ITokenizationTextModelPart {
|
||||
private readonly _semanticTokens: SparseTokensStore = new SparseTokensStore(this._languageService.languageIdCodec);
|
||||
|
||||
|
|
Loading…
Reference in New Issue