Skip to content
This repository has been archived by the owner on Nov 5, 2021. It is now read-only.

Add support for creating a custom webworker subclass #65

Merged
merged 7 commits into from
Sep 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"url": "https://github.com/Microsoft/monaco-typescript/issues"
},
"devDependencies": {
"@typescript/vfs": "^1.2.0",
alexdima marked this conversation as resolved.
Show resolved Hide resolved
"monaco-editor-core": "^0.20.0",
"monaco-languages": "^1.10.0",
"monaco-plugin-helpers": "^1.0.2",
Expand Down
19 changes: 16 additions & 3 deletions src/monaco.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
}
alexdima marked this conversation as resolved.
Show resolved Hide resolved

setMaximumWorkerIdleTime(value: number): void {
}

Expand Down Expand Up @@ -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 },
{});
alexdima marked this conversation as resolved.
Show resolved Hide resolved

function getTypeScriptWorker(): Promise<(...uris: monaco.Uri[]) => Promise<monaco.languages.typescript.TypeScriptWorker>> {
return getMode().then(mode => mode.getTypeScriptWorker());
Expand Down
5 changes: 5 additions & 0 deletions src/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ declare module monaco.languages.typescript {
diagnosticCodesToIgnore?: number[];
}

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;
}

interface IExtraLib {
content: string;
version: number;
Expand Down
28 changes: 27 additions & 1 deletion src/tsWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IExtraLibs } from './monaco.contribution';

import IWorkerContext = monaco.worker.IWorkerContext;


export class TypeScriptWorker implements ts.LanguageServiceHost, monaco.languages.typescript.TypeScriptWorker {

// --- model sync -----------------------
Expand Down Expand Up @@ -253,8 +254,33 @@ export class TypeScriptWorker implements ts.LanguageServiceHost, monaco.language
export interface ICreateData {
compilerOptions: ts.CompilerOptions;
extraLibs: IExtraLibs;
customWorkerPath?: string
}

/** The shape of the factory */
export interface CustomTSWebWorkerFactory {
(TSWorkerClass: typeof TypeScriptWorker, ts: typeof import("typescript"), libs: Record<string, string>): typeof TypeScriptWorker
}

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 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
const workerFactoryFunc: CustomTSWebWorkerFactory | undefined = self.customTSWorkerFactory
if (!workerFactoryFunc) {
throw new Error(`The script at ${createData.customWorkerPath} does not add customTSWorkerFactory to self`)
}

TSWorkerClass = workerFactoryFunc(TypeScriptWorker, ts, libFileMap)
}
}

return new TSWorkerClass(ctx, createData);
}
3 changes: 2 additions & 1 deletion src/workerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
});

Expand Down
222 changes: 222 additions & 0 deletions test/custom-worker.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
<!--
To test this file, you need to use a local server. The recommendation is that you run:

npx serve .

Then open http://localhost:5000/test/custom-worker
-->

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<link rel="stylesheet" data-name="vs/editor/editor.main" href="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.css">
</head>
<body>

<h2>Monaco Editor TypeScript test page</h2>
<button id="resetBtn">Reset Sample</button>
<div id="container" style="width:800px;height:600px;border:1px solid grey"></div>
<h3>Custom webworker</h3>
<button id="logDTS">Log DTS</button>
<button id="getAST">Print AST to console</button>

<script>
var paths = {
'vs/basic-languages': '../node_modules/monaco-languages/release/dev',
'vs/language/typescript': '../release/dev',
'vs': '../node_modules/monaco-editor-core/dev/vs'
};
if (document.location.protocol === 'http:') {
// Add support for running local http server
let testIndex = document.location.pathname.indexOf('/test/');
if (testIndex !== -1) {
let prefix = document.location.pathname.substr(0, testIndex);
paths['vs/language/typescript'] = prefix + '/release/dev';
}
}
var require = {
paths: paths
};
</script>
<script src="../node_modules/monaco-editor-core/dev/vs/loader.js"></script>
<script src="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.nls.js"></script>
<script src="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.js"></script>

