- Status: PROPOSED
- Date: 2026-05-16
- Supersedes: ADR-0025 §2, §4 (deferred items)
ADR-0025 defined the Renderer Protocol and was partially delivered in v0.15:
- Delivered:
RendererProtocolinterface, named adapters, module split, type definitions (RenderOutput,RenderError,HydrationHint,RenderInput,RenderHooks),customElementRegistrysimplification. - Deferred:
renderDSD()return type change,dsd-report.jsonwriting,RenderHooksintegration.
These were deferred for good reason: changing renderDSD() return type is a
breaking change that should happen once, together with RenderHooks integration,
rather than as two separate breaking changes.
Now is the time to complete the pipeline.
Current:
export async function renderDSD(...): Promise<string>New:
export async function renderDSD(...): Promise<RenderOutput>All callers must destructure:
const { html, errors, metrics, hydrationHints } = await renderDSD(...)No backward-compatible wrapper. Pre-1.0, breaks are acceptable.
Affected callers (must update):
adapter-vite/src/cli/ssg-render.ts— useout.htmlfor page outputadapter-vite/src/entry-renderer.ts— generated SSR entry codecore/src/render-nested.ts— propagate errors + hints upcore/__tests__/render-dsd.test.ts— update assertionsadapter-lit/src/ssr.ts— Lit SSR rendering pipeline
Add optional hooks?: RenderHooks parameter to renderDSD():
export async function renderDSD(
tagName: string,
componentClass: CustomElementConstructor,
props: Record<string, unknown> = {},
sourceInfo?: { route?: string; source?: string },
dsdOptions?: DsdOptions,
collector?: DsdRenderCollector,
nestingDepth = 0,
hooks?: RenderHooks, // NEW
): Promise<RenderOutput>;Pipeline calls:
hooks.beforeRender(input)— before instantiationhooks.afterRender(output)— after serializationhooks.onError(error)— on anyRenderError
After all pages are rendered, ssg-render.ts aggregates metrics from each
RenderOutput and writes a single dsd-report.json to the output directory.
interface DsdBuildReport {
version: 1;
timestamp: string;
totalPages: number;
totalErrors: number;
errors: RenderError[];
metrics: DsdReport;
}This enables CI gates: if (report.totalErrors > 0) process.exit(1).
Current limitation: package islands (e.g., @lessjs/ui components) are not
registered in SSR's customElements registry, so renderDSD() cannot
instantiate them.
With RenderHooks, adapter-vite can provide a beforeRender hook that
lazy-imports and registers package island components on demand:
const hooks: RenderHooks = {
beforeRender(input) {
if (!customElements.get(input.tagName)) {
// Lazy import + register package island
}
},
};This avoids hardcoding package imports in the SSR entry and solves the JSR specifier resolution issue in Vite's server module runner.
Add a deno task check:versions script that verifies:
- All
packages/*/deno.jsonhave the same version deno.lockis up to date- Cross-package
@lessjs/*@^X.Y.Zreferences match the current version
Run this in CI and as a pre-publish gate.
Set up @playwright/test in the monorepo for browser-level validation:
packages/adapter-vite/__tests__/e2e/— E2E test directory- First test: scaffold → build → serve → verify DSD output in real browser
- No legacy DOM diff/test presets — Playwright is the only browser test framework
renderDSD()output is fully structured — errors, metrics, hints are machine-readable and can drive CI gates, tooling, and hydration optimization.RenderHooksenable adapter-level intervention without modifying core pipeline.dsd-report.jsongives SSG builds visibility into rendering performance.- Package islands work in SSR via hooks, not hardcoded imports.
- Pre-publish checks prevent version drift.
- Playwright validates real browser behavior, not just unit tests.
- Breaking change to
renderDSD()return type (second and final — combined with hooks, no further signature changes expected). - Playwright adds CI time and a new dependency.
dsd-report.jsonformat is v1 — may need to evolve.
RenderHooks.onErrorreplaces ad-hoc error handling insiderenderDSD().- The
collector?: DsdRenderCollectorparameter may be removed in a future version sinceRenderOutput.metricssubsumes it.
-
renderDSD()returnsPromise<RenderOutput>and all callers updated -
RenderHooks.beforeRender/afterRender/onErrorfire correctly -
dsd-report.jsonis written after SSG build - Package island renders in SSR via
beforeRenderhook -
deno task check:versionspasses - At least 1 Playwright E2E test passing
- All existing unit tests pass
-
deno task typecheck && deno lint && deno fmt --checkall pass