mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 20:26:08 +00:00
Implements prototype patching hot-reload
This commit is contained in:
committed by
Henning Dieterichs
parent
acff02431f
commit
48bd510125
4
scripts/debugger-scripts-api.d.ts
vendored
4
scripts/debugger-scripts-api.d.ts
vendored
@@ -15,8 +15,8 @@ interface IDisposable {
|
|||||||
dispose(): void;
|
dispose(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GlobalThisAddition extends globalThis {
|
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;
|
type AcceptNewExportsFn = (newExports: Record<string, unknown>) => boolean;
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ module.exports.run = async function (debugSession) {
|
|||||||
|
|
||||||
// A frozen copy of the previous exports
|
// A frozen copy of the previous exports
|
||||||
const oldExports = Object.freeze({ ...oldModule.exports });
|
const oldExports = Object.freeze({ ...oldModule.exports });
|
||||||
const reloadFn = g.$hotReload_applyNewExports?.(oldExports);
|
const reloadFn = g.$hotReload_applyNewExports?.({ oldExports, newSrc });
|
||||||
|
|
||||||
if (!reloadFn) {
|
if (!reloadFn) {
|
||||||
console.log(debugSessionName, 'ignoring js change, as module does not support hot-reload', relativePath);
|
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;
|
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) {
|
newScript(/* define */ function (deps, callback) {
|
||||||
// Evaluating the new code was successful.
|
// Evaluating the new code was successful.
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export function registerHotReloadHandler(handler: HotReloadHandler): IDisposable
|
|||||||
return { dispose() { } };
|
return { dispose() { } };
|
||||||
} else {
|
} else {
|
||||||
const handlers = registerGlobalHotReloadHandler();
|
const handlers = registerGlobalHotReloadHandler();
|
||||||
|
|
||||||
handlers.add(handler);
|
handlers.add(handler);
|
||||||
return {
|
return {
|
||||||
dispose() { handlers.delete(handler); }
|
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.
|
* 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;
|
export type AcceptNewExportsHandler = (newExports: Record<string, unknown>) => boolean;
|
||||||
|
|
||||||
function registerGlobalHotReloadHandler() {
|
function registerGlobalHotReloadHandler() {
|
||||||
@@ -50,10 +49,44 @@ function registerGlobalHotReloadHandler() {
|
|||||||
return hotReloadHandlers;
|
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 {
|
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;
|
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 { IModelContentChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents';
|
||||||
import { LineTokens } from 'vs/editor/common/tokens/lineTokens';
|
import { LineTokens } from 'vs/editor/common/tokens/lineTokens';
|
||||||
|
|
||||||
|
/* hot-reload:patch-prototype-methods */
|
||||||
|
|
||||||
export class BracketPairsTextModelPart extends Disposable implements IBracketPairsTextModelPart {
|
export class BracketPairsTextModelPart extends Disposable implements IBracketPairsTextModelPart {
|
||||||
private readonly bracketPairsTree = this._register(new MutableDisposable<IReference<BracketPairsTree>>());
|
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 { SparseMultilineTokens } from 'vs/editor/common/tokens/sparseMultilineTokens';
|
||||||
import { SparseTokensStore } from 'vs/editor/common/tokens/sparseTokensStore';
|
import { SparseTokensStore } from 'vs/editor/common/tokens/sparseTokensStore';
|
||||||
|
|
||||||
|
/* hot-reload:patch-prototype-methods */
|
||||||
|
|
||||||
export class TokenizationTextModelPart extends TextModelPart implements ITokenizationTextModelPart {
|
export class TokenizationTextModelPart extends TextModelPart implements ITokenizationTextModelPart {
|
||||||
private readonly _semanticTokens: SparseTokensStore = new SparseTokensStore(this._languageService.languageIdCodec);
|
private readonly _semanticTokens: SparseTokensStore = new SparseTokensStore(this._languageService.languageIdCodec);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user