Repository navigation
🍕 Node 26 and Vite 8, and rewrite on native code: 7 dependencies, node:test, 4x faster to load - #98
Merged
Merged
Conversation
…ars and striptags
- .nvmrc 26.12.1; CI reads it. Travis (Node 6) is replaced by ci.yml (lint and tests)
and release.yml (npm publish with provenance, after the same checks);
scripts/release.sh is the shared release script.
- handlebars ^4.7.10 (critical advisories fixed) and striptags ^3.2.0 (the
non-string input advisory). striptags 2.2.1 and 3.2.0 give the same output on
20,000 generated well-formed HTML strings; they differ only for broken markup
(stray < and >). striptags 3 throws for a value that isn't a string, where 2
returned '' for objects, so wordCount and striptags give '' for those, which
is what {{wordCount}} with no argument (Handlebars passes the options object) did.
- sinon was a dependency, but only formatLocalDate.test.js uses it: now a
devDependency. coveralls and istanbul 0.4 are replaced by c8.
- The v1 lockfile is regenerated. 220 tests pass.
Not done: npm run build (webpack 2 + Uglify 2 + Babel 6 behind dist/browser.js)
can't parse the ES2015 that handlebars 4.7.10 and striptags 3 ship, so a release
needs a new bundler for that file first. Kiln imports dist/browser.js.
…d Uglify 2
dist/browser.js is what Kiln loads. The old toolchain could not parse the
ES2015 that handlebars 4.7.10 and striptags 3 ship, so it blocked a
release. vite.config.mjs does what the webpack setup did: it turns the
two require.context() calls in client.js into maps of static requires,
precompiles the .hbs partial, and bundles path and Buffer for browsers
(webpack 2 did both on its own). The output is the same shape,
module.exports = function (env) { ... }, at 209 KB instead of 489 KB.
Compared with the published 5.0.2 bundle: the same 66 helpers and the
partial; 104,720 helper calls over 38 argument values and combinations
(strings, numbers, dates, objects, null, undefined, NaN) give the same
results, and 60 template renders (block helpers, the dynamic partial)
match. The only difference is striptags and wordCount given an ARRAY:
striptags 2 joined its elements, this returns ''. Nothing in sites
passes one.
scripts/check-browser-bundle.js (npm run check-build, also in CI) checks
the bundle registers what index.js registers and renders the same.
prepack builds it on publish; the old prepublish hook ran on npm ci and
is deprecated. Drops 196 devDependency packages.
…r to load, a third of the install Helpers - lodash, date-fns, glob, outdent, comma-it, randomstring (and the Buffer polyfill it needed in the browser), json-stringify-safe, path-browserify and helper-yaml are gone from the helpers and the bundle. What lodash and date-fns did for us now lives in small modules under lib/ (path get/set, isEmpty, date parse and format, circular-safe stringify) that keep their rules; they were compared with the originals on over a million calls and dates in seven time zones. - Helpers are listed once (lib/helpers.js), by index.js and by the browser bundle, instead of being found with glob at run time and require.context at build time. That drops the Vite plugin that emulated require.context. - Creating an environment no longer touches the file system: partials are compiled the first time one is rendered. (A consumer's tests that mock fs, like amphora-html's, broke on the first version of this.) - The read helper caches files by path and checks them again once a second; the yaml helper uses js-yaml 5 (no code-building tags); clay-log is only set up when something is logged. - Dependencies left: clay-log, handlebars, he, js-yaml, lodash (kebabCase and longestWord, loaded when used), speakingurl, striptags. Fixes found on the way (all are covered by tests) - perWordClasses dropped the space after any word equal to the last word. - striptags with tags to keep, or a replacement, wrote "[object Object]" (the Handlebars options object) into the text. - compare took "constructor", "toString" ... for operators. - addOrdinalSuffix gave a String object, not a string, for what isn't a number. - addCommas leaked four globals and gave a ReferenceError or "0.00" for "" depending on what ran before it. - map with no function gave booleans (lodash's "matches" shorthand); it gives the items. Tooling - Tests run on node:test and node:assert with Node's own coverage (99% of lines) instead of mocha, chai, sinon and c8; ESLint 10 with a flat config. - README.md is generated by a small script from the JSDoc of the helpers instead of documentation 4.0.0-beta15 (same output as before, checked against the 5.0.2 README); CI fails if it is out of date. The pre-commit hook for it is gone. - Node 26.12.1 (engines). npm install: 918 packages to 143 (dev) and 58 to 44 (production); npm audit 98 vulnerabilities (46 critical) to 0. Loading the package takes 15 ms and 116 modules instead of 61 ms and 387; the browser bundle is 160 KB instead of 209 KB.
The Node 26.12.1 floor in engines, js-yaml 5 in the yaml helper and the helper bug fixes make this a major. Plan: publish 6.0.0 under the next dist-tag, widen the clayhandlebars ranges in amphora-html, claycli and clay-kiln to 5 || 6, then move latest to 6.0.0.
…ist-tag The release workflow no longer reads an NPM_TOKEN secret (the repository has none): once the package's npm settings name this repository and release.yml as a trusted publisher, the job gets its right to publish from GitHub's OIDC token, and the provenance statement comes with it. publishConfig.tag makes npm publish use the next dist-tag, so tagging v6.0.0 leaves latest on 5.0.2 until the clayhandlebars ranges in amphora-html, claycli and clay-kiln allow 6. Remove it when latest moves to 6.0.0. A hyphenated version still goes to prerelease. npm logs "with tag latest" for such a publish but sends the next tag (checked against a local registry that recorded the request).
jjpaulino
marked this pull request as ready for review
November 6, 2026 02:16
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
jjpaulino
added a commit
to clay/clay-kiln
that referenced
this pull request
Nov 6, 2026
clayhandlebars 6.0.0 (clay/handlebars#98) rewrites the helpers on native code and needs Node 26.12.1, like this branch. Its browser bundle is self-contained and 148 KB instead of 490 KB, so with the same build: - clay-kiln-edit.js 4,033,209 -> 3,539,275 bytes (gzip 913,895 -> 793,317) - clay-kiln-view.js 1,908,887 -> 1,420,682 bytes (gzip 455,881 -> 334,160) - view-public and the CSS don't change. The bundle starts with "use strict" and strictCommonJS keeps it inside its module wrapper, so helpers still get a primitive `this` in the non-strict IIFE. The lock drops 17 packages only 5.x pulled in (date-fns 1, comma-it, randomstring, helper-yaml, sinon 2, ...) and adds clay-log 1.6.0, js-yaml 5.5.0 and argparse 3 under clayhandlebars. Lint, the Jest suite (974 tests) and the dist smoke test pass. Helper output changes only where 6.0.0 fixed bugs: perWordClasses spacing, striptags with tags to keep, compare with "constructor" and the like as operators, addOrdinalSuffix for non-numbers, addCommas(""), map without a function, and the yaml helper on js-yaml 5. 6.0.0 is on npm under the next dist-tag while latest stays 5.0.2; ^6.0.0 still resolves to it. Sites moves to clayhandlebars 6 together with the Node 26 releases of amphora-html, claycli and kiln.
jjpaulino
added a commit
to clay/clay-kiln
that referenced
this pull request
Nov 6, 2026
clayhandlebars 6.0.0 (clay/handlebars#98) rewrites the helpers on native code and needs Node 26.12.1, like this branch. Its browser bundle is self-contained and 148 KB instead of 490 KB, so with the same build: - clay-kiln-edit.js 4,033,209 -> 3,539,275 bytes (gzip 913,895 -> 793,317) - clay-kiln-view.js 1,908,887 -> 1,420,682 bytes (gzip 455,881 -> 334,160) - view-public and the CSS don't change. The bundle starts with "use strict" and strictCommonJS keeps it inside its module wrapper, so helpers still get a primitive `this` in the non-strict IIFE. The lock drops 17 packages only 5.x pulled in (date-fns 1, comma-it, randomstring, helper-yaml, sinon 2, ...) and adds clay-log 1.6.0, js-yaml 5.5.0 and argparse 3 under clayhandlebars. Lint, the Jest suite (975 passed, 3 skipped) and the dist smoke test pass. Helper output changes only where 6.0.0 fixed bugs: perWordClasses spacing, striptags with tags to keep, compare with "constructor" and the like as operators, addOrdinalSuffix for non-numbers, addCommas(""), map without a function, and the yaml helper on js-yaml 5. 6.0.0 is on npm under the next dist-tag while latest stays 5.0.2; ^6.0.0 still resolves to it. Sites moves to clayhandlebars 6 together with the Node 26 releases of amphora-html, claycli and kiln.
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.
Moves clayhandlebars to Node 26.12.1 and Vite 8, and replaces most of its third-party code with native code. Five commits, in order:
node:test, a third of the install, 4x faster to load.nextdist-tag.The version is 6.0.0 (commit 4):
enginesis now>=26.12.1,yamlmoved to js-yaml 5 and some helpers changed behavior, so this is a semver major. The release plan: publish 6.0.0 under thenextdist-tag, notlatest; widen the clayhandlebars ranges to5 || 6in amphora-html and claycli (peer) and clay-kiln (dependency); then movelatestto 6.0.0. Until thenlateststays 5.0.2, so nothing that pins or ranges on 5 changes.Result
npm auditrequire('clayhandlebars')()node:test, 99% of linesNo helper got slower.
ifAll,ifAnyandifNoneare 2-3x faster,defaulton arrays 4x, and thereadhelper 24x (files are cached by path and checked again once a second;CLAYHANDLEBARS_READ_CACHE=falseturns it off).Dependencies
Rule: native code first; a package stays only where nothing native gives the same output.
lib/date.jsis a port of the date-fns 1 parser and formatter), glob (the helper list is a manifest, and a test fails if a file is missing from it), outdent, comma-it, randomstring and its Buffer polyfill (crypto.getRandomValues), json-stringify-safe, helper-yaml, path-browserify, buffer; lodash from 23 helpers (lib/path.js,lib/is-empty.js,lib/collection.jskeep lodash's rules where templates depend on them).requireESM), speakingurl (11 to 14), js-yaml (3 to 5, safe by default), lodash forkebabCaseandlongestWordonly.readandyamlreach logging and error tracking through it; set up on first log).node:test,node:assertand Node's own coverage.documentation(a 600-package beta tree) becomesdocs/generate-readme.js, which reproduces the 5.0.2 README from the 5.0.2 sources; CI runsnpm run check-readme. ESLint 4 becomes ESLint 10 with a flat config.package.jsonfilesnow lists what ships, so local files never end up in the tarball.Behavior changes (bug fixes, each with a test)
perWordClassesdropped the space after every word equal to the last word ("a a").striptagswith tags to keep, or a replacement, wrote[object Object](Handlebars' options object) into the text.comparetookconstructor,toStringandhasOwnPropertyfor operators.addOrdinalSuffixreturned aStringobject for a non-number (truthy even when empty).addCommas("")threw or returned "0.00" depending on what had run before it (comma-it's implicit globals).mapwith no function returned booleans instead of the items.yamlnow uses js-yaml 5, so the old!!js/functiontag is gone and a parse failure is logged through clay-log instead ofconsole.log. Nothing else about the helpers' output changed.Compatibility notes for consumers
node:fsand mocksfswholesale, so library code uses plainfsandpath, the factory no longer reads the file system, and partials compile on first render.(env?),.wrapPartial(name, code)and thebrowserfield (dist/browser.js) are unchanged.How it was checked
wrapPartialgives the same text for all 634.npm ci,npm test,npm run build,check-build,check-readmeandnpm auditpass from a clean checkout of the branch.Not in this PR
v6.0.0tag is pushed after the merge, and needs the trusted publisher to be set up on npmjs.com for this repository andrelease.yml(commit 5 removes theNPM_TOKENsecret the workflow used to read; the repository has none).publishConfig.tagin commit 5 keepslateston 5.0.2 and goes away whenlatestmoves.kebabCase,longestWord), the DOM entity decoder in place ofhein the browser, orconsolein place of clay-log. Each trades output parity or error reporting for size, so they are left for later.