renderer: tree-shake unused rendu context helpers in production templates
Summary
The production renderer template (src/build/virtual/renderer-template.ts) compiles the HTML template when Nitro builds, then emits import { renderToResponse } from 'rendu'. renderToResponse always creates the full render context, so the output always includes rendu's cookie utilities (cookie-es parse/serialize plus the lazy $COOKIES proxy), even when the template never uses $COOKIES or setCookie.
Proposed change
rendu is adding a build-time codegen API, compileTemplateToModule(). It generates an ES module that imports and passes in only the render context variables the template actually uses. There is also a lower-level renderContextToResponse() with individually importable createRenderResponse / createRenderURL / createCookies / createSetCookie / createRedirect helpers.
This API is not released yet. The Nitro change is blocked until a rendu release includes it.
Bundling generated modules with rolldown against rendu's dist, with srvx left out:
| Case | Size | Cookie code |
|---|---|---|
Template using only {{ title }} and $URL |
~3.0 KB | no |
Template using setCookie and $COOKIES |
~9.2 KB | yes |
Current renderToResponse import |
~8.5 KB | always |
The production branch of renderer-template.ts would become roughly:
import { hasTemplateSyntax, compileTemplateToModule } from "rendu";
const isVite = nitro.options.builder === "vite";
const renderModule = compileTemplateToModule(html, {
contextKeys: ["fetch", "serverFetch", ...(isVite ? ["fetchViteEnv"] : [])],
});
return /* js */ `
${renderModule}
import { fetch, serverFetch } from 'nitro/app'
${isVite ? `import { fetchViteEnv } from "nitro/vite/runtime"` : ""}
const context = { fetch, serverFetch${isVite ? ", fetchViteEnv" : ""} }
export const rendererTemplate = (request) => render(request, context)
`;The dev handler (renderer-template.dev.ts) can keep using compileTemplate + renderToResponse, since bundle size doesn't matter there.
Related bug (likely)
Today the production template is compiled with contextKeys: [...RENDER_CONTEXT_KEYS], but the context also contains fetch, serverFetch and (with Vite) fetchViteEnv. In contextKeys mode a compiled template can only see the listed names. So serverFetch / fetchViteEnv in a production template probably throw a ReferenceError, and fetch falls back to globalThis.fetch instead of Nitro's fetch. This is reasoned from the code and not reproduced in a running Nitro app. Dev works because it uses with() mode. Passing these names as contextKeys (as in the snippet above) fixes it.
Tasks
- Release rendu with
compileTemplateToModule - Switch the production renderer template to
compileTemplateToModule - Add a test for
serverFetch/fetchaccess in production renderer templates
Source: nitrojs/nitro