Skip to content

fix(monaco): whitelist the monaco-write path the dev server actually resolves - #2740

Merged
antfu merged 1 commit into
slidevjs:mainfrom
NgoQuocViet2001:fix-monaco-write-path
Sep 15, 2026
Merged

antfu merged 1 commit into
slidevjs:mainfrom
NgoQuocViet2001:fix-monaco-write-path

Conversation

@NgoQuocViet2001

Copy link
Copy Markdown
Contributor

Problem

resolveSnippetImport returns two different things:

const src = slash(
  filepath.startsWith('@/')
    ? path.resolve(userRoot, filepath.slice(2))
    : path.resolve(dir, filepath),      // dir = dirname(slide.source.filepath)
)
...
return { content, filepath, lang, meta, src }

filepath is the specifier exactly as typed in the Markdown; src is where it actually lives. Reading uses src. The {monaco-write} branch used filepath:

monacoWriterWhitelist.add(filepath)
...
const safeFilepath = JSON.stringify(filepath).slice(1, -1)
token.content = `<Monaco writable="${safeFilepath}" ... />`

and createMonacoWriterPlugin resolves whatever it receives against userRoot:

const filepath = path.resolve(userRoot, file)

So the snippet is read through one resolution scheme and written back through another.

snippet read from written to
<<< @/snippets/x.ts {monaco-write} <userRoot>/snippets/x.ts <userRoot>/@/snippets/x.ts
<<< ./x.ts {monaco-write} in pages/deck.md <userRoot>/pages/x.ts <userRoot>/x.ts

The @/ form — the one the docs recommend — writes into a directory literally named @, which does not exist, so fs.writeFile rejects with ENOENT inside the websocket handler and the save is lost with no feedback in the editor. The relative form silently writes to a different file than the one on screen.

Fix

Hand the writer the userRoot-relative form of the resolved path, which is the scheme it resolves in:

const writePath = slash(path.relative(userRoot, src))
monacoWriterWhitelist.add(writePath)

monacoWrite.ts and Monaco.vue are untouched. The plugin's rel.startsWith('..') guard keeps working and becomes meaningful — it now compares a path that really was resolved against userRoot.

Test plan

  • Added a case to the existing packages/slidev/node/syntax/snippet.test.ts, reusing its test/fixtures/ root and snippets/snippet.ts fixture. It asserts the whitelisted value, the writable attribute, and that path.resolve(userRoot, whitelisted) lands back on the file that was read — the writer's own expression.
  • Ran: vitest run packages/slidev/node/syntax/snippet.test.ts → 6 passed.
  • Checked: reverting only snippet.ts fails the new test with [ '@/snippets/snippet.ts' ] instead of [ 'snippets/snippet.ts' ].
  • Ran: eslint on both touched files → clean.

…resolves

resolveSnippetImport returns both the raw specifier as typed in the Markdown
and the resolved absolute path. Reading uses the resolved one; the
{monaco-write} branch whitelisted and embedded the raw one.

createMonacoWriterPlugin resolves whatever it receives against userRoot, so
an @/ alias became <userRoot>/@/... and a slide-relative path was resolved
from the wrong directory. Saving from the editor either failed with ENOENT
or wrote to a different file than the one displayed.

Hand it the userRoot-relative form of the resolved path instead, which keeps
the writer's outside-the-root guard meaningful.
@netlify

netlify Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for slidev ready!

Name Link
🔨 Latest commit 32c009a
🔍 Latest deploy log https://app.netlify.com/projects/slidev/deploys/6aa568152030c700087ef97e
😎 Deploy Preview https://deploy-preview-2740--slidev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@pkg-pr-new

pkg-pr-new Bot commented Sep 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

@slidev/client

npm i https://pkg.pr.new/@slidev/client@2740

create-slidev

npm i https://pkg.pr.new/create-slidev@2740

create-slidev-theme

npm i https://pkg.pr.new/create-slidev-theme@2740

@slidev/parser

npm i https://pkg.pr.new/@slidev/parser@2740

@slidev/cli

npm i https://pkg.pr.new/@slidev/cli@2740

@slidev/types

npm i https://pkg.pr.new/@slidev/types@2740

commit: 32c009a

@antfu
antfu merged commit ba821a4 into slidevjs:main Sep 15, 2026
21 checks passed
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.

2 participants