<script>
function getDefaultCode() {
return [
'/* Game of Life',
' * Implemented in TypeScript',
' * To learn more about TypeScript, please visit http://www.typescriptlang.org/',
' */',
'',
'module Conway {',
'',
' export class Cell {',
' public row: number;',
' public col: number;',
' public live: boolean;',
'',
' constructor(row: number, col: number, live: boolean) {',
' this.row = row;',
' this.col = col;',
' this.live = live',
' }',
' }',
'',
' export class GameOfLife {',
' private gridSize: number;',
' private canvasSize: number;',
' private lineColor: string;',
' private liveColor: string;',
' private deadColor: string;',
' private initialLifeProbability: number;',
' private animationRate: number;',
' private cellSize: number;',
' private context: CanvasRenderingContext2D;',
' private world;',
'',
'',
' constructor() {',
' this.gridSize = 50;',
' this.canvasSize = 600;',
' this.lineColor = \'#cdcdcd\';',
' this.liveColor = \'#666\';',
' this.deadColor = \'#eee\';',
' this.initialLifeProbability = 0.5;',
' this.animationRate = 60;',
' this.cellSize = 0;',
' this.world = this.createWorld();',
' this.circleOfLife();',
' }',
'',
' public createWorld() {',
' return this.travelWorld( (cell : Cell) => {',
' cell.live = Math.random() < this.initialLifeProbability;',
' return cell;',
' });',
' }',
'',
' public circleOfLife() : void {',
' this.world = this.travelWorld( (cell: Cell) => {',
' cell = this.world[cell.row][cell.col];',
' this.draw(cell);',
' return this.resolveNextGeneration(cell);',
' });',
' setTimeout( () => {this.circleOfLife()}, this.animationRate);',
' }',
'',
' public resolveNextGeneration(cell : Cell) {',
' var count = this.countNeighbors(cell);',
' var newCell = new Cell(cell.row, cell.col, cell.live);',
' if(count < 2 || count > 3) newCell.live = false;',
' else if(count == 3) newCell.live = true;',
' return newCell;',
' }',
'',
' public countNeighbors(cell : Cell) {',
' var neighbors = 0;',
' for(var row = -1; row <=1; row++) {',
' for(var col = -1; col <= 1; col++) {',
' if(row == 0 && col == 0) continue;',
' if(this.isAlive(cell.row + row, cell.col + col)) {',
' neighbors++;',
' }',
' }',
' }',
' return neighbors;',
' }',
'',
' public isAlive(row : number, col : number) {',
' if(row < 0 || col < 0 || row >= this.gridSize || col >= this.gridSize) return false;',
' return this.world[row][col].live;',
' }',
'',
' public travelWorld(callback) {',
' var result = [];',
' for(var row = 0; row < this.gridSize; row++) {',
' var rowData = [];',
' for(var col = 0; col < this.gridSize; col++) {',
' rowData.push(callback(new Cell(row, col, false)));',
' }',
' result.push(rowData);',
' }',
' return result;',
' }',
'',
' public draw(cell : Cell) {',
' if(this.context == null) this.context = this.createDrawingContext();',
' if(this.cellSize == 0) this.cellSize = this.canvasSize/this.gridSize;',
'',
' this.context.strokeStyle = this.lineColor;',
' this.context.strokeRect(cell.row * this.cellSize, cell.col*this.cellSize, this.cellSize, this.cellSize);',
' this.context.fillStyle = cell.live ? this.liveColor : this.deadColor;',
' this.context.fillRect(cell.row * this.cellSize, cell.col*this.cellSize, this.cellSize, this.cellSize);',
' }',
'',
' public createDrawingContext() {',
' var canvas = <HTMLCanvasElement> document.getElementById(\'conway-canvas\');',
' if(canvas == null) {',
' canvas = document.createElement(\'canvas\');',
' canvas.id = \'conway-canvas\';',
' canvas.width = this.canvasSize;',
' canvas.height = this.canvasSize;',
' document.body.appendChild(canvas);',
' }',
' return canvas.getContext(\'2d\');',
' }',
' }',
'}',
'',
'var game = new Conway.GameOfLife();',
].join('\n');
}

function getDefaultComplierOpts() {
return { target: 99, jsx: 1, allowNonTsExtensions: true }
}
require([
'vs/basic-languages/monaco.contribution',
'vs/language/typescript/monaco.contribution'
], () => {

monaco.languages.typescript.typescriptDefaults.setWorkerOptions({ customWorkerPath: "http://localhost:5000/test/custom-worker.js" })
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ target: 99, jsx: 1, allowNonTsExtensions: true, declaration: true, noLibCheck: true })

var editor = monaco.editor.create(document.getElementById('container'), {
value: localStorage.getItem("code") || getDefaultCode(),
language: 'typescript',
lightbulb: { enabled: true }
});

editor.onDidChangeModelContent(() => {
const code = editor.getModel().getValue()
localStorage.setItem("code", code)
});

document.getElementById('resetBtn').onclick = () => {
editor.setValue(getDefaultCode());
};

document.getElementById('logDTS').onclick = async () => {
const model = editor.getModel()
const worker = await monaco.languages.typescript.getTypeScriptWorker()
const thisWorker = await worker(model.uri)
const dts = await thisWorker.getDTSEmitForFile(model.uri.toString())
console.log(dts)
};

document.getElementById('getAST').onclick = async () => {
const model = editor.getModel()
const worker = await monaco.languages.typescript.getTypeScriptWorker()
const thisWorker = await worker(model.uri)
const ast = await thisWorker.printAST(model.uri.toString())
console.log(ast)
};

});
</script>
</body>
</html>
Loading