From 26d78129faf0c9a6b1b8fbf848ca5ee78bcdaea2 Mon Sep 17 00:00:00 2001 From: Orta Date: Fri, 21 Aug 2020 07:43:59 -0400 Subject: [PATCH 1/6] Add support for creating a custom webworker subclass --- src/monaco.contribution.ts | 19 +++- src/monaco.d.ts | 4 + src/tsWorker.ts | 21 +++- src/workerManager.ts | 3 +- test/custom-worker.html | 211 +++++++++++++++++++++++++++++++++++++ test/custom-worker.js | 9 ++ 6 files changed, 262 insertions(+), 5 deletions(-) create mode 100644 test/custom-worker.html create mode 100644 test/custom-worker.js diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index c038d4b5..a26b7e2a 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -31,13 +31,15 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. private _eagerModelSync: boolean; private _compilerOptions!: monaco.languages.typescript.CompilerOptions; private _diagnosticsOptions!: monaco.languages.typescript.DiagnosticsOptions; + private _workerOptions!: monaco.languages.typescript.WorkerOptions; private _onDidExtraLibsChangeTimeout: number; - constructor(compilerOptions: monaco.languages.typescript.CompilerOptions, diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions) { + constructor(compilerOptions: monaco.languages.typescript.CompilerOptions, diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions, workerOptions: monaco.languages.typescript.WorkerOptions) { this._extraLibs = Object.create(null); this._eagerModelSync = false; this.setCompilerOptions(compilerOptions); this.setDiagnosticsOptions(diagnosticsOptions); + this.setWorkerOptions(workerOptions) this._onDidExtraLibsChangeTimeout = -1; } @@ -49,6 +51,10 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. return this._onDidExtraLibsChange.event; } + get workerOptions(): monaco.languages.typescript.WorkerOptions { + return this._workerOptions + } + getExtraLibs(): IExtraLibs { return this._extraLibs; } @@ -142,6 +148,11 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. this._onDidChange.fire(undefined); } + setWorkerOptions(options: monaco.languages.typescript.WorkerOptions): void { + this._workerOptions = options || Object.create(null); + this._onDidChange.fire(undefined); + } + setMaximumWorkerIdleTime(value: number): void { } @@ -202,11 +213,13 @@ enum ModuleResolutionKind { const typescriptDefaults = new LanguageServiceDefaultsImpl( { allowNonTsExtensions: true, target: ScriptTarget.Latest }, - { noSemanticValidation: false, noSyntaxValidation: false }); + { noSemanticValidation: false, noSyntaxValidation: false }, + {}); const javascriptDefaults = new LanguageServiceDefaultsImpl( { allowNonTsExtensions: true, allowJs: true, target: ScriptTarget.Latest }, - { noSemanticValidation: true, noSyntaxValidation: false }); + { noSemanticValidation: true, noSyntaxValidation: false }, + {}); function getTypeScriptWorker(): Promise<(...uris: monaco.Uri[]) => Promise> { return getMode().then(mode => mode.getTypeScriptWorker()); diff --git a/src/monaco.d.ts b/src/monaco.d.ts index 1e99b41b..698ce738 100644 --- a/src/monaco.d.ts +++ b/src/monaco.d.ts @@ -137,6 +137,10 @@ declare module monaco.languages.typescript { diagnosticCodesToIgnore?: number[]; } + export interface WorkerOptions { + customWorkerPath?: string; + } + interface IExtraLib { content: string; version: number; diff --git a/src/tsWorker.ts b/src/tsWorker.ts index e5560f86..a7e012db 100644 --- a/src/tsWorker.ts +++ b/src/tsWorker.ts @@ -238,8 +238,27 @@ export class TypeScriptWorker implements ts.LanguageServiceHost, monaco.language export interface ICreateData { compilerOptions: ts.CompilerOptions; extraLibs: IExtraLibs; + customWorkerPath?: string } export function create(ctx: IWorkerContext, createData: ICreateData): TypeScriptWorker { - return new TypeScriptWorker(ctx, createData); + let TSWorkerClass = TypeScriptWorker + if (createData.customWorkerPath) { + + // @ts-ignore - This is available in a webworker + if (typeof importScripts === "undefined") { + console.warn("Monaco is not using webworker workers, and that is needed to support the customWorkerPath flag") + } else { + // @ts-ignore - This is available in a webworker + importScripts(createData.customWorkerPath) + // @ts-ignore - This should come from + if(!self.extendTSWorkerFactory) { + throw new Error(`The script at ${createData.customWorkerPath} does not add extendTSWorkerFactory to self`) + } + // @ts-ignore - The throw validates this + TSWorkerClass = self.extendTSWorkerFactory(TypeScriptWorker) + } + } + + return new TSWorkerClass(ctx, createData); } diff --git a/src/workerManager.ts b/src/workerManager.ts index ff0b560e..fe6e2455 100644 --- a/src/workerManager.ts +++ b/src/workerManager.ts @@ -72,7 +72,8 @@ export class WorkerManager { // passed in to the create() method createData: { compilerOptions: this._defaults.getCompilerOptions(), - extraLibs: this._defaults.getExtraLibs() + extraLibs: this._defaults.getExtraLibs(), + customWorkerPath: this._defaults.workerOptions.customWorkerPath } }); diff --git a/test/custom-worker.html b/test/custom-worker.html new file mode 100644 index 00000000..fcaceaf3 --- /dev/null +++ b/test/custom-worker.html @@ -0,0 +1,211 @@ + + + + + + + + + +

Monaco Editor TypeScript test page

+ +
+

Compiler settings

+
+ + + + + + + + + + diff --git a/test/custom-worker.js b/test/custom-worker.js new file mode 100644 index 00000000..ad3c8dfd --- /dev/null +++ b/test/custom-worker.js @@ -0,0 +1,9 @@ +/// + +console.log("worker") + +self.extendTSWorkerFactory = (TypeScriptWorker) => { + return class MonacoTSWorker extends TypeScriptWorker { + + } +} From 3e1a236812d2c3b75a89f7205d8c3e81aee7bf64 Mon Sep 17 00:00:00 2001 From: Orta Date: Fri, 21 Aug 2020 08:04:24 -0400 Subject: [PATCH 2/6] Make a useful sample --- test/custom-worker.html | 35 ++++++++++++++++++++--------------- test/custom-worker.js | 9 +++++++-- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/test/custom-worker.html b/test/custom-worker.html index fcaceaf3..259a8907 100644 --- a/test/custom-worker.html +++ b/test/custom-worker.html @@ -1,3 +1,11 @@ + + @@ -10,9 +18,8 @@

Monaco Editor TypeScript test page

-

Compiler settings

-
- +

Custom webworker

+ diff --git a/test/custom-worker.js b/test/custom-worker.js index 498b5672..ccc7e0f4 100644 --- a/test/custom-worker.js +++ b/test/custom-worker.js @@ -1,4 +1,19 @@ -self.customTSWorkerFactory = (TypeScriptWorker) => { +// This example uses @typescript/vfs to create a virtual TS program +// which can do work on a bg thread. + +importScripts("https://unpkg.com/@typescript/vfs@1.3.0/dist/vfs.globals.js") + +/** + * + * @param {import("../src/tsWorker").TypeScriptWorker} TypeScriptWorker + * @param {import("typescript")} ts + * @param {Record} libFileMap + * + */ +const worker = (TypeScriptWorker, ts, libFileMap) => { + /** @type { import("@typescript/vfs") } */ + const tsvfs = globalThis.tsvfs + return class MonacoTSWorker extends TypeScriptWorker { // Adds a custom function to the webworker @@ -8,5 +23,44 @@ self.customTSWorkerFactory = (TypeScriptWorker) => { return (firstDTS && firstDTS.text) || "" } + async printAST(fileName) { + console.log("Creating virtual TS project") + const compilerOptions = this.getCompilationSettings() + const fsMap = new Map() + for (const key of Object.keys(libFileMap)) { + fsMap.set(key, "/" + libFileMap[key]) + } + + const thisCode = await this.getScriptText(fileName) + fsMap.set("index.ts", thisCode) + + console.log("Starting up TS program") + const system = tsvfs.createSystem(fsMap) + const host = tsvfs.createVirtualCompilerHost(system, compilerOptions, ts) + + const program = ts.createProgram({ + rootNames: [...fsMap.keys()], + options: compilerOptions, + host: host.compilerHost, + }) + + // Now I can look at the AST for the .ts file too + const mainSrcFile = program.getSourceFile("index.ts") + let miniAST = "SourceFile" + + const recurse = (parent, depth) => { + if (depth > 5) return + ts.forEachChild(parent, node => { + const spaces = " ".repeat(depth + 1) + miniAST += `\n${spaces}${ts.SyntaxKind[node.kind]}` + recurse(node, depth + 1) + }) + } + recurse(mainSrcFile, 0) + return miniAST + } + } } + +self.customTSWorkerFactory = worker From 5ee395c5eed1b570b2330fa644c7bb7ce8b42f50 Mon Sep 17 00:00:00 2001 From: Orta Date: Mon, 31 Aug 2020 13:38:06 -0400 Subject: [PATCH 6/6] Tightens the public API --- src/monaco.d.ts | 1 + src/tsWorker.ts | 16 +++++++++++----- test/custom-worker.js | 16 +++++----------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/monaco.d.ts b/src/monaco.d.ts index 698ce738..d63c60d6 100644 --- a/src/monaco.d.ts +++ b/src/monaco.d.ts @@ -138,6 +138,7 @@ declare module monaco.languages.typescript { } export interface WorkerOptions { + /** A full HTTP path to a JavaScript file which adds a function `customTSWorkerFactory` to the self inside a web-worker */ customWorkerPath?: string; } diff --git a/src/tsWorker.ts b/src/tsWorker.ts index f4079ae4..c785d75d 100644 --- a/src/tsWorker.ts +++ b/src/tsWorker.ts @@ -37,7 +37,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost, monaco.language return models.concat(Object.keys(this._extraLibs)); } - _getModel(fileName: string): monaco.worker.IMirrorModel | null { + private _getModel(fileName: string): monaco.worker.IMirrorModel | null { let models = this._ctx.getMirrorModels(); for (let i = 0; i < models.length; i++) { if (models[i].uri.toString() === fileName) { @@ -257,22 +257,28 @@ export interface ICreateData { customWorkerPath?: string } +/** The shape of the factory */ +export interface CustomTSWebWorkerFactory { + (TSWorkerClass: typeof TypeScriptWorker, ts: typeof import("typescript"), libs: Record): typeof TypeScriptWorker +} + export function create(ctx: IWorkerContext, createData: ICreateData): TypeScriptWorker { let TSWorkerClass = TypeScriptWorker if (createData.customWorkerPath) { - // @ts-ignore - This is available in a webworker if (typeof importScripts === "undefined") { console.warn("Monaco is not using webworkers for background tasks, and that is needed to support the customWorkerPath flag") } else { // @ts-ignore - This is available in a webworker importScripts(createData.customWorkerPath) + // @ts-ignore - This should come from the above eval - if (!self.customTSWorkerFactory) { + const workerFactoryFunc: CustomTSWebWorkerFactory | undefined = self.customTSWorkerFactory + if (!workerFactoryFunc) { throw new Error(`The script at ${createData.customWorkerPath} does not add customTSWorkerFactory to self`) } - // @ts-ignore - The throw validates this - TSWorkerClass = self.customTSWorkerFactory(TypeScriptWorker, ts, libFileMap) + + TSWorkerClass = workerFactoryFunc(TypeScriptWorker, ts, libFileMap) } } diff --git a/test/custom-worker.js b/test/custom-worker.js index ccc7e0f4..3eeacd1d 100644 --- a/test/custom-worker.js +++ b/test/custom-worker.js @@ -1,19 +1,14 @@ // This example uses @typescript/vfs to create a virtual TS program // which can do work on a bg thread. +// This version of the vfs edits the global scope (in the case of a webworker, this is 'self') importScripts("https://unpkg.com/@typescript/vfs@1.3.0/dist/vfs.globals.js") -/** - * - * @param {import("../src/tsWorker").TypeScriptWorker} TypeScriptWorker - * @param {import("typescript")} ts - * @param {Record} libFileMap - * - */ -const worker = (TypeScriptWorker, ts, libFileMap) => { - /** @type { import("@typescript/vfs") } */ - const tsvfs = globalThis.tsvfs +/** @type { import("@typescript/vfs") } */ +const tsvfs = globalThis.tsvfs +/** @type {import("../src/tsWorker").CustomTSWebWorkerFactory }*/ +const worker = (TypeScriptWorker, ts, libFileMap) => { return class MonacoTSWorker extends TypeScriptWorker { // Adds a custom function to the webworker @@ -59,7 +54,6 @@ const worker = (TypeScriptWorker, ts, libFileMap) => { recurse(mainSrcFile, 0) return miniAST } - } }