From c7b9ff67946afc9dd626fd2f3a06bf87f36e0bf4 Mon Sep 17 00:00:00 2001 From: Jordi Bunster Date: Sun, 25 Apr 2021 21:31:10 -0700 Subject: [PATCH] Lazy loaded Monaco (#720) Lazy loaded Monaco --- .../DiffEditor/DiffEditorComponent.ts | 6 +- .../Controls/Editor/EditorComponent.ts | 13 ++-- src/Explorer/Controls/Editor/EditorReact.tsx | 5 +- .../JsonEditor/JsonEditorComponent.ts | 13 ++-- .../IndexingPolicyComponent.tsx | 12 ++-- src/Explorer/LazyMonaco.ts | 5 ++ src/Explorer/Tabs/ScriptTabBase.ts | 65 +------------------ webpack.config.js | 11 ++-- 8 files changed, 40 insertions(+), 90 deletions(-) create mode 100644 src/Explorer/LazyMonaco.ts diff --git a/src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts b/src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts index 854cb26f3..a20bb4718 100644 --- a/src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts +++ b/src/Explorer/Controls/DiffEditor/DiffEditorComponent.ts @@ -1,6 +1,6 @@ import * as ViewModels from "../../../Contracts/ViewModels"; +import { loadMonaco, monaco } from "../../LazyMonaco"; import template from "./diff-editor-component.html"; -import * as monaco from "monaco-editor"; /** * Helper class for ko component registration @@ -92,7 +92,7 @@ export class DiffEditorViewModel { /** * Create the monaco editor on diff mode and attach to DOM */ - protected createDiffEditor( + protected async createDiffEditor( originalContent: string, modifiedContent: string, createCallback: (e: monaco.editor.IStandaloneDiffEditor) => void @@ -111,7 +111,7 @@ export class DiffEditorViewModel { } const language = this.params.editorLanguage || "json"; - + const monaco = await loadMonaco(); const originalModel = monaco.editor.createModel(originalContent, language); const modifiedModel = monaco.editor.createModel(modifiedContent, language); const diffEditor: monaco.editor.IStandaloneDiffEditor = monaco.editor.createDiffEditor( diff --git a/src/Explorer/Controls/Editor/EditorComponent.ts b/src/Explorer/Controls/Editor/EditorComponent.ts index 5f77c0a52..0b697c8e2 100644 --- a/src/Explorer/Controls/Editor/EditorComponent.ts +++ b/src/Explorer/Controls/Editor/EditorComponent.ts @@ -1,7 +1,6 @@ +import { loadMonaco, monaco } from "../../LazyMonaco"; import { JsonEditorParams, JsonEditorViewModel } from "../JsonEditor/JsonEditorComponent"; import template from "./editor-component.html"; -import * as monaco from "monaco-editor"; -import { SqlCompletionItemProvider, ErrorMarkProvider } from "@azure/cosmos-language-service"; /** * Helper class for ko component registration @@ -49,15 +48,17 @@ class EditorViewModel extends JsonEditorViewModel { return this.params.contentType; } - protected registerCompletionItemProvider() { - let sqlCompletionItemProvider = new SqlCompletionItemProvider(); + protected async registerCompletionItemProvider() { if (EditorViewModel.providerRegistered.indexOf("sql") < 0) { - monaco.languages.registerCompletionItemProvider("sql", sqlCompletionItemProvider); + const { SqlCompletionItemProvider } = await import("@azure/cosmos-language-service"); + const monaco = await loadMonaco(); + monaco.languages.registerCompletionItemProvider("sql", new SqlCompletionItemProvider()); EditorViewModel.providerRegistered.push("sql"); } } - protected getErrorMarkers(input: string): Q.Promise { + protected async getErrorMarkers(input: string): Promise { + const { ErrorMarkProvider } = await import("@azure/cosmos-language-service"); return ErrorMarkProvider.getErrorMark(input); } } diff --git a/src/Explorer/Controls/Editor/EditorReact.tsx b/src/Explorer/Controls/Editor/EditorReact.tsx index 49cf2aca2..71273ed20 100644 --- a/src/Explorer/Controls/Editor/EditorReact.tsx +++ b/src/Explorer/Controls/Editor/EditorReact.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import * as monaco from "monaco-editor"; +import { loadMonaco, monaco } from "../../LazyMonaco"; export interface EditorReactProps { language: string; @@ -61,7 +61,7 @@ export class EditorReact extends React.Component { /** * Create the monaco editor and attach to DOM */ - private createEditor(createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) { + private async createEditor(createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) { const options: monaco.editor.IEditorConstructionOptions = { value: this.props.content, language: this.props.language, @@ -74,6 +74,7 @@ export class EditorReact extends React.Component { }; this.rootNode.innerHTML = ""; + const monaco = await loadMonaco(); createCallback(monaco.editor.create(this.rootNode, options)); } diff --git a/src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts b/src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts index 542f3e6a8..381281d30 100644 --- a/src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts +++ b/src/Explorer/Controls/JsonEditor/JsonEditorComponent.ts @@ -1,6 +1,5 @@ -import Q from "q"; -import * as monaco from "monaco-editor"; import * as ViewModels from "../../../Contracts/ViewModels"; +import { loadMonaco, monaco } from "../../LazyMonaco"; import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel"; import template from "./json-editor-component.html"; @@ -88,7 +87,7 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel { /** * Create the monaco editor and attach to DOM */ - protected createEditor(content: string, createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) { + protected async createEditor(content: string, createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) { this.registerCompletionItemProvider(); this.editorContainer = document.getElementById(this.getEditorId()); const options: monaco.editor.IEditorConstructionOptions = { @@ -102,6 +101,7 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel { }; this.editorContainer.innerHTML = ""; + const monaco = await loadMonaco(); createCallback(monaco.editor.create(this.editorContainer, options)); } @@ -109,15 +109,16 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel { protected registerCompletionItemProvider() {} // Interface. Will be implemented in children editor view model such as EditorViewModel. - protected getErrorMarkers(input: string): Q.Promise { - return Q.Promise(() => {}); + protected async getErrorMarkers(_: string): Promise { + return []; } protected getEditorLanguage(): string { return "json"; } - protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) { + protected async configureEditor(editor: monaco.editor.IStandaloneCodeEditor) { + const monaco = await loadMonaco(); this.editor = editor; const queryEditorModel = this.editor.getModel(); if (!this.params.isReadOnly && this.params.updatedContent) { diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx index 631880844..9e4949bf1 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx @@ -1,9 +1,9 @@ +import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react"; import * as React from "react"; import * as DataModels from "../../../../Contracts/DataModels"; -import * as monaco from "monaco-editor"; -import { isDirty, isIndexTransforming } from "../SettingsUtils"; -import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react"; +import { loadMonaco, monaco } from "../../../LazyMonaco"; import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils"; +import { isDirty, isIndexTransforming } from "../SettingsUtils"; import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent"; export interface IndexingPolicyComponentProps { @@ -84,9 +84,9 @@ export class IndexingPolicyComponent extends React.Component< return false; }; - private createIndexingPolicyEditor = (): void => { + private async createIndexingPolicyEditor(): Promise { const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4); - + const monaco = await loadMonaco(); this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, { value: value, language: "json", @@ -98,7 +98,7 @@ export class IndexingPolicyComponent extends React.Component< indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this)); this.props.logIndexingPolicySuccessMessage(); } - }; + } private onEditorContentChange = (): void => { const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel(); diff --git a/src/Explorer/LazyMonaco.ts b/src/Explorer/LazyMonaco.ts new file mode 100644 index 000000000..4b1c97c52 --- /dev/null +++ b/src/Explorer/LazyMonaco.ts @@ -0,0 +1,5 @@ +import type * as monaco from "monaco-editor/esm/vs/editor/editor.api"; +export type { monaco }; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const loadMonaco = () => import(/* webpackChunkName: "lazy-monaco" */ "monaco-editor/esm/vs/editor/editor.api"); diff --git a/src/Explorer/Tabs/ScriptTabBase.ts b/src/Explorer/Tabs/ScriptTabBase.ts index a5a994a9b..4f270cb60 100644 --- a/src/Explorer/Tabs/ScriptTabBase.ts +++ b/src/Explorer/Tabs/ScriptTabBase.ts @@ -1,5 +1,4 @@ import * as ko from "knockout"; -import * as monaco from "monaco-editor"; import Q from "q"; import DiscardIcon from "../../../images/discard.svg"; import SaveIcon from "../../../images/save-cosmos.svg"; @@ -8,6 +7,7 @@ import editable from "../../Common/EditableUtility"; import * as DataModels from "../../Contracts/DataModels"; import * as ViewModels from "../../Contracts/ViewModels"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; +import { loadMonaco, monaco } from "../LazyMonaco"; import TabsBase from "./TabsBase"; export default abstract class ScriptTabBase extends TabsBase implements ViewModels.WaitsForTemplate { @@ -299,38 +299,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode return !!value; } - private static _toSeverity(severity: string): monaco.MarkerSeverity { - switch (severity.toLowerCase()) { - case "error": - return monaco.MarkerSeverity.Error; - case "warning": - return monaco.MarkerSeverity.Warning; - case "info": - return monaco.MarkerSeverity.Info; - case "ignore": - default: - return monaco.MarkerSeverity.Hint; - } - } - - private static _toEditorPosition(target: number, lines: string[]): ViewModels.EditorPosition { - let cursor: number = 0; - let previousCursor: number = 0; - let i: number = 0; - while (target > cursor + lines[i].length) { - cursor += lines[i].length + 2; - i++; - } - - const editorPosition: ViewModels.EditorPosition = { - line: i + 1, - column: target - cursor + 1, - }; - - return editorPosition; - } - - protected _createBodyEditor() { + protected async _createBodyEditor() { const id = this.editorId; const container = document.getElementById(id); const options = { @@ -341,7 +310,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode }; container.innerHTML = ""; - + const monaco = await loadMonaco(); const editor = monaco.editor.create(container, options); this.editor(editor); @@ -353,32 +322,4 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode const editorModel = this.editor().getModel(); this.editorContent(editorModel.getValue()); } - - private _setModelMarkers(errors: ViewModels.QueryError[]) { - const markers: monaco.editor.IMarkerData[] = errors.map((e) => this._toMarker(e)); - const editorModel = this.editor().getModel(); - monaco.editor.setModelMarkers(editorModel, this.tabId, markers); - } - - private _resetModelMarkers() { - const queryEditorModel = this.editor().getModel(); - monaco.editor.setModelMarkers(queryEditorModel, this.tabId, []); - } - - private _toMarker(error: ViewModels.QueryError): monaco.editor.IMarkerData { - const editorModel = this.editor().getModel(); - const lines: string[] = editorModel.getLinesContent(); - const start: ViewModels.EditorPosition = ScriptTabBase._toEditorPosition(Number(error.start), lines); - const end: ViewModels.EditorPosition = ScriptTabBase._toEditorPosition(Number(error.end), lines); - - return { - severity: ScriptTabBase._toSeverity(error.severity), - message: error.message, - startLineNumber: start.line, - startColumn: start.column, - endLineNumber: end.line, - endColumn: end.column, - code: error.code, - }; - } } diff --git a/webpack.config.js b/webpack.config.js index 8f3e1ba53..12c980224 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable no-undef */ require("dotenv/config"); const path = require("path"); const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin"); @@ -83,7 +85,8 @@ const typescriptRule = { exclude: /node_modules/, }; -module.exports = function (env = {}, argv = {}) { +// eslint-disable-next-line @typescript-eslint/no-unused-vars +module.exports = function (_env = {}, argv = {}) { const mode = argv.mode || "development"; const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule]; const envVars = { @@ -211,6 +214,7 @@ module.exports = function (env = {}, argv = {}) { util: true, tls: "empty", net: "empty", + fs: "empty", }, output: { chunkFilename: "[name].[chunkhash:6].js", @@ -264,7 +268,7 @@ module.exports = function (env = {}, argv = {}) { target: "https://main.documentdb.ext.azure.com", changeOrigin: true, logLevel: "debug", - bypass: function (req, res, proxyOptions) { + bypass: (req, res) => { if (req.method === "OPTIONS") { res.statusCode = 200; res.send(); @@ -304,8 +308,5 @@ module.exports = function (env = {}, argv = {}) { }, }, stats: "minimal", - node: { - fs: "empty", - }, }; };