feat(core): add headless HTML renderer for SSR/SSG - #358
Open
DeerDavids wants to merge 1 commit into
Open
DeerDavids wants to merge 1 commit into
DeerDavids wants to merge 1 commit into
Conversation
Expose the exportHTML() pipeline as createHtmlRenderer() / renderMarkdownToHtml() so hosts can render Markdown to HTML in Node.js (SSR, SSG, CLIs) without a DOM. exportHTML() now delegates to the shared renderer, built lazily and reused, so browser export and server render cannot drift. Also adds SSR contract tests for the react/vue <Editor /> bindings, README / ROADMAP docs and the OpenSpec change add-headless-html-render. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary / 摘要
Expose the
exportHTML()pipeline as a DOM-free API —createHtmlRenderer()/renderMarkdownToHtml()— so hosts can render Nexus documents to HTML in Node.js (SSR, SSG, CLIs), and add SSR contract tests for the React / Vue<Editor />bindings.Motivation / 背景与动机
EditorAPI.exportHTML()is the only way to get HTML out of a Nexus document, and it lives on a mounted editor: it needs a container element, a CodeMirror view and adocumentglobal. Onmain, importing@floatboat/nexus-corein plain Node.js works, butcreateEditor()throwsReferenceError: document is not defined— so there is no supported way to produce HTML on a server.That blocks the use cases the README targets (docs CMS, static-site authoring, LLM writing tools):
<div>and users see nothing until the CodeMirror bundle runs.exportHTML()diverges from what the editor exports as soon as a plugin changes.The pipeline itself (
markdownToHtml()ineditor.ts) was already pure; it was just unreachable without an editor instance.openspec/changes/add-headless-html-render(openspec validate --strictpasses)Changes / 变更内容
packages/core:src/render-html.ts—createHtmlRenderer({ plugins?, transform? })builds the unified pipeline once (same "build once, freeze" pattern ascreateParser()) and returns{ render(markdown) };renderMarkdownToHtml()is the one-shot wrapper. Exported fromindex.tstogether with theHtmlRenderer/HtmlRendererOptionstypes.exportHTML()now delegates to the shared renderer (created lazily on first call, reused afterwards) instead of the privatemarkdownToHtml(). Output is unchanged; theremark-rehype/rehype-stringifyimports move out ofeditor.ts.README.mddocuments the API.packages/react,packages/vue: tests only —<Editor />renders its container<div>underreact-dom/server/vue/server-rendererin a Node (no-DOM) vitest environment. No binding code changes; this locks in the SSR contract the README now promises.README.md/README.zh.md: new "Server-side rendering & SSG" section under API Reference with a Next.js App Router example (crawlable HTML on first paint, editor hydrates on the client).docs/ROADMAP.md/docs/ROADMAP.zh.md: row feat(react,vue): add controlled document mode to Editor #30.openspec/:changes/add-headless-html-render/— proposal, tasks,specs/html-rendering/spec.md.No breaking changes. No new runtime dependencies.
Out of scope (listed in the proposal): server-rendering the live preview / widgets (mermaid, highlight.js, KaTeX), raw-HTML pass-through, and an
ssrHtmlplaceholder prop on<Editor />.Testing / 测试
pnpm testpasses / 全绿 — 899 passed on Windows; the only failures (10 inapps/electron-demo/test/plugin-host-broker.test.ts) areEPERM: operation not permitted, symlinkand fail identically on an untouchedmaincheckout (Windows needs elevated rights to create symlinks). They pass on Linux CI.pnpm build) / 受影响包构建通过 — alsopnpm typecheck,pnpm check:api,pnpm build:electron-demo.packages/core/test/render-html.test.ts(@vitest-environment node, 8 cases): assertsdocument/windoware undefined; basic rendering; empty input; pluginremarkPluginshonoured (GFM table); raw HTML dropped;transformhook; renderer reuse across documents; factory ≡ one-shot helper.packages/core/test/render-html-editor.test.ts(jsdom):renderMarkdownToHtml()output is byte-identical toeditor.exportHTML()for the same document and plugins.packages/react/test/editor-ssr.test.tsx,packages/vue/test/editor-ssr.test.ts(node): server-rendered<Editor />is exactly the container<div>.createHtmlRenderer is not a function) before the implementation.Compliance / 合规自检
AI-assisted notes / AI 使用说明:This PR was developed with substantial AI assistance (Claude Code). The direction — headless rendering for SSR/SSG — was chosen by me; the implementation, tests, docs and OpenSpec files were drafted with Claude Code following the repo's OpenSpec + test-first workflow, then verified locally (
pnpm typecheck/test/build/check:api,openspec validate --strict). I am leaving the checkbox above unticked because the functional code was largely AI-drafted; disclosing that honestly seemed more useful than ticking the box.remark-parse,remark-rehype,rehype-stringify,unifiedare existing core dependencies.dist/,dist-electron/, compiled.jsfrom.ts) / 未提交构建产物.env/ personal vault data committed / 无敏感信息Checklist / 自检清单
live-preview-table.ts→ N/A (not touched)Screenshots / Recordings · 截图或录屏 (UI changes)
N/A — no visible UI change. Headless usage:
🤖 Generated with Claude Code