Skip to content

Improve editor load and keep Monaco features stable.#1658

Merged
elalish merged 5 commits into
elalish:masterfrom
israrkhan921:improve-editor-lazy-load-and-suggestions
Apr 21, 2026
Merged

Improve editor load and keep Monaco features stable.#1658
elalish merged 5 commits into
elalish:masterfrom
israrkhan921:improve-editor-lazy-load-and-suggestions

Conversation

@israrkhan921

Copy link
Copy Markdown
Contributor

Load important Monaco parts first, move heavy parts to background loading, and fix local dev service worker behavior so editor features stay reliable.

Load important Monaco parts first, move heavy parts to background loading, and fix local dev service worker behavior so editor features stay reliable.
@codecov

codecov Bot commented Apr 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.29%. Comparing base (a74d946) to head (03d7c3e).
⚠️ Report is 8 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1658      +/-   ##
==========================================
- Coverage   95.38%   95.29%   -0.10%     
==========================================
  Files          34       36       +2     
  Lines        7839     7943     +104     
==========================================
+ Hits         7477     7569      +92     
- Misses        362      374      +12     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@israrkhan921

Copy link
Copy Markdown
Contributor Author

@elalish
need to explain Sir:

Changed Monaco loading to lazy loading

Before: big Monaco parts were loaded early.
Now: only needed parts are loaded first, so first page load is faster.
Loaded Monaco core modules with dynamic import

Editor API, TypeScript contribution, and workers now load through loadMonacoModules().
This avoids heavy top-level imports.
Suggestions are loaded early (for reliability)

Added ensureMonacoSuggestLoaded().
It loads only:
suggest controller
parameter hints
These are loaded before editor creation, so autocomplete works more consistently.
Navigation features are loaded early

Added ensureMonacoNavigationLoaded().
It loads:
links
hover
go-to-definition commands
This brings blue links / hover / navigation earlier, without loading full editor.all.
Heavy Monaco bundle moved to background

editor.all is still used, but now loaded later with ensureMonacoContributionsLoaded().
It starts in background after editor is interactive.
Auto-typings moved to background

monaco-editor-auto-typings is now imported lazily in initializeAutoTypings().
It is started by ensureAutoTypings() in background, not blocking first paint.
Background enhancement flow added

startEnhancements() runs once and starts:
full Monaco contributions
auto-typings
Triggered by:
setTimeout(..., 0) (as soon as possible, non-blocking)
first editor focus
first typing
Service worker behavior improved for local dev

Added skipServiceWorker logic for:
?no-sw
import.meta.env.DEV
localhost / 127.0.0.1
This avoids local fetch/cache issues that were breaking editor behavior.
Editor styling/color fix

Added Monaco CSS imports for editor and tokens.

Replaced wrong module/moduleResolution setup with:
target: ScriptTarget.ESNext
Keeps TS language behavior correct in Monaco.
Safer initialization checks

Added guard in getModelForScript() if Monaco is not ready yet.
Changed worker ready check to use editor != null before initialize run logic.

In short: this PR makes initial load lighter, keeps suggestions/navigation reliable, fixes local dev service worker issues, and restores proper editor colors.

@elalish

elalish commented Apr 16, 2026

Copy link
Copy Markdown
Owner

Thanks, before I review this in more detail, would you mind showing some before/after of the chrome dev tools performance metrics and perhaps the page load graphics? Also, can you describe as a user the difference you see, particularly if you throttle your network? How soon do the buttons react? When is the script visible? How long until it runs and shows the model?

@israrkhan921

israrkhan921 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, before I review this in more detail, would you mind showing some before/after of the chrome dev tools performance metrics and perhaps the page load graphics? Also, can you describe as a user the difference you see, particularly if you throttle your network? How soon do the buttons react? When is the script visible? How long until it runs and shows the model?

@elalish

the videos are on 5.00x speed:

before: https://drive.google.com/file/d/1PSJj8bFbghHWk_0pfXyWuF0xop7ED-Gc/view?usp=drive_link

after: https://drive.google.com/file/d/11VwLIDYdlaXV_7mxZzJbYE5oo4eksY-U/view?usp=drive_link

before-- after--

@elalish elalish left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@tonious This is refactoring code you most recently touched - would you be up for giving this a review?

Comment thread bindings/wasm/examples/editor/editor.js Outdated
const {monaco: monacoApi, editorWorker, tsWorker} = await loadMonacoModules();
monaco = monacoApi;
await ensureMonacoSuggestLoaded();
await ensureMonacoNavigationLoaded();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

In your after video, it appears that esbuild.wasm is only triggered to download after the Run button is pressed. Can that also be proactively pulled down in the background with everything else?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In your after video, it appears that esbuild.wasm is only triggered to download after the Run button is pressed. Can that also be proactively pulled down in the background with everything else?

yes , i am going to start work on this ,and i will make a video

@elalish elalish requested a review from tonious April 17, 2026 07:01
@israrkhan921

israrkhan921 commented Apr 17, 2026

Copy link
Copy Markdown
Contributor Author

@elalish elalish left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This is looking pretty ready to me, except for the syntax highlighting issue. Can you do a before/after pass and look for anything else that may have changed in the UX?

