Skip to content

v0.5.0

Latest

Choose a tag to compare

@github-actions github-actions released this 21 Aug 21:59

The first release since 2021. remark-hbs moves to ESM and unified 11, and the whole toolchain is modernized.

The plugin's rendered output is unchanged. This release is a platform and packaging update, not a behaviour change — see What did not change for how that was verified.

💥 Breaking changes

ESM-only

The package is now "type": "module" with a default export and an exports map. There is no CommonJS build.

// Before
const hbs = require('remark-hbs');

// After
import hbs from 'remark-hbs';

CommonJS consumers are not shut out — require() of an ES module works on the Node versions this package supports, and returns a namespace with __esModule set, so a compiled-TypeScript require('remark-hbs').default keeps working. If you are on an older Node, use await import('remark-hbs').

Because an exports map is now declared, deep imports into the package (remark-hbs/something) are no longer resolvable. Only the package root is public.

Node 20.19+ required

engines moves from >= 10.* to ^20.19.0 || >=22.12.0. Those specific floors are the Node versions that support require() of an ES module, which is what keeps CommonJS consumers working.

unified 11 is now a peer dependency

"peerDependencies": { "unified": "^11.0.0" }

Install unified yourself. unified 9 and 10 are no longer supported — the plugin now uses unist-util-visit 5 and the unified 11 type surface.

Types rewritten

index.d.ts used export = with a namespace, which cannot describe an ESM default export. It is now a default export with RemarkHbsOptions as a named export:

// Before
import remarkHbs from 'remark-hbs';           // via esModuleInterop
// (RemarkHbsOptions lived on a namespace)

// After
import remarkHbs, { type RemarkHbsOptions } from 'remark-hbs';

The option names are unchanged — escapeCurliesCode and escapeCurliesInlineCode, both defaulting to true.

unist-builder dropped

It was only used to build html and text nodes, which are now plain object literals. The dependency is gone; unist-util-visit (^5.1.0) is the only runtime dependency.

🐛 Fixes

  • Incompatible with Unified v10 (#27) — the old types imported Settings from unified, which unified 10 had removed, breaking type-checking with TS2305. unified 11 exports Settings again and the types now resolve cleanly.
  • Removed a latent ReferenceError: parseHBS contained a bare recursion = 0 assignment to an undeclared global, which throws under ESM's implicit strict mode.

What did not change

The point of this release was to modernize the platform without moving a single byte of rendered output, and that was verified rather than assumed:

  • A harness was pinned to the old stack (unified 9 / remark-html 13.0.1 / unist-util-visit 2 / unist-builder 2) and first confirmed to reproduce the previously committed snapshots exactly, establishing it as a trustworthy baseline.
  • Old and new were then diffed across every test input and both fixtures, under default options and with escapeCurliesCode: false / escapeCurliesInlineCode: false: byte-identical throughout.
  • Snapshots were migrated to vitest by decoding and comparing them key-by-key against that baseline, not by re-recording them.
  • Verified against a real consumer (Docfy): clean compile, unchanged test results, successful classic-Ember and Vite builds, and all 40 generated .gjs template files byte-identical by checksum.

⚠️ Note for remark-html users

Unrelated to this release, but easy to trip over while upgrading: remark-html began sanitizing by default in 13.0.2, which strips the raw component invocations this plugin emits. If your components vanish from the output, pass sanitize: false:

unified().use(markdown).use(hbs).use(html, { sanitize: false });

Known limitations

Unchanged from previous releases, and confirmed to behave identically:

  • An inline component invocation followed by more text drops the trailing text — Text with <MyComponent /> and {{foo}} loses and {{foo}} (see josemarluedke/docfy#150).
  • Closing an element tag on its own line is parsed as a blockquote by CommonMark (#17).

🛠️ Internal

  • jest → vitest, which runs ESM natively rather than needing NODE_OPTIONS='--experimental-vm-modules'
  • yarn → pnpm, with a packageManager field
  • CI rebuilt: actions/checkout@v5 / actions/setup-node@v5 and a Node 20 / 22 / 24 matrix, replacing a global yarn install on Node 10
  • Added eslint (flat config) and a tsc --noEmit type check, both behind a single pnpm run lint; prettier 2 → 3
  • Dev tooling to current majors: eslint 10, TypeScript 7, vitest 4, globals 17
  • Added files and exports; removed the stale .npmignore

🤝 Credits

The ESM conversion began in #28 by @NullVoxPopuli back in December 2021 — that PR's ESM work, its .prettierrc.cjs rename, and its fix for the undeclared-global bug all carried into #35.


💥 Breaking Change

📦 Dependencies


Contributors

@NullVoxPopuli, @dependabot, @dependabot[bot] and @josemarluedke


For full changes, see the comparison between v0.4.1 and v0.5.0