Skip to content

v5: move virtual page generation for PageTypes to async #2575

Description

@Spelkington

Is your feature request related to a problem? Please describe.

I'm in the process of my v5 migration and ran into an API surface snag while building a page-type plugin that renders .mdx files.

Core only parses .md files (build.ts#L84), so, like the canvas and bases page types, the plugin creates its pages in generate. For each .mdx file it:

  1. Runs the .mdx body through the site's own transformer chain
  2. Bundles the widgets the page imports with esbuild.
  3. Renders each widget to HTML at build time as an island that a small runtime hydrates in the browser.

The snag: steps 1 and 2 are both async:

  • Four of the default transformers are async (CreatedModifiedDate, SyntaxHighlighting, ObsidianFlavoredMarkdown, Description), and unified().runSync() throws on them.
  • esbuild plugins only work with its async API.

However, the emit and partial emit generate are both sync, so a generate that returns a promise crashes the build with virtualPages is not iterable.

Describe the solution you'd like

My copy of the Quartz source has a working solution that the live site uses: widen the return type to allow a promise, and await it at both call sites:

 export type PageGenerator = (args: {
   ...
-}) => VirtualPage[]
+}) => VirtualPage[] | Promise<VirtualPage[]>
   // PageTypePluginEntry
-  generate?: (...args: never[]) => VirtualPage[]
+  generate?: (...args: never[]) => VirtualPage[] | Promise<VirtualPage[]>

plugins/pageTypes/dispatcher.ts

-        const virtualPages = pt.generate({ content, cfg, ctx })
+        const virtualPages = await pt.generate({ content, cfg, ctx })

This looks like a capability that was lost from v4 to v5. In v4, the plugins that created pages (tag and folder pages) were emitters, and emitters could be async (types.ts#L51, tagPage.tsx#L124). I couldn't gauge whether the move to sync was an intentional design choice or a migration oversight, and didn't want to push a PR without understanding the call.

If this gets resolved, I should be able to publish the MDX plugin as a community plugin.

Describe alternatives you've considered

  • Other hooks: none of them gets the work done before the dispatcher needs the pages.
    • Transformers only see .md files, and they run in worker threads.
    • Filters are synchronous.
    • Other emitters run after the dispatcher (emit.ts#L67-L74). That's too late, because virtual pages have to exist before rendering so they're visible to allFiles and transclusion.
  • Naming files .mdx.md so core parses them, and re-parsing flagged files with MDX syntax inside a transformer. It works around the limit, but it's a strange convention to force on authors.
  • Letting page types register file extensions with the parse phase, so non-.md formats go through the worker pipeline. This is arguably the more principled design and I'd welcome it. The change above is small and doesn't rule it out.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions