Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 56 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@ jobs:
- name: Checkout
uses: actions/checkout@v6

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'pnpm'

- name: Install dependencies
run: npm ci
run: pnpm install --frozen-lockfile

- name: Lint
run: npm run lint
run: pnpm run lint

test:
name: Test (Node)
Expand All @@ -33,32 +37,66 @@ jobs:
matrix:
operating-system: ['ubuntu-latest', 'windows-latest']
# https://nodejs.org/en/about/releases/
node-version: ['20', '22', '24']
# Node 20 is supported at runtime (see "engines" in package.json) but is not
# part of this pnpm matrix because pnpm >=11 requires Node >=22.13. It is
# validated separately in the "Test (Node 20)" job below using npm.
node-version: ['22', '24']

steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

- name: Install dependencies
run: npm ci
run: pnpm install --frozen-lockfile

- name: Test
run: npm run test
run: pnpm run test

- name: Test (Publish)
if: matrix.node-version != '20'
run: npx vitest run --project publish
run: pnpm exec vitest run --project publish

- name: Test (Integration)
if: matrix.operating-system == 'ubuntu-latest'
run: npm run test:integration
run: pnpm run test:integration

test-node20:
# Node 20 is a supported runtime (see "engines" in package.json) but cannot run
# the pinned pnpm (pnpm >=11 requires Node >=22.13), so it is excluded from the
# main "test" matrix. This job validates Node 20 compatibility of the library by
# installing with npm and running the full unit test suite, which imports the
# library source directly (no build/tooling step required).
name: Test (Node 20)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20'

# No package-lock.json is committed (the repo uses pnpm-lock.yaml), so install
# with `npm install` rather than `npm ci`. --legacy-peer-deps mirrors the
# existing bundler integration tests.
- name: Install dependencies
run: npm install --legacy-peer-deps

- name: Test (unit)
run: npm run test:unit

browser:
name: Test (Browser)
Expand All @@ -69,23 +107,27 @@ jobs:
with:
submodules: true

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'pnpm'

- name: Install dependencies
run: npm ci
run: pnpm install --frozen-lockfile

- name: Install Playwright
run: |
npx playwright install-deps
npx playwright install
pnpm exec playwright install-deps
pnpm exec playwright install

- name: Build
run: npm run build
run: pnpm run build

- name: Test
run: |
npm run test:browser-smoke
npm run test:browser
pnpm run test:browser-smoke
pnpm run test:browser
8 changes: 6 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,23 @@ jobs:
with:
submodules: true

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'pnpm'

- name: Install dependencies
run: npm ci
run: pnpm install --frozen-lockfile

- name: Publish
run: |
git config --global user.email "release@handlebarsjs.com"
git config --global user.name "handlebars-lang"
npm run publish:aws
pnpm run publish:aws
env:
S3_BUCKET_NAME: 'builds.handlebarsjs.com'
S3_REGION: 'us-east-1'
Expand Down
26 changes: 15 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ To build Handlebars.js you'll need Node.js installed.

Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`.

Project dependencies may be installed via `npm install`.
This project uses [pnpm](https://pnpm.io/) as its package manager (the required version is pinned in the `packageManager` field of `package.json`). The easiest way to get the right version is via [Corepack](https://nodejs.org/api/corepack.html), which ships with Node.js: run `corepack enable` once, and pnpm commands will automatically use the pinned version.

To build Handlebars.js from scratch, run `npm run build` in the root of the project. That will compile CJS modules via SWC and bundle UMD distributions via rspack, outputting results to the dist/ folder. To run tests, use `npm test`.
> **Note:** While Handlebars itself supports Node.js >=20 at runtime (see `engines` in `package.json`), the development tooling requires a newer Node.js for the pinned pnpm version: pnpm 11 requires Node.js >=22.13. Use Node.js 22.13 or later when building and testing locally.

Project dependencies may be installed via `pnpm install`.

To build Handlebars.js from scratch, run `pnpm run build` in the root of the project. That will compile CJS modules via SWC and bundle UMD distributions via rspack, outputting results to the dist/ folder. To run tests, use `pnpm test`.

If you notice any problems, please report them to the GitHub issue tracker at
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
Expand All @@ -51,7 +55,7 @@ If you notice any problems, please report them to the GitHub issue tracker at
To run tests locally, first install all dependencies.

```sh
npm install
pnpm install
```

Clone the mustache specs into the spec/mustache folder.
Expand All @@ -65,7 +69,7 @@ git clone https://github.com/mustache/spec.git mustache
From the root directory, run the tests.

```sh
npm test
pnpm test
```

## Linting and Formatting
Expand All @@ -75,10 +79,10 @@ Committed files are linted and formatted in a pre-commit hook.

You can use the following scripts to make sure that the CI job does not fail:

- **npm run lint** will run all linters and fail on warnings
- **npm run format** will format all files
- **npm run check-before-pull-request** will perform all checks that our CI job does, excluding integration tests.
- **npm run test:integration** will run integration tests (bundler compatibility with webpack, rollup, etc.)
- **pnpm run lint** will run all linters and fail on warnings
- **pnpm run format** will format all files
- **pnpm run check-before-pull-request** will perform all checks that our CI job does, excluding integration tests.
- **pnpm run test:integration** will run integration tests (bundler compatibility with webpack, rollup, etc.)
These tests only work on Linux.

## Releasing the latest version
Expand All @@ -96,9 +100,9 @@ _When releasing a previous version of Handlebars, please look into the CONTRIBUN
A full release may be completed with the following:

```
npm ci
npm run build
npm publish
pnpm install --frozen-lockfile
pnpm run build
pnpm publish
```

After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,33 +110,33 @@ The project includes a comprehensive benchmark suite (powered by [tinybench](htt

```bash
# Run benchmarks (auto-labels with current git branch)
npm run bench
pnpm run bench

# Run with a custom label
npm run bench -- --label my-optimization
pnpm run bench -- --label my-optimization

# Filter templates by name (regex, case-insensitive)
npm run bench -- --grep "complex|recursive"
pnpm run bench -- --grep "complex|recursive"

# Run only specific sections (regex, case-insensitive)
npm run bench -- --section precompil
npm run bench -- --section "compilation|precompil"
pnpm run bench -- --section precompil
pnpm run bench -- --section "compilation|precompil"

# Compare results
npm run bench:compare
pnpm run bench:compare

# Or specify files explicitly
npm run bench:compare -- bench/results/bench-*-main.md bench/results/bench-*-feat.md
pnpm run bench:compare -- bench/results/bench-*-main.md bench/results/bench-*-feat.md
```

Results are saved as timestamped Markdown files in `bench/results/`. Each report includes ops/sec, avg latency, p50/p75/p99 percentiles, and sample counts.

Typical workflow for comparing branches:

```bash
git checkout main && npm run bench
git checkout my-feature && npm run bench
npm run bench:compare
git checkout main && pnpm run bench
git checkout my-feature && pnpm run bench
pnpm run bench:compare
```

When run without arguments, `bench:compare` auto-selects two result files: if a file labelled "main" exists it is always used as the baseline, otherwise the older file is the baseline. The comparison uses p75 latency for the diff to filter outliers, and marks changes with `!` (>2%) and `!!` (>5%).
Expand Down
Loading