528 lines
21 KiB
Markdown
528 lines
21 KiB
Markdown
# Handlebars Template Engine Compatibility with Bun Runtime
|
|
|
|
## Table of Contents
|
|
|
|
1. [Executive Summary](#executive-summary)
|
|
2. [Handlebars in the npm Ecosystem](#handlebars-in-the-npm-ecosystem)
|
|
3. [Bun Runtime Compatibility](#bun-runtime-compatibility)
|
|
4. [Performance Benchmarks](#performance-benchmarks)
|
|
5. [Bundle Size Analysis](#bundle-size-analysis)
|
|
6. [Precompilation Support](#precompilation-support)
|
|
7. [Alternative Template Engines](#alternative-template-engines)
|
|
8. [Comparison with Plain Template Literals](#comparison-with-plain-template-literals)
|
|
9. [Existing Codebase Assessment](#existing-codebase-assessment)
|
|
10. [Build Pipeline Considerations](#build-pipeline-considerations)
|
|
11. [Recommendation](#recommendation)
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
Handlebars v4.7.9 works correctly in the Bun runtime with no native module dependencies. However, it adds significant bundle weight (~216 KB bundled, or ~40 KB runtime-only) and its CJS-only module format means `bun build` bundles the entire library rather than tree-shaking unused helpers. For the open-memory plugin, which currently uses plain TypeScript template literals for all output formatting, introducing Handlebars would be a net negative: it adds a dependency, increases bundle size by 8-46%, and provides no capability that cannot be achieved with template literals plus the existing `lines.push()` pattern already in use.
|
|
|
|
If a template engine is needed in the future for user-facing or complex conditional templates, **Mustache** is the best lightweight option (14.8 KB bundled, logicless, ESM-compatible), and **Eta** is the best ergonomic option (16.1 KB bundled, ERB-style syntax) though it has a Bun-specific bug with compiled template invocation.
|
|
|
|
---
|
|
|
|
## Handlebars in the npm Ecosystem
|
|
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| Latest version | **4.7.9** (published 2026-03-26) |
|
|
| License | MIT |
|
|
| Weekly downloads | ~25M |
|
|
| Repository | <https://github.com/handlebars-lang/handlebars.js> |
|
|
| Dependencies | `neo-async`, `source-map`, `uglify-js` (compiler only), `minimist` (CLI only), `wordwrap` (CLI only) |
|
|
| Native modules | **None** -- pure JavaScript, no `.node` binaries, no `node-gyp` |
|
|
| TypeScript support | `@types/handlebars` v4.1.0; `runtime.d.ts` included in package |
|
|
| ESM support | **No** -- CJS only, no `"module"` or `"exports"` field in `package.json`; Bun's CJS interop makes it work |
|
|
|
|
### Package Structure
|
|
|
|
```
|
|
handlebars/
|
|
├── lib/index.js # Main entry (CJS)
|
|
├── runtime.js # Alias for runtime-only entry
|
|
├── dist/
|
|
│ ├── cjs/
|
|
│ │ ├── handlebars.js # Full CJS bundle (204 KB, compiler + runtime)
|
|
│ │ └── handlebars.runtime.js # Runtime-only CJS (72 KB)
|
|
│ └── handlebars.min.js # Minified full (89 KB)
|
|
│ └── handlebars.runtime.min.js # Minified runtime (29 KB)
|
|
├── bin/ # CLI for precompilation
|
|
└── types/
|
|
└── index.d.ts # Type declarations
|
|
```
|
|
|
|
The `runtime.js` entry point exports only the template execution engine (no compiler), which is the right import for production use with precompiled templates.
|
|
|
|
---
|
|
|
|
## Bun Runtime Compatibility
|
|
|
|
### Test Results
|
|
|
|
| Test | Result |
|
|
|------|--------|
|
|
| `import Handlebars from "handlebars"` | Works (CJS interop) |
|
|
| `Handlebars.compile()` + render | Works correctly |
|
|
| `import HandlebarsRuntime from "handlebars/runtime"` | Works |
|
|
| Precompiled template spec + runtime | Works correctly |
|
|
| `require("handlebars")` | Works (CJS in Bun) |
|
|
| `bun build --target bun` bundling | Works, 44 modules bundled |
|
|
| `Handlebars.registerHelper()` (custom helpers) | Works |
|
|
| `Handlebars.Utils.escapeExpression()` | Works |
|
|
|
|
### No Issues Found
|
|
|
|
Handlebars is pure JavaScript with no native bindings. There are no `.node` files, no `node-gyp` build steps, and no WebAssembly dependencies. All filesystem operations in the compiler/CLI path use standard `fs` module calls that Bun supports. The core template compilation and rendering rely only on string manipulation and `Function()` constructor for generated template functions -- both supported by Bun.
|
|
|
|
### CJS-Only Concern
|
|
|
|
Handlebars does not ship an ESM entry point. In the `package.json`:
|
|
|
|
```json
|
|
{
|
|
"main": "lib/index.js",
|
|
"module": "", // intentionally empty / absent
|
|
"type": "" // CJS by default
|
|
}
|
|
```
|
|
|
|
This means `bun build` cannot tree-shake individual helpers or utilities -- the entire CJS module is bundled as a single chunk. In practice this means you get the full Handlebars library in your bundle even if you only use `compile()` and `escapeExpression()`.
|
|
|
|
---
|
|
|
|
## Performance Benchmarks
|
|
|
|
All benchmarks run in Bun v1.3.11 on Linux x64. 10,000 iterations each.
|
|
|
|
### Simple Template (`Hello {{name}}!`)
|
|
|
|
| Engine | Compile (μs/op) | Render (μs/op) | Combined (μs/op) |
|
|
|--------|------------------|-----------------|-------------------|
|
|
| Template literals | n/a | **0.17** | 0.17 |
|
|
| Mustache | 0.83 | **1.13** | 1.13* |
|
|
| Handlebars | 0.56 | 3.66 | 3.66* |
|
|
| EJS | 34.56 | 56.47* | 56.47* |
|
|
| Eta (renderString) | n/a | -- | 15** |
|
|
|
|
*Mustache and EJS combine parse + render in their `render()` call; separate compilation benchmark provided for reference.
|
|
|
|
**Eta has a bug in Bun with compiled template invocation (see below).
|
|
|
|
### Complex Template (list of 20 items with conditional formatting)
|
|
|
|
| Engine | Compile (μs/op) | Render (μs/op) |
|
|
|--------|------------------|-----------------|
|
|
| Template literals | n/a | **6.25** |
|
|
| Mustache | 0.47 | 18.13 |
|
|
| Handlebars | 0.70 | 18.76 |
|
|
| Eta (renderString) | n/a | 14.64 |
|
|
| EJS | 34.56 | 56.47 |
|
|
|
|
### Key Takeaways
|
|
|
|
1. **Template literals are ~3-30x faster** than any template engine for rendering, and ~3-10x faster even than pre-compiled engine-render paths.
|
|
2. **Handlebars and Mustache render performance** are nearly identical (~18 μs/op for complex templates). Handlebars has slightly slower render due to its richer helper system.
|
|
3. **EJS is by far the slowest** due to its `Function()` constructor approach and `with()` statement for scoping.
|
|
4. **Compilation cost is negligible** for all engines except EJS. Pre-compiling at build time saves ~1 μs at runtime -- not meaningful unless you're compiling hundreds of unique templates per second.
|
|
5. For the open-memory plugin, which renders ~1 template per tool call invocation, even the slowest engine would add under 60 μs per call. Render performance is not a concern; **bundle size is the deciding factor**.
|
|
|
|
---
|
|
|
|
## Bundle Size Analysis
|
|
|
|
### Standalone Engine Bundle Size
|
|
|
|
Bundled with `bun build --target bun --format esm`, minimal test program:
|
|
|
|
| Engine | Bundled Size | Modules | Notes |
|
|
|--------|-------------|---------|-------|
|
|
| **(none - template literals)** | **0 B** | 0 | Zero-dependency |
|
|
| Mustache | 14.8 KB | 2 | Smallest engine |
|
|
| Eta | 16.1 KB | 2 | ESM-native |
|
|
| EJS | 21.5 KB | 3 | Includes `jake` and async utilities |
|
|
| **Handlebars (runtime only)** | **40.4 KB** | 22 | For use with precompiled templates |
|
|
| **Handlebars (full)** | **216.8 KB** | 44 | Includes compiler + all built-in helpers |
|
|
|
|
### Impact on open-memory Plugin
|
|
|
|
The current open-memory plugin bundle is **474 KB** (mostly `@opencode-ai/plugin` + `@opencode-ai/sdk`).
|
|
|
|
| Addition | Size Added | % Increase |
|
|
|----------|-----------|------------|
|
|
| Mustache | +14.8 KB | +3.1% |
|
|
| Eta | +16.1 KB | +3.4% |
|
|
| EJS | +21.5 KB | +4.5% |
|
|
| Handlebars runtime-only | +40.4 KB | +8.5% |
|
|
| Handlebars full | +216.8 KB | **+45.7%** |
|
|
|
|
A 46% bundle size increase for Handlebars-full is unacceptable for a plugin loaded at OpenCode startup. Even the runtime-only variant adds 40 KB for template rendering capability already achievable with template literals.
|
|
|
|
### Handlebars Runtime vs. Full
|
|
|
|
The runtime-only bundle (`handlebars/runtime`) at 40.4 KB includes:
|
|
- Template execution engine
|
|
- `escapeExpression()` for HTML escaping
|
|
- Built-in helpers (`if`, `unless`, `each`, `with`, `log`, `lookup`)
|
|
- SafeString class
|
|
- Data tracking
|
|
|
|
The full bundle at 216.8 KB additionally includes:
|
|
- The AST compiler (parses `{{}}` syntax into template functions)
|
|
- The JavaScript compiler (generates function source from AST)
|
|
- The printer (AST → source text)
|
|
- Source map generation
|
|
|
|
If using precompiled templates, you only need the runtime.
|
|
|
|
---
|
|
|
|
## Precompilation Support
|
|
|
|
Handlebars supports template precompilation, which separates the compile step (build time) from the render step (runtime).
|
|
|
|
### Precompile CLI
|
|
|
|
```bash
|
|
npx handlebars src/templates/ -f dist/templates.js \
|
|
--commonjs handlebars/runtime \
|
|
--known each \
|
|
--known if \
|
|
--known unless
|
|
```
|
|
|
|
This produces a JS module containing precompiled template function specifications that can be instantiated with only the runtime:
|
|
|
|
```typescript
|
|
import HandlebarsRuntime from "handlebars/runtime";
|
|
|
|
// Template spec from precompile (could be imported from a generated file)
|
|
const templateSpec = {"compiler":[8,">=4.3.0"],"main":function(container,depth0,...){...},"useData":true};
|
|
|
|
const template = HandlebarsRuntime.template(templateSpec);
|
|
console.log(template({ name: "World" })); // "Hello World!"
|
|
```
|
|
|
|
### Precompile API
|
|
|
|
```typescript
|
|
import Handlebars from "handlebars";
|
|
|
|
// At build time
|
|
const spec = Handlebars.precompile("Hello {{name}}!");
|
|
// spec is a JSON-safe object string containing the template function source
|
|
|
|
// At runtime (only needs handlebars/runtime, 40 KB)
|
|
import HandlebarsRuntime from "handlebars/runtime";
|
|
const template = HandlebarsRuntime.template(eval("(" + spec + ")"));
|
|
```
|
|
|
|
### Feasibility for open-memory
|
|
|
|
Precompilation is feasible but adds complexity to the build pipeline. Since the open-memory plugin currently has only 4-5 formatting functions (all in `src/history/format.ts` and `src/compaction/prompt.ts`), the overhead of setting up precompilation is unjustified. Precompiled templates would save ~176 KB (full - runtime = 216.8 - 40.4) at the cost of a custom build step, with no meaningful runtime performance gain for templates called once per tool invocation.
|
|
|
|
---
|
|
|
|
## Alternative Template Engines
|
|
|
|
### Mustache (v4.2.0)
|
|
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| License | MIT |
|
|
| Philosophy | Logic-less templates -- no `if`, no `for`, only sections |
|
|
| ESM Support | Yes (conditional exports in `package.json`) |
|
|
| Dependencies | None |
|
|
| Bundle size | **14.8 KB** |
|
|
| Bun compatibility | Works perfectly |
|
|
| TypeScript types | `@types/mustache` |
|
|
|
|
```typescript
|
|
import Mustache from "mustache";
|
|
Mustache.render("Hello {{name}}!", { name: "World" });
|
|
```
|
|
|
|
**Strengths**: Smallest bundle, zero dependencies, works in Bun, well-understood spec, XSS-safe by default.
|
|
|
|
**Weaknesses**: No logic at all -- cannot do conditional formatting without data preprocessing. For example, you cannot render `"No sessions found."` vs. a table based on row count without preparing the data model to include a flag. This is a significant limitation for the open-memory plugin's formatting needs.
|
|
|
|
### Eta (v4.5.1)
|
|
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| License | MIT |
|
|
| Philosophy | Lightweight ERB-style templates, ESM-native |
|
|
| ESM Support | Yes (`"type": "module"`, dual CJS/ESM exports) |
|
|
| Dependencies | None |
|
|
| Bundle size | **16.1 KB** |
|
|
| Bun compatibility | **Partial** -- `renderString()` works, but compiled template invocation fails with `TypeError: undefined is not an object (evaluating 'this.config.escapeFunction')` |
|
|
| TypeScript types | Built-in |
|
|
|
|
```typescript
|
|
import { Eta } from "eta";
|
|
const eta = new Eta();
|
|
eta.renderString("Hello <%= it.name %>!", { name: "World" });
|
|
```
|
|
|
|
**Strengths**: ERB-style syntax (`<%= %>`, `<% %>`) familiar to many developers, ESM-native, very small, configurable delimiters.
|
|
|
|
**Weaknesses**: The compiled template bug in Bun is a blocker for production use. The `compile()` method produces a function that references `this.config` on a context that is `undefined` when invoked in Bun. This appears to be a `this`-binding issue in Bun's ESM module evaluation.
|
|
|
|
**Workaround**: Use `renderString()` only (no separate compile step). This is fine for the plugin's use case but eliminates the precompilation advantage.
|
|
|
|
### EJS (v5.0.2)
|
|
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| License | Apache-2.0 |
|
|
| Philosophy | Embedded JavaScript templates |
|
|
| ESM Support | Yes (dual CJS/ESM) |
|
|
| Dependencies | None (previously had jake, asap; now zero) |
|
|
| Bundle size | **21.5 KB** |
|
|
| Bun compatibility | Works |
|
|
| TypeScript types | `@types/ejs` |
|
|
|
|
```typescript
|
|
import ejs from "ejs";
|
|
ejs.render("Hello <%= name %>!", { name: "World" });
|
|
```
|
|
|
|
**Strengths**: Familiar syntax, async rendering support, includes, layouts.
|
|
|
|
**Weaknesses**: **Slowest engine** in benchmarks (56 μs/op for complex templates). Uses `Function()` constructor which is a security concern if templates contain user input (not relevant for open-memory, but worth noting). No logic-less mode -- templates can execute arbitrary JS.
|
|
|
|
### Plain Template Literals (No Dependency)
|
|
|
|
```typescript
|
|
// Current open-memory pattern
|
|
export const formatSessionList = (rows: Record<string, unknown>[]): string => {
|
|
if (rows.length === 0) return "No sessions found.";
|
|
const lines: string[] = ["# Recent Sessions\n"];
|
|
lines.push("| ID | Title | Updated | Messages |");
|
|
lines.push("|----|-------|---------|----------|");
|
|
for (const row of rows) {
|
|
lines.push(`| ${row.id} | ${row.title} | ${row.updated} | ${row.msgs} |`);
|
|
}
|
|
return lines.join("\n");
|
|
};
|
|
```
|
|
|
|
**Strengths**: Zero bundle cost, fastest rendering, full TypeScript type safety, no dependency to maintain, no security surface.
|
|
|
|
**Weaknesses**: Verbose for complex conditional formatting. Harder to visually parse the output format from code. No built-in HTML escaping (irrelevant for this plugin which outputs plain text/Markdown).
|
|
|
|
---
|
|
|
|
## Comparison with Plain Template Literals
|
|
|
|
The open-memory plugin currently formats all output using TypeScript template literals and the `lines.push()` pattern. Here is an assessment of whether Handlebars would improve each formatting function:
|
|
|
|
### `formatSessionList()` (format.ts)
|
|
|
|
```typescript
|
|
// Current: 23 lines, clear, zero dependencies
|
|
// Handlebars equivalent:
|
|
const template = Handlebars.compile(`
|
|
# Recent Sessions
|
|
{{#if sessions.length}}
|
|
| ID | Title | Updated | Messages |
|
|
|----|-------|---------|----------|
|
|
{{#each sessions}}
|
|
| {{id}} | {{title}} | {{updated}} | {{msgs}} |
|
|
{{/each}}
|
|
{{else}}
|
|
No sessions found.
|
|
{{/if}}
|
|
`);
|
|
```
|
|
|
|
The Handlebars version is arguably more readable for the template structure, but adds a 216 KB dependency for marginal readability improvement.
|
|
|
|
### `formatMessageList()` (format.ts)
|
|
|
|
```typescript
|
|
// Current: 30 lines, with role icons, truncation logic, separator lines
|
|
// Handlebars would need a custom helper for truncation and role icons
|
|
// → Handlebars adds complexity, not simplicity
|
|
```
|
|
|
|
### `getCompactionPrompt()` (prompt.ts)
|
|
|
|
```typescript
|
|
// Current: 42 lines of static template text
|
|
// This is a static string, not a dynamic template at all
|
|
// Handlebars would be pure overhead
|
|
```
|
|
|
|
### Verdict
|
|
|
|
For the open-memory plugin's current formatting needs (4-5 functions, ~120 lines total), template literals are the right choice. Template engines become valuable when you have:
|
|
- Many templates (20+) that need to be maintained separately from code
|
|
- Non-developers editing templates
|
|
- Complex conditional rendering with repeated patterns
|
|
- Internationalization / localization requirements
|
|
|
|
None of these apply to open-memory currently.
|
|
|
|
---
|
|
|
|
## Existing Codebase Assessment
|
|
|
|
### Current Dependencies (package.json)
|
|
|
|
```json
|
|
{
|
|
"dependencies": {
|
|
"@opencode-ai/plugin": "^1.1.3"
|
|
},
|
|
"devDependencies": {
|
|
"@types/bun": "^1.2.0",
|
|
"@types/node": "^20.14.0",
|
|
"typescript": "^5.7.3"
|
|
}
|
|
}
|
|
```
|
|
|
|
**No template engine dependency exists.** All formatting is done with:
|
|
1. Template literals (`` `Hello ${name}!` ``) for simple interpolation
|
|
2. `lines.push()` + `lines.join("\n")` pattern for multi-line structured output
|
|
3. `String(row.field ?? "default")` for safe data access
|
|
4. `text.slice(0, maxLen)` for truncation
|
|
|
|
These patterns are used consistently across:
|
|
- `src/history/format.ts` -- 3 functions, 73 lines
|
|
- `src/history/search.ts` -- 1 function, 61 lines
|
|
- `src/tools.ts` -- inline formatting in handlers (session lists, compaction tables, context status)
|
|
- `src/compaction/prompt.ts` -- 1 static template, 42 lines
|
|
|
|
Total template-related code: ~250 lines across 4 files. Not enough to justify a template engine dependency.
|
|
|
|
---
|
|
|
|
## Build Pipeline Considerations
|
|
|
|
### Current Build Setup
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"build": "bun build src/index.ts --outdir dist --target bun --format esm && tsc --emitDeclarationOnly"
|
|
}
|
|
}
|
|
```
|
|
|
|
The build uses `bun build` (Bun's native bundler) with `--target bun --format esm`. This produces a single ESM bundle at `dist/index.js` (currently 474 KB).
|
|
|
|
### How `bun build` Handles Handlebars
|
|
|
|
When `bun build` encounters `import Handlebars from "handlebars"`:
|
|
|
|
1. It resolves `handlebars` through Bun's module resolution (looks in `node_modules`)
|
|
2. Since Handlebars is CJS with no ESM entry, Bun's CJS interop wraps it
|
|
3. The bundler traces all reachable exports and includes them in the output
|
|
4. **No tree-shaking occurs** because CJS exports are dynamic by nature
|
|
5. The entire Handlebars library (compiler + runtime + helpers) is included: **216.8 KB bundled**
|
|
|
|
With precompiled templates and `import HandlebarsRuntime from "handlebars/runtime"`:
|
|
1. Only the runtime entry point is resolved
|
|
2. Still CJS, so still no tree-shaking
|
|
3. But only 22 modules (vs. 44): **40.4 KB bundled**
|
|
|
|
### Custom Build Steps
|
|
|
|
If using Handlebars precompilation, the build pipeline would become:
|
|
|
|
```bash
|
|
# Step 1: Precompile templates (new step)
|
|
npx handlebars src/templates/ -f src/generated/templates.ts --commonjs handlebars/runtime
|
|
|
|
# Step 2: Existing build
|
|
bun build src/index.ts --outdir dist --target bun --format esm
|
|
|
|
# Step 3: Existing type declaration
|
|
tsc --emitDeclarationOnly
|
|
```
|
|
|
|
This adds toolchain complexity for minimal benefit. Precompiled template specs would also need TypeScript type declarations.
|
|
|
|
---
|
|
|
|
## Recommendation
|
|
|
|
### For the open-memory Plugin: Do NOT Add Handlebars
|
|
|
|
**Rationale:**
|
|
|
|
| Factor | Template Literals | Handlebars |
|
|
|--------|-------------------|------------|
|
|
| Bundle size impact | 0 KB | +40 KB (runtime) / +217 KB (full) |
|
|
| Dependencies added | 0 | 1 (plus transitive deps) |
|
|
| Build complexity | None | None (runtime) or added step (precompile) |
|
|
| Rendering speed | ~6 μs | ~19 μs |
|
|
| Code readability | Moderate | Slightly better for complex templates |
|
|
| Maintainability | TypeScript-native | New template syntax, separate .hbs files |
|
|
| Security surface | None | Template injection (mitigated by no user input) |
|
|
|
|
The open-memory plugin has:
|
|
- ~250 lines of template code across 4 files
|
|
- Simple formatting (Markdown tables, lists, status lines)
|
|
- No user-editable templates
|
|
- No internationalization needs
|
|
- No complex conditional logic beyond `if (rows.length === 0)`
|
|
- Startup-time load concerns (OpenCode loads plugins at session start)
|
|
|
|
Adding Handlebars would increase the bundle by 8-46% for zero functional benefit.
|
|
|
|
### If a Template Engine Is Needed in the Future
|
|
|
|
If the formatting requirements grow significantly (e.g., user-configurable output templates, i18n, dozens of templates), the recommended priority order is:
|
|
|
|
1. **Mustache** (14.8 KB) -- If you need only interpolation and section-based logic. Smallest footprint, zero dependencies, works in Bun, XSS-safe by default. The "logic-less" constraint forces cleaner data modeling.
|
|
|
|
2. **Eta** (16.1 KB) -- If you need ERB-style control flow (`<% if (...) { %>`) and are willing to use `renderString()` only (avoid the compiled-template `this` binding bug in Bun). ESM-native, excellent TypeScript support, configurable.
|
|
|
|
3. **Handlebars runtime-only** (40.4 KB) -- If you need Handlebars features (partials, custom helpers, precompilation workflow) and can accept the larger bundle. Use with precompiled templates only -- do not bundle the full Handlebars compiler.
|
|
|
|
4. **Handlebars full** (216.8 KB) -- Only if you need runtime template compilation (e.g., user-provided templates). Not recommended for plugins.
|
|
|
|
5. **EJS** -- Not recommended. Slowest engine, security concerns with `Function()` constructor, minimal advantages over Eta.
|
|
|
|
### Template Literal Best Practices (Current Approach)
|
|
|
|
For now, continue using template literals but consider these improvements:
|
|
|
|
```typescript
|
|
// Helper for markdown tables (type-safe)
|
|
function markdownTable(headers: string[], rows: string[][]): string {
|
|
const headerLine = `| ${headers.join(" | ")} |`;
|
|
const separatorLine = `| ${headers.map(() => "---").join(" | ")} |`;
|
|
const dataLines = rows.map(row => `| ${row.join(" | ")} |`);
|
|
return [headerLine, separatorLine, ...dataLines].join("\n");
|
|
}
|
|
|
|
// Use tagged templates for multi-line strings
|
|
const compactionPrompt = String.raw`
|
|
You are compacting your own session to free context space.
|
|
...
|
|
`;
|
|
```
|
|
|
|
This keeps the zero-dependency advantage while reducing the `lines.push()` boilerplate.
|
|
|
|
---
|
|
|
|
## Appendix: Test Environment
|
|
|
|
- **Runtime**: Bun v1.3.11 (Linux x64)
|
|
- **Node compatibility**: Handlebars tested on Node v22+ (works)
|
|
- **Bundle target**: `--target bun --format esm`
|
|
- **Benchmark**: 10,000 iterations per test, single-threaded, warmed up
|
|
- **Template complexity**: Simple (`Hello {{name}}!`) and complex (20-item list with conditionals)
|
|
- **All engines tested**: Handlebars 4.7.9, Mustache 4.2.0, Eta 4.5.1, EJS 5.0.2
|
|
|
|
---
|
|
|
|
*Research conducted 2026-04-22. Versions and benchmarks reflect the state of npm at the time of writing.*
|