Skip to content

Fix flow editor slowness due to many instances of monaco #5621

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cbdcc74
replace on click wt on pointer down on flow node click
diegoimbert Apr 15, 2025
6cdcd57
pointerdown on virtualitems
diegoimbert Apr 15, 2025
d70eeff
Load monaco async with a placeholder to avoid size flash
diegoimbert Apr 15, 2025
ae256e6
monaco placeholder for editor
diegoimbert Apr 15, 2025
3126069
less flashing
diegoimbert Apr 15, 2025
9ea976c
simulate first line bg
diegoimbert Apr 15, 2025
4bba218
better match to monaco
diegoimbert Apr 16, 2025
cd1260c
more fine tune
diegoimbert Apr 16, 2025
07779c4
fix for increased browser font sizes
diegoimbert Apr 16, 2025
82dd084
flow nodes feel much better to click on
diegoimbert Apr 16, 2025
80dd6e4
move setTimeout upwards
diegoimbert Apr 16, 2025
4b3e967
only load async in flow editor
diegoimbert Apr 16, 2025
712ed88
load async monaco in app
diegoimbert Apr 16, 2025
37ae655
lots of components dont respect the type and pass undefined
diegoimbert Apr 16, 2025
4fa2bd3
weird outline when opening and closing OutputPicker
diegoimbert Apr 16, 2025
e0f9edc
fixed hover flow nodes
diegoimbert Apr 16, 2025
9ad3c46
Merge branch 'main' into di/fix-flow-slowness
diegoimbert Apr 17, 2025
30c6a5f
moved setTimeout upwards
diegoimbert Apr 17, 2025
84a7530
hover color for virtual items
diegoimbert Apr 17, 2025
c3a37c9
wrong Cargo.lock
diegoimbert Apr 17, 2025
5bf1540
disable interaction if not selectable
diegoimbert Apr 17, 2025
e9acbe1
pixel perfect editor placeholder
diegoimbert Apr 17, 2025
9080602
fake monaco editor perfect in flow editor
diegoimbert Apr 17, 2025
ba28323
fake monaco for app json editor
diegoimbert Apr 17, 2025
c20fca1
(temp) never load editor monaco
diegoimbert Apr 17, 2025
2183c15
os dependant constants in monaco
diegoimbert Apr 17, 2025
3f896cc
Revert " (temp) never load editor monaco"
diegoimbert Apr 17, 2025
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
16 changes: 14 additions & 2 deletions frontend/src/lib/components/Editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
import GlobalReviewButtons from './copilot/chat/GlobalReviewButtons.svelte'
import { writable } from 'svelte/store'
import { formatResourceTypes } from './copilot/chat/core'
import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte'
// import EditorTheme from './EditorTheme.svelte'

let divEl: HTMLDivElement | null = null
Expand Down Expand Up @@ -221,6 +222,7 @@
export let disabled: boolean = false
export let lineNumbersMinChars = 3
export let isAiPanelOpen: boolean = false
export let loadAsync = false

const rHash = randomHash()
$: filePath = computePath(path)
Expand Down Expand Up @@ -1374,9 +1376,14 @@
})
}

onMount(() => {
onMount(async () => {
if (BROWSER) {
loadMonaco().then((x) => (disposeMethod = x))
if (loadAsync) {
// setTimeout(() => loadMonaco().then((x) => (disposeMethod = x)), 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commenting out the setTimeout means that when loadAsync is true, loadMonaco is never called. This may break the async loading behavior. Consider either removing the loadAsync branch entirely or calling loadMonaco directly (without setTimeout) so that the loading strategy is consistent. Also, avoid leaving dead/commented code in the repository.

Suggested change
// setTimeout(() => loadMonaco().then((x) => (disposeMethod = x)), 0)
loadMonaco().then((x) => (disposeMethod = x))

} else {
let m = await loadMonaco()
disposeMethod = m
}
}
})

Expand Down Expand Up @@ -1408,6 +1415,11 @@
</script>

<EditorTheme />
{#if !editor}
<div class="inset-0 absolute overflow-clip">
<FakeMonacoPlaceHolder {code} />
</div>
{/if}
<div bind:this={divEl} class="{$$props.class} editor {disabled ? 'disabled' : ''}"></div>
{#if $vimMode}
<div class="fixed bottom-0 z-30" bind:this={statusDiv}></div>
Expand Down
112 changes: 112 additions & 0 deletions frontend/src/lib/components/FakeMonacoPlaceHolder.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<!-- Used to avoid height jitter when loading monaco asynchronously -->

<script lang="ts">
import { getOS } from '$lib/utils'

type Props = {
code?: string
autoheight?: boolean
lineNumbersWidth?: number
lineNumbersOffset?: number
class?: string
fontSize?: number
}

let {
code,
autoheight = false,
lineNumbersWidth = 51,
lineNumbersOffset = 0,
class: className = '',
fontSize = 14
}: Props = $props()

// https://github.com/microsoft/vscode/blob/baa2dad3cdacd97ac02eff0604984faf1167ff1e/src/vs/editor/common/config/editorOptions.ts#L5421
const DEFAULT_WINDOWS_FONT_FAMILY = "Consolas, 'Courier New', monospace"
const DEFAULT_MAC_FONT_FAMILY = "Menlo, Monaco, 'Courier New', monospace"
const DEFAULT_LINUX_FONT_FAMILY = "'Droid Sans Mono', 'monospace', monospace"
const fontFamily =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid redundant getOS() calls: cache the result in a variable (e.g. const os = getOS();) to reuse it for both fontFamily and GOLDEN_LINE_HEIGHT_RATIO. This improves efficiency and keeps the code DRY.

getOS() === 'Windows'
? DEFAULT_WINDOWS_FONT_FAMILY
: getOS() === 'macOS'
? DEFAULT_MAC_FONT_FAMILY
: DEFAULT_LINUX_FONT_FAMILY

// https://github.com/microsoft/vscode/blob/baa2dad3cdacd97ac02eff0604984faf1167ff1e/src/vs/editor/common/config/fontInfo.ts#L14
const GOLDEN_LINE_HEIGHT_RATIO = getOS() == 'macOS' ? 1.5 : 1.35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use strict equality (===) instead of == when comparing getOS() result for consistency.

Suggested change
const GOLDEN_LINE_HEIGHT_RATIO = getOS() == 'macOS' ? 1.5 : 1.35
const GOLDEN_LINE_HEIGHT_RATIO = getOS() === 'macOS' ? 1.5 : 1.35


let lines = $derived(code?.split('\n') ?? [])

const charWidth = 9 // try to match as closely as possible to monaco editor

const lineHeight = fontSize * GOLDEN_LINE_HEIGHT_RATIO

let [clientWidth, clientHeight] = $state([0, 0])
let showHorizontalScrollbar = $derived(
lines.some((line) => line.length * charWidth > clientWidth - 40)
)

let [editorWidth, editorHeight] = $derived([
clientWidth,
autoheight ? lines.length * lineHeight + (showHorizontalScrollbar ? 12 : 0) : clientHeight
])
</script>

<!-- Copy pasted from actual monaco editor in the web inspector -->

<div
bind:clientWidth
bind:clientHeight
class="h-full w-full relative editor dark:bg-[#272D38] {className}"
style="--vscode-editorCodeLens-lineHeight: 18px; --vscode-editorCodeLens-fontSize: 12px; --vscode-editorCodeLens-fontFeatureSettings: 'liga' off, 'calt' off; --code-editorInlayHintsFontFamily: {fontFamily};"
>
<div
class="monaco-editor no-user-select mac standalone showUnused showDeprecated vs-dark"
role="code"
>
<div class="overflow-guard" style="width: {editorWidth}px; height: {editorHeight}px;">
<div
class="margin"
role="presentation"
aria-hidden="true"
style="position: absolute; transform: translate3d(0px, 0px, 0px); contain: strict; top: 0px; width: {editorWidth}px; height: {editorHeight}px;"
>
<div
class="margin-view-overlays"
role="presentation"
aria-hidden="true"
style="position: absolute; font-family: {fontFamily}; font-weight: normal; font-size: {fontSize}px; font-feature-settings: 'liga' 0, 'calt' 0; font-variation-settings: normal; line-height: {lineHeight}px; letter-spacing: 0px; width: {lineNumbersWidth}px; height: 4893px;"
>
{#each lines as _, i}
<div style="top:{lineHeight * i}px;height:{lineHeight}px;">
<div class="line-numbers" style="left:{lineNumbersOffset}px;width:25px;">{i + 1}</div>
</div>
{/each}
</div>
</div>
<div
class="monaco-scrollable-element editor-scrollable vs-dark mac"
style="position: absolute; overflow: hidden; left: {lineNumbersWidth}px; width: {editorWidth}px; height: {editorHeight}px"
>
<div
class="lines-content monaco-editor-background"
style="position: absolute; overflow: hidden; width: 1.67772e+07px; height: 1.67772e+07px; transform: translate3d(0px, 0px, 0px); contain: strict; top: 0px; left: 0px;"
>
<div
class="view-lines monaco-mouse-cursor-text text-tertiary/60"
style="line-height: {lineHeight}px; position: absolute; font-family: {fontFamily}; font-weight: normal; font-size: {fontSize}px; font-feature-settings: 'liga' 0, 'calt' 0; font-variation-settings: normal; line-height: {lineHeight}px; letter-spacing: 0px; width: 1143px; height: 789px;"
>
{#each lines as line, i}
<div
style="height: {lineHeight}px; top: {i * lineHeight}px;"
class="text-nowrap whitespace-pre"
>
{line}
</div>
{/each}
</div>
</div>
</div>
</div>
</div>
</div>
2 changes: 2 additions & 0 deletions frontend/src/lib/components/InputTransformForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@
on:change={() => {
dispatch('change', { argName, arg })
}}
loadAsync
/>
{/if}
</div>
Expand Down Expand Up @@ -583,6 +584,7 @@
focused = false
}}
autoHeight
loadAsync
/>
</div>
{#if !hideHelpButton}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib/components/JsonEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
export let error = ''
export let editor: SimpleEditor | undefined = undefined
export let small = false
export let loadAsync = false

$: tooBig = code && code?.length > 1000000

Expand Down Expand Up @@ -38,6 +39,7 @@
<div class="flex flex-col w-full">
<div class="border w-full">
<SimpleEditor
{loadAsync}
{small}
on:focus
on:blur
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/components/ResourcePicker.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
}}
/>

<div class="flex flex-col w-full items-start">
<div class="flex flex-col w-full items-start min-h-9">
<div class="flex flex-row w-full items-center">
{#if collection?.length > 0}
<Select
Expand Down
38 changes: 30 additions & 8 deletions frontend/src/lib/components/SimpleEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import { vimMode } from '$lib/stores'
import { initVim } from './monaco_keybindings'
import { buildWorkerDefinition } from '$lib/monaco_workers/build_workers'
import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte'
// import { createConfiguredEditor } from 'vscode/monaco'
// import type { IStandaloneCodeEditor } from 'vscode/vscode/vs/editor/standalone/browser/standaloneCodeEditor'

Expand Down Expand Up @@ -99,7 +100,8 @@
autofocus = false,
allowVim = false,
tailwindClasses = [],
class: className = ''
class: className = '',
loadAsync = false
} = $props<{
lang: string
code?: string
Expand All @@ -121,6 +123,7 @@
allowVim?: boolean
tailwindClasses?: string[]
class?: string
loadAsync?: boolean
}>()

const dispatch = createEventDispatcher()
Expand Down Expand Up @@ -279,6 +282,8 @@
}
})

