From e39fa719bcacf94bda5e4dd8b6c7d308573ee35d Mon Sep 17 00:00:00 2001 From: placatus Date: Wed, 6 Feb 2019 00:50:50 +0200 Subject: [PATCH] Optimize how external libs are handled and allow for custom languages: * Adding/removing extra libs does not trigger a full worker refresh * Manual control over when the extra libs are sent to the worker * Adding a new lib with the same name replaces it inplace Also included, the capability to define custom languages --- scripts/bundle.js | 13 +++- src/monaco.contribution.ts | 131 ++++++++++++++++++++++++++++++------- src/monaco.d.ts | 15 +++++ src/tsMode.ts | 35 +++------- src/tsWorker.ts | 16 +++-- src/tsconfig.esm.json | 8 ++- src/tsconfig.json | 8 ++- 7 files changed, 167 insertions(+), 59 deletions(-) diff --git a/scripts/bundle.js b/scripts/bundle.js index e195e33d..6f39f345 100644 --- a/scripts/bundle.js +++ b/scripts/bundle.js @@ -29,6 +29,8 @@ bundleOne('monaco.contribution'); bundleOne('tsMode'); bundleOne('tsWorker'); +updateImports('monaco.contribution'); + function bundleOne(moduleId, exclude) { requirejs.optimize({ baseUrl: 'release/dev/', @@ -36,7 +38,8 @@ function bundleOne(moduleId, exclude) { out: 'release/min/' + moduleId + '.js', exclude: exclude, paths: { - 'vs/language/typescript': REPO_ROOT + '/release/dev' + 'vs/language/typescript': REPO_ROOT + '/release/dev', + 'vs/basic-languages': REPO_ROOT + '/node_modules/monaco-languages/release/dev' }, optimize: 'none' }, function(buildResponse) { @@ -53,3 +56,11 @@ function bundleOne(moduleId, exclude) { fs.writeFileSync(filePath, BUNDLED_FILE_HEADER + result.code); }) } + +function updateImports(moduleId) { + console.log(`ESM: updating relative imports paths for ${moduleId}...`); + const filePath = path.join(REPO_ROOT, 'release/esm/' + moduleId + '.js'); + var fileContents = fs.readFileSync(filePath).toString(); + fileContents = fileContents.replace(/vs\/basic-languages\//g, "../../basic-languages/"); + fs.writeFileSync(filePath, fileContents); +} \ No newline at end of file diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index 1e122b9f..6ef44379 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -5,6 +5,8 @@ 'use strict'; import * as mode from './tsMode'; +import * as tsDefinitions from 'vs/basic-languages/typescript/typescript'; +import * as jsDefinitions from 'vs/basic-languages/javascript/javascript'; import Emitter = monaco.Emitter; import IEvent = monaco.IEvent; @@ -15,17 +17,20 @@ import IDisposable = monaco.IDisposable; export class LanguageServiceDefaultsImpl implements monaco.languages.typescript.LanguageServiceDefaults { private _onDidChange = new Emitter(); - private _extraLibs: { [path: string]: string }; + private _extraLibs: { [path: string]: { content: string, version: number } }; private _workerMaxIdleTime: number; private _eagerModelSync: boolean; private _compilerOptions: monaco.languages.typescript.CompilerOptions; private _diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions; + private _languageId: string; + private _eagerExtraLibSync: boolean = true; - constructor(compilerOptions: monaco.languages.typescript.CompilerOptions, diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions) { + constructor(langualgeId: string, compilerOptions: monaco.languages.typescript.CompilerOptions, diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions) { this._extraLibs = Object.create(null); this._workerMaxIdleTime = 2 * 60 * 1000; this.setCompilerOptions(compilerOptions); this.setDiagnosticsOptions(diagnosticsOptions); + this._languageId = langualgeId; } get onDidChange(): IEvent { @@ -46,21 +51,44 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. } if (this._extraLibs[filePath]) { - throw new Error(`${filePath} already a extra lib`); + this._extraLibs[filePath].version++; + this._extraLibs[filePath].content = content; + } else { + this._extraLibs[filePath] = { + content: content, + version: 1 + }; + } + if (this._eagerExtraLibSync) { + this.syncExtraLibs(); } - - this._extraLibs[filePath] = content; - this._onDidChange.fire(this); return { dispose: () => { - if (delete this._extraLibs[filePath]) { - this._onDidChange.fire(this); + if (delete this._extraLibs[filePath] && this._eagerExtraLibSync) { + this.syncExtraLibs(); } } }; } + async syncExtraLibs() { + try { + let worker; + // we don't care if the get language worker fails. + // This happens because the worker initialzies much slower than the addExtraLib calls + try { + worker = await getLanguageWorker(this._languageId); + } catch (ignored) { + return; + } + const client = await worker(""); + client.syncExtraLibs(this._extraLibs); + } catch (error) { + console.error(error); + } + } + getCompilerOptions(): monaco.languages.typescript.CompilerOptions { return this._compilerOptions; } @@ -98,6 +126,10 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. getEagerModelSync() { return this._eagerModelSync; } + + setEagerExtraLibSync(value: boolean) { + this._eagerExtraLibSync = value; + } } //#region enums copied from typescript to prevent loading the entire typescriptServices --- @@ -138,20 +170,57 @@ enum ModuleResolutionKind { } //#endregion -const typescriptDefaults = new LanguageServiceDefaultsImpl( - { allowNonTsExtensions: true, target: ScriptTarget.Latest }, - { noSemanticValidation: false, noSyntaxValidation: false }); +const languageDefaultOptions = { + javascript: { + compilerOptions: { allowNonTsExtensions: true, allowJs: true, target: ScriptTarget.Latest }, + diagnosticsOptions: { noSemanticValidation: true, noSyntaxValidation: false }, + }, + typescript: { + compilerOptions: { allowNonTsExtensions: true, target: ScriptTarget.Latest }, + diagnosticsOptions: { noSemanticValidation: false, noSyntaxValidation: false } + } +} + +const languageDefaults: { [name: string]: LanguageServiceDefaultsImpl } = {}; -const javascriptDefaults = new LanguageServiceDefaultsImpl( - { allowNonTsExtensions: true, allowJs: true, target: ScriptTarget.Latest }, - { noSemanticValidation: true, noSyntaxValidation: false }); +function setupLanguageServiceDefaults(languageId, isTypescript) { + const languageOptions = languageDefaultOptions[isTypescript ? "typescript" : "javascript"] + languageDefaults[languageId] = new LanguageServiceDefaultsImpl(languageId, languageOptions.compilerOptions, languageOptions.diagnosticsOptions); +} + +setupLanguageServiceDefaults("typescript", true); +setupLanguageServiceDefaults("javascript", false); function getTypeScriptWorker(): Promise { - return getMode().then(mode => mode.getTypeScriptWorker()); + return getLanguageWorker("typescript"); } function getJavaScriptWorker(): Promise { - return getMode().then(mode => mode.getJavaScriptWorker()); + return getLanguageWorker("javascript"); +} + +function getLanguageWorker(languageName: string): Promise { + return getMode().then(mode => mode.getNamedLanguageWorker(languageName)); +} + +function getLanguageDefaults(languageName: string): LanguageServiceDefaultsImpl { + return languageDefaults[languageName]; +} + +function setupNamedLanguage(languageDefinition: monaco.languages.ILanguageExtensionPoint, isTypescript: boolean, registerLanguage?: boolean): void { + if (registerLanguage) { + monaco.languages.register(languageDefinition); + + const langageConfig = isTypescript ? tsDefinitions : jsDefinitions; + monaco.languages.setMonarchTokensProvider(languageDefinition.id, langageConfig.language); + monaco.languages.setLanguageConfiguration(languageDefinition.id, langageConfig.conf); + } + + setupLanguageServiceDefaults(languageDefinition.id, isTypescript); + + monaco.languages.onLanguage(languageDefinition.id, () => { + return getMode().then(mode => mode.setupNamedLanguage(languageDefinition.id, isTypescript, languageDefaults[languageDefinition.id])); + }); } // Export API @@ -162,10 +231,13 @@ function createAPI(): typeof monaco.languages.typescript { NewLineKind: NewLineKind, ScriptTarget: ScriptTarget, ModuleResolutionKind: ModuleResolutionKind, - typescriptDefaults: typescriptDefaults, - javascriptDefaults: javascriptDefaults, + typescriptDefaults: getLanguageDefaults("typescript"), + javascriptDefaults: getLanguageDefaults("javascript"), + getLanguageDefaults: getLanguageDefaults, getTypeScriptWorker: getTypeScriptWorker, - getJavaScriptWorker: getJavaScriptWorker + getJavaScriptWorker: getJavaScriptWorker, + getLanguageWorker: getLanguageWorker, + setupNamedLanguage: setupNamedLanguage } } monaco.languages.typescript = createAPI(); @@ -176,9 +248,18 @@ function getMode(): Promise { return import('./tsMode'); } -monaco.languages.onLanguage('typescript', () => { - return getMode().then(mode => mode.setupTypeScript(typescriptDefaults)); -}); -monaco.languages.onLanguage('javascript', () => { - return getMode().then(mode => mode.setupJavaScript(javascriptDefaults)); -}); +setupNamedLanguage({ + id: 'typescript', + extensions: ['.ts', '.tsx'], + aliases: ['TypeScript', 'ts', 'typescript'], + mimetypes: ['text/typescript'] +}, true); + +setupNamedLanguage({ + id: 'javascript', + extensions: ['.js', '.es6', '.jsx'], + firstLine: '^#!.*\\bnode', + filenames: ['jakefile'], + aliases: ['JavaScript', 'javascript', 'js'], + mimetypes: ['text/javascript'], +}, false); \ No newline at end of file diff --git a/src/monaco.d.ts b/src/monaco.d.ts index 2a3bab0c..44ae26d7 100644 --- a/src/monaco.d.ts +++ b/src/monaco.d.ts @@ -164,6 +164,17 @@ declare module monaco.languages.typescript { * to the worker on start or restart. */ setEagerModelSync(value: boolean): void; + + /** + * Configure if the extra libs should be eagerly synced after each addExtraLibCall. + * This is true by default + */ + setEagerExtraLibSync(value: boolean): void; + + /** + * If EagerExtraLibSync is disabled, call this to trigger the changes. + */ + syncExtraLibs(): void; } export var typescriptDefaults: LanguageServiceDefaults; @@ -171,4 +182,8 @@ declare module monaco.languages.typescript { export var getTypeScriptWorker: () => Promise; export var getJavaScriptWorker: () => Promise; + + export var getLanguageWorker: (langaugeName: string) => Promise; + export var setupNamedLanguage: (languageDefinition: languages.ILanguageExtensionPoint, isTypescript: boolean, registerLanguage?: boolean) => void; + export var getLanguageDefaults: (languageName: string) => LanguageServiceDefaults; } diff --git a/src/tsMode.ts b/src/tsMode.ts index ce9caeca..e2fdf643 100644 --- a/src/tsMode.ts +++ b/src/tsMode.ts @@ -11,40 +11,23 @@ import * as languageFeatures from './languageFeatures'; import Uri = monaco.Uri; -let javaScriptWorker: (first: Uri, ...more: Uri[]) => Promise; -let typeScriptWorker: (first: Uri, ...more: Uri[]) => Promise; +let scriptWorkerMap: { [name: string]: (first: Uri, ...more: Uri[]) => Promise } = {}; -export function setupTypeScript(defaults: LanguageServiceDefaultsImpl): void { - typeScriptWorker = setupMode( +export function setupNamedLanguage(languageName: string, isTypescript: boolean, defaults: LanguageServiceDefaultsImpl): void { + scriptWorkerMap[languageName + "Worker"] = setupMode( defaults, - 'typescript' + languageName ); } -export function setupJavaScript(defaults: LanguageServiceDefaultsImpl): void { - javaScriptWorker = setupMode( - defaults, - 'javascript' - ); -} - -export function getJavaScriptWorker(): Promise<(first: Uri, ...more: Uri[]) => Promise> { - return new Promise((resolve, reject) => { - if (!javaScriptWorker) { - return reject("JavaScript not registered!"); - } - - resolve(javaScriptWorker); - }); -} - -export function getTypeScriptWorker(): Promise<(first: Uri, ...more: Uri[]) => Promise> { +export function getNamedLanguageWorker(languageName: string): Promise<(first: Uri, ...more: Uri[]) => Promise> { + let workerName = languageName + "Worker"; return new Promise((resolve, reject) => { - if (!typeScriptWorker) { - return reject("TypeScript not registered!"); + if (!scriptWorkerMap[workerName]) { + return reject(languageName + " not registered!"); } - resolve(typeScriptWorker); + resolve(scriptWorkerMap[workerName]); }); } diff --git a/src/tsWorker.ts b/src/tsWorker.ts index ef154b80..f5668fa1 100644 --- a/src/tsWorker.ts +++ b/src/tsWorker.ts @@ -24,7 +24,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { // --- model sync ----------------------- private _ctx: IWorkerContext; - private _extraLibs: { [fileName: string]: string } = Object.create(null); + private _extraLibs: { [path: string]: { content: string, version: number } } = Object.create(null); private _languageService = ts.createLanguageService(this); private _compilerOptions: ts.CompilerOptions; @@ -59,9 +59,11 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { let model = this._getModel(fileName); if (model) { return model.version.toString(); - } else if (this.isDefaultLibFileName(fileName) || fileName in this._extraLibs) { - // extra lib and default lib are static + } else if (this.isDefaultLibFileName(fileName)) { + // default lib is static return '1'; + } else if(fileName in this._extraLibs) { + return this._extraLibs[fileName].version.toString(); } } @@ -74,7 +76,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { } else if (fileName in this._extraLibs) { // static extra lib - text = this._extraLibs[fileName]; + text = this._extraLibs[fileName].content; } else if (fileName === DEFAULT_LIB.NAME) { text = DEFAULT_LIB.CONTENTS; @@ -196,11 +198,15 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { getEmitOutput(fileName: string): Promise { return Promise.resolve(this._languageService.getEmitOutput(fileName)); } + + syncExtraLibs(extraLibs: { [path: string]: { content: string, version: number } }) { + this._extraLibs = extraLibs; + } } export interface ICreateData { compilerOptions: ts.CompilerOptions; - extraLibs: { [path: string]: string }; + extraLibs: { [path: string]: { content: string, version: number } }; } export function create(ctx: IWorkerContext, createData: ICreateData): TypeScriptWorker { diff --git a/src/tsconfig.esm.json b/src/tsconfig.esm.json index b95798a9..546491e4 100644 --- a/src/tsconfig.esm.json +++ b/src/tsconfig.esm.json @@ -9,7 +9,13 @@ "es5", "es2015.collection", "es2015.promise" - ] + ], + "baseUrl": "", + "paths": { + "vs/basic-languages/*": [ + "../node_modules/monaco-languages/release/esm/*" + ] + }, }, "include": [ "**/*.ts" diff --git a/src/tsconfig.json b/src/tsconfig.json index f4ff52fe..418cf371 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -9,7 +9,13 @@ "es5", "es2015.collection", "es2015.promise" - ] + ], + "baseUrl": "", + "paths": { + "vs/basic-languages/*": [ + "../node_modules/monaco-languages/release/dev/*" + ] + }, }, "include": [ "**/*.ts"