Skip to content

feat: lazy context loading and mops source caching for faster startup#456

Draft
Kamirus wants to merge 2 commits intomasterfrom
feat/lazy-context-loading
Draft

feat: lazy context loading and mops source caching for faster startup#456
Kamirus wants to merge 2 commits intomasterfrom
feat/lazy-context-loading

Conversation

@Kamirus
Copy link
Copy Markdown
Contributor

@Kamirus Kamirus commented Apr 15, 2026

Problem

In workspaces with many Motoko projects (~20 mops.toml files), the extension startup is very slow. The language server eagerly creates a compiler context for every discovered project in parallel — spawning 40+ child processes (npx --no ic-mops -- --version, npx --no ic-mops sources, possibly dfx cache show + moc --version per project), downloading/loading moc.js for each unique version, and resolving all packages before the server is usable. With 20 projects this can take over a minute.

Root cause

notifyPackageConfigChange() runs Promise.all(directories.map(...)) over all discovered project directories, where each iteration spawns multiple npx child processes, downloads moc.js from GitHub releases, require()s a multi-MB JS compiler module, and runs mops sources. All of this runs eagerly at server initialization before the user opens any file.

Fix

Lazy context creation: The glob scan for mops.toml/vessel.dhall/dfx.json still runs at startup (it's cheap), but the expensive per-project work is deferred until a file in that project is actually opened. ensureContextLoaded(uri) is called in all LSP request handlers (onDidOpen, onCompletion, onHover, onDefinition, onCodeAction, onDocumentSymbol, onSignatureHelp, onReferences, onPrepareRename, onRenameRequest, and checkImmediate). Loading promises are deduplicated so concurrent requests for the same project share a single load.

Mops source caching: The output of npx --no ic-mops sources is cached to .mops/.sources-cache.json, keyed by SHA-256 hash of mops.toml + mops.lock. On cache hit, the npx invocations are skipped entirely.

Caveats

  • notifyWorkspace() still runs at startup and writes all workspace files to the default context's virtual FS. When a project context is later lazily loaded, files are re-read from disk to populate the new context. This duplication is acceptable since the expensive operations (child process spawns, moc.js loading) are what dominated startup time.
  • On transient load failures (e.g. network error downloading moc.js), the pending directory is removed and the project falls back to the default context. The user would need to reopen the file or restart to retry.

Test plan

  • All existing tests pass (pre-existing enhancedMigration failure only)
  • requestMocJs test updated to trigger lazy loading before asserting context state
  • extraFlags test passes — default context gets moc flags applied at discovery time
  • Manual: open a workspace with multiple mops.toml projects, verify only the opened project's context loads
  • Manual: verify .mops/.sources-cache.json is written and reused on subsequent startups
  • Manual: modify mops.toml or mops.lock and verify cache is invalidated and packages reload

Defer per-project moc.js loading and package resolution until a file
in that project is actually opened, instead of eagerly loading all
projects at startup. Cache mops source resolution results to disk
(keyed by hash of mops.toml + mops.lock) to avoid repeated npx
invocations across sessions.

For workspaces with many projects (~20 mops.toml files), this reduces
startup from spawning 40+ child processes and loading 20 compiler
instances to a fast glob scan with near-instant per-project loading
on first file open.

Made-with: Cursor
@Kamirus Kamirus requested a review from a team as a code owner April 15, 2026 15:02
@Kamirus Kamirus marked this pull request as draft April 20, 2026 09:03
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.

1 participant