enhancementsStarted = true;
// Start downloads in background; don't block editor interactivity.
ensureEsbuildWasmPreloaded().catch(error => {
console.warn('Failed to preload esbuild.wasm:', error);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Not for this PR, but esbuild.wasm looks like it's pulling 13MB or so, which is ridiculous. It would be interesting to see if there's some way we could pare that down.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yeah... Unfortunately that pretty much means replacing it.

Comment thread bindings/wasm/examples/editor/editor.js Outdated
async function createEditor() {
const {monaco: monacoApi, editorWorker, tsWorker} = await loadMonacoModules();
monaco = monacoApi;
await ensureMonacoSuggestLoaded();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Not for this PR, but I believe there are multiple ways to load Monaco. I believe we're now loading more of their code than we're actually using, so it would be great to look into minimizing that. Might be good to make a little chart of our download sizes and what they're used for.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not for this PR, but I believe there are multiple ways to load Monaco. I believe we're now loading more of their code than we're actually using, so it would be great to look into minimizing that. Might be good to make a little chart of our download sizes and what they're used for.

yes , we will work on this

@israrkhan921

Copy link
Copy Markdown
Contributor Author

@elalish elalish left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Looks good, just a couple of minor nits.

Comment thread bindings/wasm/examples/editor/editor.js Outdated

async function createEditor() {
const {monaco: monacoApi, editorWorker, tsWorker} = await loadMonacoModules();
monaco = monacoApi;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I think you're just renaming this and then back again, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes Sir
I destructured as monacoApi to avoid confusion with the outer monaco variable, then assigned to the module-level monaco,I will simplify naming.

minimap: {enabled: false},
quickSuggestions: true,
suggestOnTriggerCharacters: true,
parameterHints: {enabled: true},

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

These look interesting - how did you decide which to use?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These were chosen to restore expected Monaco TS UX while keeping imports minimal:

quickSuggestions: shows completions while typing (no manual trigger needed).
suggestOnTriggerCharacters: supports . / import-path trigger behavior.
parameterHints: function signature help while typing arguments.

I enabled only these because they are low-cost and directly tied to the delayed-intellisense issue.

Comment thread bindings/wasm/examples/editor/editor.js Outdated
let monacoContributionsReady = false;
let autoTypings = undefined;
let autoTypingsPromise = undefined;
let esbuildWasmPreloadPromise = undefined;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I prefer to use null instead of undefined pretty much across the board. It tends to be easier to debug and even runs a touch faster, plus it's easier to type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok , i am going to replace it with null.

@tonious tonious left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good! There's a few small comments in here, but they're all optional.

enhancementsStarted = true;
// Start downloads in background; don't block editor interactivity.
ensureEsbuildWasmPreloaded().catch(error => {
console.warn('Failed to preload esbuild.wasm:', error);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yeah... Unfortunately that pretty much means replacing it.

Comment thread bindings/wasm/examples/editor/editor.js Outdated
let updateTypeIndicator = () => {};

async function loadMonacoModules() {
if (!monacoModulesPromise) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These all follow the same kind of pattern, which is basically a memoize...
It's possible to extract that out. Something like:

async function memoizeAsync(fn) {
  let promise = null;
  return async () => {
    if (!promise) promise = fn();
    return promise;
  }
}

async function loadMonacoModules() {
  return Promise.all([
    // .... Stuff goes here.
  ]);
}

const ensureLoadMonacoModules = memoizeAsync(loadMonacoModules);

Typed, but not tested.

If this approach makes the PR more readable to you, great! If not, that's fine too -- this is just another technique that may help someday.

Comment thread bindings/wasm/examples/editor/editor.js Outdated
const isLocalhost = window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1';
const skipServiceWorker =
disableServiceWorker || import.meta.env.DEV || isLocalhost;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I understand using the term skipServiceWorker as it encompasses more than just disableServiceWorker. But I think I'd prefer to call out the cases individually.

const disableServiceWorker = params.has('no-sw');
const isLocalhost = window.location.hostname === 'localhost' ||
      window.location.hostname === '127.0.0.1';
const isDevEnv = import.meta.env.DEV;

This makes the check more explicit:

// Disable the service worker if asked, in development or
// on localhost (in which case you can work offline anyhow).
if (disableServiceWorker || isDevEnv || isLocalhost) {

@elalish elalish merged commit e7e39f0 into elalish:master Apr 21, 2026
37 checks passed
@israrkhan921

Copy link
Copy Markdown
Contributor Author

@tonious
Thank you Sir, for the notes
I applied all 3 suggestions:

I changed the service worker check to be more clear (disableServiceWorker, isDevEnv, isLocalhost) and used them directly in the if condition.
I also made one small helper for the repeated async loading pattern (memoizeAsync) and used it for Monaco and esbuild loading.
I kept the same behavior for retry if loading fails.

Thanks again, your suggestion helped make this part cleaner.

@israrkhan921

Copy link
Copy Markdown
Contributor Author

@elalish @tonious
Thanks for merging and for your feedback!

@israrkhan921

Copy link
Copy Markdown
Contributor Author

@elalish
I am going to start work and make plan, according to the 12-week schedule that i have created for GSoC 2026, starting from where I previously paused, in order to regain a full understanding of the project and create a structured plan for the next steps.

@elalish elalish mentioned this pull request May 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants