nitrojs / nitrojs/nitro

renderer: tree-shake unused rendu context helpers in production templates

Open
#4,622 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
11.2k
Forks
899
Avg merge
2d 24m
Merged PRs (30d)
40

Description

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 / fetch access in production renderer templates

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with src/build/virtual/renderer-template.ts and compare it with renderer-template.dev.ts, then confirm the required rendu release includes compileTemplateToModule(). Check the production renderer path for access to fetch, serverFetch, and fetchViteEnv. Done means production templates import only the context they use and a test covers serverFetch and fetch access.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend, build-system
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.