Repository navigation
[#30] Render templates with Handlebars and release 4.0.0 - #32
Merged
Merged
Conversation
Replace the hand-written renderer with Handlebars behind the 3.x API, add escape modes, strict mode, helpers and partials, make image inlining work with the CLI inlining only on request, add --data and --strict to the CLI, and ship MIGRATION.md with a prompt for converting 3.x templates. Closes #30. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
Why
Closes #30
The hand-written
{{ }}renderer needed three fixes in the 3.0.2 batch, including a privately reported re-expansion bug. We don't want to re-invent the wheel: template renderers have lots of risks and loopholes that we don't want to own or champion. Handlebars covers all our features and supersets our renderer.What
4.0 compiles and renders every template with Handlebars, keeping the 3.x API and
{{slot:name}}syntax, and adds escape modes, strict mode, helpers and partials. Image inlining works for the first time, off by default in the CLI, and the CLI can now render a template with sample data as a local test of the email. A migration guide atMIGRATION.mdlists every difference from 3.x and ends with a prompt for converting templates with an AI assistant.Changes
src/engine.tsowns every contact with Handlebars: an isolated instance per compile, Handlebars' own escaping behindescape: 'html' | 'none'with the 3.x booleans as aliases, Handlebars' strict mode behindstrict: truewith the parent-context lookup turned off because the two crash together,{{slot:name}}rewritten in the AST to the built-inlookuphelper on the rootslots, compile-time validation, and ahandlebarsoption for helpers, partials and other compile options withcompat,noEscape,strictand thelookuphelper reserved.renderTemplateis one call into it;compileEmailTemplatecompiles its three templates once andrendercalls them. ATypeErrorduring a strict render is rethrown asStrict render failed: ...with a hint and the original ascause, andsrc/style.tsdecodes the three entities Handlebars adds so tone names still match. We don't want to re-invent the wheel because template renderers have lots of risks and loopholes that we don't want to own or champion; Handlebars covers all features and supersets our stuff.inlineEmailHtmlruns juice'sinlineExternalbefore the CSS pass when any ofimages,links,scriptsorsvgsis set, eachtrue,falseor a kilobyte cap; an unreachable resource is skipped with juice's warning unlesswebResources.strict: true. The CLI takes--inlineImages, accepts--noInlineImagesas a deprecated no-op with one stderr line, and gains--data <file>and--strictso it renders the finished email with sample data. Inline images improve the performance of emails to clients, since image URLs are opt-in on email clients; the features had to work, so testing confirmed and fixed them. Failed emails are the negative case, so an email with a broken image is preferred by default, and the developer controls it per template: a password reset must go out, a marketing email with broken images should fail. The CLI is meant as a local test of the email, so it has the same features.MIGRATION.mdopens by saying 4.0 renders with Handlebars, gives a verified before-and-after example for every difference, and ends with a prompt to paste into an AI assistant with your templates and sample data; the README gets a callout at the top, the Handlebars-based template syntax, the new options, thejuice.webResourceskinds and the CLI flags;.github/pull_request_template.mdis ported from the developer handbook with its Closes convention;CONTRIBUTING.mdshows the v4 tag; version 4.0.0. We say 4.0 is 100% Handlebars and go that way with a migration guide, written so it also works as an AI prompt. Docs only, so no risk.test/engine.test.tsis new; the 3.0.2 renderer regression tests are all kept, with two assertions changed for behaviour the spec changes on purpose; inert-value, strict, compile-option, example, CLI and image failure-mode tests are added. These are the tests and they cover all the functions we updated and added; adding them is how so many issues were found..freethinking/specs/handlebars-renderer/: the approved requirements, design and task list this branch was built from. Specs are just docs.Testing
npm ci,npm audit --audit-level=high --omit=dev,npm run build,npm test,npm run examples,npm pack --dry-run: all exit 0 on Node 24. Suite: 7 files, 286 tests passing. Node 20 and 22 are left to CI.npm run test:visualpasses 5 of 5 against freshly rendered examples; no snapshot changed.src/(measured once with a temporary provider, not added as a dependency):engine.tsandtemplate.ts100% lines, branches and functions;compile.ts86.5% lines;cli.ts64.2% lines, the gaps being argument-error paths unchanged by this branch; total 86.9% lines.MIGRATION.mdwas run through the 3.0.2 renderer and the 4.0 code.Risk and rollout
Breaking change, released as 4.0.0. It could break existing clients depending on how they used the previous library;
MIGRATION.mdlists every difference. Visible changes for 3.x users: Handlebars' entity set (',=and backtick escaped), bare blocks over0and""render, malformed templates throw at compile time, standalone-tag whitespace, object values render[object Object], a row'snullfield falls through to the enclosing context, the CLI no longer inlines images by default and fails on a malformed template. No rollout steps beyond the major version; roll back by pinning to 3.0.2.Notes for reviewers
src/engine.ts; the rest is straightforward and reduces complexity.webResources.strict: true.MIGRATION.mdedits and the AI prompt at its end, and the README.Strict render failedwrapper; both are documented and tested.🤖 Generated with Claude Code