let fontSize = $derived(small ? 12 : 14)

async function loadMonaco() {
await initializeVscode()
initialized = true
Expand Down Expand Up @@ -327,7 +332,7 @@
model,
lineDecorationsWidth: 6,
lineNumbersMinChars: 2,
fontSize: small ? 12 : 14,
fontSize: fontSize,
quickSuggestions: disableSuggestions
? { other: false, comments: false, strings: false }
: { other: true, comments: true, strings: true },
Expand Down Expand Up @@ -511,12 +516,16 @@

onMount(async () => {
if (BROWSER) {
mounted = true
await loadMonaco()
if (autofocus) {
setTimeout(() => {
focus()
if (loadAsync) {
setTimeout(async () => {
await loadMonaco()
mounted = true
if (autofocus) setTimeout(() => focus(), 0)
}, 0)
} else {
await loadMonaco()
mounted = true
if (autofocus) setTimeout(() => focus(), 0)
}
}
})
Expand Down Expand Up @@ -556,9 +565,22 @@
{suggestion}
</div>
{/if}

{#if !editor}
<FakeMonacoPlaceHolder
{code}
autoheight
lineNumbersWidth={(23 * fontSize) / 14}
lineNumbersOffset={fontSize == 14 ? -8 : -11}
{fontSize}
/>
{/if}

<div
bind:this={divEl}
class="relative {className} editor simple-editor {!allowVim ? 'nonmain-editor' : ''}"
class="relative {className} {!editor ? 'hidden' : ''} editor simple-editor {!allowVim
? 'nonmain-editor'
: ''}"
bind:clientWidth={width}
>
{#if placeholder}
Expand Down
27 changes: 24 additions & 3 deletions frontend/src/lib/components/TemplateEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import { initializeVscode } from './vscode'
import EditorTheme from './EditorTheme.svelte'
import { buildWorkerDefinition } from '$lib/monaco_workers/build_workers'
import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte'

export const conf = {
wordPattern:
Expand Down Expand Up @@ -374,6 +375,7 @@
export let autoHeight = true
export let fixedOverflowWidgets = true
export let fontSize = 16
export let loadAsync = false

if (typeof code != 'string') {
code = ''
Expand Down Expand Up @@ -594,8 +596,15 @@
let mounted = false
onMount(async () => {
if (BROWSER) {
await loadMonaco()
mounted = true
if (loadAsync) {
setTimeout(async () => {
await loadMonaco()
mounted = true
}, 0)
} else {
await loadMonaco()
mounted = true
}
}
})

Expand Down Expand Up @@ -626,10 +635,22 @@

<EditorTheme />

{#if !editor}
<FakeMonacoPlaceHolder
autoheight
{code}
lineNumbersWidth={23}
lineNumbersOffset={-8}
class="border template nonmain-editor rounded min-h-4 mx-0.5 overflow-clip"
/>
{/if}
<div
bind:this={divEl}
style="height: 18px;"
class="{$$props.class ?? ''} border template nonmain-editor rounded min-h-4 mx-0.5 overflow-clip"
class="{$$props.class ??
''} border template nonmain-editor rounded min-h-4 mx-0.5 overflow-clip {!editor
? 'hidden'
: ''}"
bind:clientWidth={width}
></div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@
<Splitpanes horizontal class="h-full">
<Pane size={50}>
<SimpleEditor
loadAsync
class="h-full w-full"
bind:this={editor}
lang="javascript"
bind:code={componentInput.expr}
bind:code={() => componentInput.expr ?? '', (e) => (componentInput.expr = e)}
shouldBindKey={false}
fixedOverflowWidgets={false}
{extraLib}
Expand Down Expand Up @@ -105,10 +106,11 @@
<div class="border relative">
{#if !fullscreen}
<SimpleEditor
loadAsync
small
bind:this={editor}
lang="javascript"
bind:code={componentInput.expr}
bind:code={() => componentInput.expr ?? '', (e) => (componentInput.expr = e)}
shouldBindKey={false}
{extraLib}
autoHeight
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@
{:else}
<div class="flex w-full flex-col">
<JsonEditor
loadAsync
small
bind:value={componentInput.value}
code={JSON.stringify(componentInput.value, null, 2)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,11 +358,12 @@

<div class="min-h-0 flex-grow" id="flow-editor-editor">
<Splitpanes horizontal>
<Pane bind:size={editorPanelSize} minSize={10}>
<Pane bind:size={editorPanelSize} minSize={10} class="relative">
{#if flowModule.value.type === 'rawscript'}
{#if !noEditor}
{#key flowModule.id}
<Editor
loadAsync
folding
path={$pathStore + '/' + flowModule.id}
bind:websocketAlive
Expand Down
Loading
Loading