You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
setup/preparser.ts shipped by an addon (slidev-addon-*) is not applied to the initial render, regardless of how the addon is referenced (neither via addons: in slides.md frontmatter, nor via package.json.slidev.addons). The addon’s pre-parser only takes effect after an HMR reload triggered by editing a project file — which the user reports as "works sometimes, breaks after a restart".
Close relative of #926 ("Preparser is not applied when addon is imported via package.json"). #926 was closed by stale[bot] in May 2023 without a fix. PR #2558 closed the gap for themes (April 2026) but did not extend it to addons. This issue tracks the remaining path.
The bug blocks any official-conforming preparser extension — e.g. the audience-filter feature requested in #2636.
Reproduction
I built a minimal standalone addon: slidev-addon-audience-filter. Its published setup/preparser.ts exports a definePreparserSetup callback that filters slides via showFor/hideFor frontmatter against an AUDIENCE env var or the deck's audience: headmatter field.
A deck of 62 slides, with audience: live set in slides.md and ~10 slides tagged hideFor: live / showFor: live, linked to the addon exclusively via:
Reproduced by calling resolveOptions({entry:'slides.md'},'dev') from @slidev/cli and reading options.data.slides.length — this is exactly what both slidev dev and slidev build render from.
AUDIENCE env
audience: headmatter
Project-side preparser
options.data.slides
bypass
live
none (addon only)
62 (unfiltered)
live
live
none (addon only)
62 (unfiltered)
unset
live
none (addon only)
62 (unfiltered)
live
live
stub that imports the addon
53 (filtered)
After editing any project file mid-session the HMR chain re-runs the loader with the full roots (which now includes the addon root), so the filter starts to work — producing the same intermittent "works only after a restart or edit" symptoms users hit.
Root cause (in @slidev/cli)
@slidev/cli/serve-bnzg299W.mjs, resolveOptions():
// L984-987 — initial load with ONLY the project rootconstloaded=awaitparser.load({userRoot: rootsInfo.userRoot,roots: [rootsInfo.userRoot]},entry,void0,mode,)// ... resolution of themeRaw, themeMeta, config from loaded.headmatter// L994-999 — addons resolved AFTER the initial load already happenedconstaddonRoots=awaitresolveAddons(config.addons)constroots=uniq([...themeRoots, ...addonRoots,rootsInfo.userRoot])
Subsequent consumers render from options.data.slides (the initial load result), so loadSetups(roots, 'preparser.ts', ...) is only ever called with full roots during HMR's late loadData() (@slidev/cli/serve-bnzg299W.mjs ~L1552). Addon preparsers miss the moment that matters most: the initial render and the static build output.
PR #2558 fixed the same defect for theme preparsers but left the addon path untouched, despite sharing the same iteration in loadSetups.
Expected behavior
setup/preparser.ts shipped by an addon should be applied to the initial render and to the build output, in addition to HMR reloads. This is the contract documented for addons, and required for any pre-parser extension to be useful as a publishable package (otherwise every consumer project needs its own thin project-level stub, defeating the addon model).
Suggested fix
Mirror what PR #2558 did for themes — re-resolve roots (including addon roots) before producing the data that gets fed to the renderers and to the build, OR perform a second parser.load once full roots are known. Sketch:
asyncfunctionresolveOptions(entryOptions,mode){constentry=awaitresolveEntry(entryOptions.entry)constrootsInfo=awaitgetRoots(entry)constopts={userRoot: rootsInfo.userRoot}// Initial parse for headmatter/theme/config discovery (roots = project only)constloaded=awaitparser.load({ ...opts,roots: [rootsInfo.userRoot]},entry,void0,mode)letthemeRaw=entryOptions.theme||loaded.headmatter.themethemeRaw=themeRaw===null ? 'none' : themeRaw||'default'const[theme,themeRoot]=awaitresolveTheme(themeRaw,entry)constthemeRoots=themeRoot ? [themeRoot] : []constthemeMeta=themeRoot ? awaitgetThemeMeta(theme,themeRoot) : void0constconfig=parser.resolveConfig(loaded.headmatter,themeMeta,entryOptions.entry)constaddonRoots=awaitresolveAddons(config.addons)constroots=uniq([...themeRoots, ...addonRoots,rootsInfo.userRoot])// Re-load with full roots so addon/theme preparsers run on the initial renderconstreloaded=awaitparser.load({ ...opts, roots },entry,void0,mode)return{
...rootsInfo, ...entryOptions,data: { ...reloaded, config, themeMeta },
mode, entry, themeRaw, theme, themeRoots, addonRoots, roots,utils: awaitcreateDataUtils({ ... /* see current impl */})}}
The re-load is cheap: the preparser is the only thing that depends on roots, and @slidev/parser re-reads file content only if it changed.
Users currently relying on preparsers via addons must either copy the preparser into a project-side setup/preparser.ts (3–6 line stub) or rely on HMR after a file edit — a workaround that does not help when building/exporting the deck.
The bug is silent: no warning is printed when an addon's preparser contributes no extensions to the initial parse, so consumers do not realize the addon is a no-op on cold start.
Summary
setup/preparser.tsshipped by an addon (slidev-addon-*) is not applied to the initial render, regardless of how the addon is referenced (neither viaaddons:inslides.mdfrontmatter, nor viapackage.json.slidev.addons). The addon’s pre-parser only takes effect after an HMR reload triggered by editing a project file — which the user reports as "works sometimes, breaks after a restart".Close relative of #926 ("Preparser is not applied when addon is imported via package.json"). #926 was closed by
stale[bot]in May 2023 without a fix. PR #2558 closed the gap for themes (April 2026) but did not extend it to addons. This issue tracks the remaining path.The bug blocks any official-conforming preparser extension — e.g. the audience-filter feature requested in #2636.
Reproduction
I built a minimal standalone addon:
slidev-addon-audience-filter. Its publishedsetup/preparser.tsexports adefinePreparserSetupcallback that filters slides viashowFor/hideForfrontmatter against anAUDIENCEenv var or the deck'saudience:headmatter field.A deck of 62 slides, with
audience: liveset inslides.mdand ~10 slides taggedhideFor: live/showFor: live, linked to the addon exclusively via:slides.mdfrontmatter:addons: [slidev-addon-audience-filter]package.jsonpackage localslidev.addonspackage.jsondependencies: { "slidev-addon-audience-filter": "file:../slidev-addon-audience-filter" }With no project-side
setup/preparser.ts(i.e. the addon is the only preparser source).Environment:
slidev@52.16.0,@slidev/cli@52.16.0, Slidev route modehash, Node 22.Observed: initial render slide count
Reproduced by calling
resolveOptions({entry:'slides.md'},'dev')from@slidev/cliand readingoptions.data.slides.length— this is exactly what bothslidev devandslidev buildrender from.AUDIENCEenvaudience:headmatteroptions.data.slidesbypassliveliveliveliveliveliveAfter editing any project file mid-session the HMR chain re-runs the loader with the full
roots(which now includes the addon root), so the filter starts to work — producing the same intermittent "works only after a restart or edit" symptoms users hit.Root cause (in
@slidev/cli)@slidev/cli/serve-bnzg299W.mjs,resolveOptions():Subsequent consumers render from
options.data.slides(the initial load result), soloadSetups(roots, 'preparser.ts', ...)is only ever called with full roots during HMR's lateloadData()(@slidev/cli/serve-bnzg299W.mjs~L1552). Addon preparsers miss the moment that matters most: the initial render and the static build output.PR #2558 fixed the same defect for theme preparsers but left the addon path untouched, despite sharing the same iteration in
loadSetups.Expected behavior
setup/preparser.tsshipped by an addon should be applied to the initial render and to the build output, in addition to HMR reloads. This is the contract documented for addons, and required for any pre-parser extension to be useful as a publishable package (otherwise every consumer project needs its own thin project-level stub, defeating the addon model).Suggested fix
Mirror what PR #2558 did for themes — re-resolve
roots(including addon roots) before producing the data that gets fed to the renderers and to the build, OR perform a secondparser.loadonce fullrootsare known. Sketch:The re-load is cheap: the preparser is the only thing that depends on
roots, and@slidev/parserre-reads file content only if it changed.Impact
slidev-addon-*(including the audience-filter proposed in feat: Add built-in audience-based slide filtering via showFor/hideFor frontmatter #2636).setup/preparser.ts(3–6 line stub) or rely on HMR after a file edit — a workaround that does not help when building/exporting the deck.Environment
Related
stale[bot]without a fix.