Repository navigation
fix(editor): load the email code block renderer lazily - #3843
Conversation
The code block's email renderer read themes off `import * as` react-email by name, which keeps every react-email export alive, and imported CodeBlock eagerly, which carries all 371 Prism grammars. Both ended up in every bundle that mounts the editor. The renderer now lives in its own module, loaded with React.lazy when an email with a code block is rendered; render() already waits for it inside its Suspense boundary.
🦋 Changeset detectedLatest commit: c13f8a8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
klotty
left a comment
There was a problem hiding this comment.
No issues found across 4 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Auto-approved: Defers the email code block renderer into a lazy chunk via an explicit react-email theme map, so editor bundles stop eagerly shipping all Prism grammars and react-email components; rendered output is unchanged and a test guards theme-map drift.
View guided diff | Re-trigger cubic
dielduarte
left a comment
There was a problem hiding this comment.
LGTM! did some local tests and no regressions found
Every bundle that mounts the editor ships all 371 Prism grammars and every react-email component, even when no email is ever rendered from it. On resend.com this is a 1.36 MB (427 KB gzip) chunk on the homepage, which only hosts an editor demo.
The cause is the code block's email renderer in
extensions/code-block.tsx:import * as ReactEmailComponents from 'react-email', read by a dynamic key to pick the theme, so bundlers can't tree-shake any react-email export (Tailwind, Markdown, every theme,CodeBlock, etc.).CodeBlockimported eagerly, andCodeBlockimportsprism.tswith every grammar.Changes
extensions/code-block-email.tsx(new): the email renderer, with an explicit map of react-email's 38 code block themes in place of the namespace lookup. Unknown names, including the'default'theme, still fall back to the gray block.extensions/code-block.tsx:renderToReactEmailrenders that module throughReact.lazy.render()already wraps the tree in<Suspense>and awaitsallReady(browser and node builds), so the composed HTML is unchanged: the existing Dracula snapshot passes as is. tsdown emitscode-block-email-*.mjs/.cjsas a separate chunk.code-block.spec.tsx: a test that every theme react-email exports is in the map, so adding a theme to react-email without adding it here fails CI (checked by removing one).@react-email/editorpatch.Impact
Bundling
StarterKitfrom@react-email/editor/extensionsfor the browser with rolldown, react and react-dom external:In a Next.js 16 (Turbopack) production build of resend.com with the same change applied as a patch, the 427 KB gzip chunk leaves the homepage and the editor pages, and the lazy chunk is referenced by no route. esbuild's splitter still hoists
CodeBlockinto a shared chunk because the eager graph reaches react-email'sindex.mjs, so esbuild users see a smaller win.Testing
pnpm test:unitinpackages/editor: 54 files, 522 tests pass (521 before plus the new one).pnpm typecheck,pnpm lint,pnpm build.composeReactEmailin jsdom and in the browser editor).Not in this PR
prism-plugin.tsstill doesimport(\prismjs/components/prism-${language}`), which makes bundlers emit a chunk for every Prism component; resend.com patches it to a fixed list. AloadLanguageoption onCodeBlockPrism` would let apps choose, but it changes the API, so it's left for a separate discussion.Summary by cubic
Loads the email code block renderer lazily so bundles that mount the editor no longer ship all 371 Prism grammars and every react-email component. On resend.com this removes a 427 KB gzip chunk from the homepage.
React.lazyloads it only when an email with a code block is rendered.render()already waits for the lazy chunk inside its Suspense boundary.Written for commit c13f8a8. Summary will update on new commits.