7ab49f999a fix: charge join/json/inspect filters by produced output size (#925)
* fix(filters): charge join/array_to_sentence_string by output size

join charged memoryLimit by array element count, not by the string it
produces, letting concat doubling (cheap reference copies) inflate an
array's element count and then materialize a huge string via join far
past the configured memoryLimit (GHSA-4r6h-5v86-94p3). Charge by the
sum of stringified element lengths plus separators before allocating.
Apply the same fix to the sibling array_to_sentence_string filter.

Co-authored-by: Cursor <[email protected]>

* refactor(filters): simplify join output-size accounting

Sum stringified element lengths in a single pass and keep the guarded
Array.prototype.join for the result, instead of building an intermediate
parts array.

Co-authored-by: Cursor <[email protected]>

* fix(filters): charge json/jsonify/inspect serialization to memoryLimit

json/jsonify/inspect serialized values without charging memoryLimit, so
a concat-doubled array (cheap reference copies) could be materialized
into a huge JSON string past the configured limit — the same unbounded
class as the join bug (GHSA-4r6h-5v86-94p3). Charge via a JSON.stringify
replacer that accounts string lengths as it walks, aborting mid-
serialization instead of allocating the full blob first.

Co-authored-by: Cursor <[email protected]>

* fix(memory): charge rendered output to memoryLimit at emission

Move output-length accounting into the emitters, which charge each
written chunk against ctx.memoryLimit right before it reaches the
result string or stream. Filters/tags now only pre-charge the extra
working memory they allocate apart from that output, so join drops its
bespoke output-size counting and charges array.length like its siblings.

The block.super capture emitter intentionally omits the limiter to
avoid double-counting content that is re-emitted through the final
emitter.

Co-authored-by: Cursor <[email protected]>

* refactor(filters): rely on emitter output charge for json/inspect/array_to_sentence_string

With rendered output charged at emission, these filters no longer need
bespoke output-size counting: the emitted case is covered by the final
emitter. Revert json/inspect to their original form and array_to_sentence_string
to its element-count charge, dropping the non-emitted `| size` guards.

Co-authored-by: Cursor <[email protected]>

* revert(memory): drop emitter output charge, restore filter output-size accounting

join/array_to_sentence_string/json/inspect charge memoryLimit by the
string they materialize (not element count), so discarded results like
{% assign out = a | join %}{{ out | size }} are still bounded.

Remove the emitter-level limiter added in 2f343f063; it cannot catch
materialized-but-not-emitted values.

Co-authored-by: Cursor <[email protected]>

* fix(filters): charge json/inspect replacer by serialized node size

Replace the flat 1-unit charge for non-string JSON nodes with per-type
estimates (primitives via JSON.stringify length, containers by structure).

Co-authored-by: Cursor <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>
2026-07-06 23:54:00 +08:00
2024-08-28 00:31:59 +08:00
2021-06-20 13:25:08 +08:00
2026-06-23 10:43:45 +00:00
2022-07-08 02:56:39 +08:00
2016-06-13 15:39:31 +08:00
2026-06-23 10:43:45 +00:00

LiquidJS

A simple, expressive, extensible Liquid template engine for JavaScript — Shopify, Jekyll and GitHub Pages compatible, for Node.js, browsers, and the CLI, with TypeScript support.

npm version npm downloads Build Status Coverage License: MIT

Documentation · Playground · Setup guide · Contributing

LiquidJS playground: edit a template and context, see live HTML output

Try the online playground.

Quick start

import { Liquid } from 'liquidjs'

const engine = new Liquid()
const html = await engine.parseAndRender(
  'Hello, {{ name | capitalize }}!',
  { name: 'liquid' }
)
//=> 'Hello, Liquid!'

Installation

Node.js

npm install liquidjs

Browser (jsDelivr UMD bundle)

<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.browser.min.js"></script>

CLI

npx liquidjs --template 'Hello, {{ name }}!' --context '{"name": "Liquid"}'

See the setup guide for partials, layouts, caching, and other options.

Used by

Eleventy Opensense Power Pages, Azure API Management developer portal GitHub Docs Kibana Shopify CLI, Checkout Blocks Customer IO
Syntax Podcast TestMu AI Chudovo PakStyle.pk EscortA.com Touchless
Dropkiq Directus Builder.io, Mitosis Pattern Lab Rock RMS WISMOlabs Freshet

Products and projects running on LiquidJS. Open a PR to add yours.

Financial Support

If you personally love LiquidJS or it's benefiting your business, please consider financially support us via GitHub Sponsors. Special thanks to our sponsors!

Eleventy Opensense Inc. Microsoft Sentry Checkout Blocks Customer IO Syntax Podcast
TestMu AI Tal Chudovo Dailycontributors PakStyle.pk EscortA.com
Touchless Dropkiq Peter deHaan Serkan Holat amit777 Khaled Salem Emmanuel Cartelli Cartelli Emmanuel

Contributors

Want to contribute? see Contribution Guidelines. Thanks goes to these wonderful people:

Jun Yang
Jun Yang

🚧 💻
chenos
chenos

💻
Zach Leatherman
Zach Leatherman

🐛
Tim Hardy
Tim Hardy

💻
Paul Robert Lloyd
Paul Robert Lloyd

💻 🐛
Alec Larson
Alec Larson

💻
Patrick Malouin
Patrick Malouin

💻 📖
jaswrks
jaswrks

💻
三三
三三

💻 🤔
ssendev
ssendev

💻 📖
wojtask9
wojtask9

💻
Andrew Barclay
Andrew Barclay

💻
Cory Mawhorter
Cory Mawhorter

💻
Mehdi Jaffery
Mehdi Jaffery

💻
Robin Bijlani
Robin Bijlani

💻 🐛
Ryan Kennedy
Ryan Kennedy

💻
Sami Kukkonen
Sami Kukkonen

💻
Scott Santucci
Scott Santucci

💻
Steven
Steven

💡 💻
azu
azu

📖
Joonas
Joonas

💻
Jamel A.
Jamel A.

💻
Brandon Pittman
Brandon Pittman

💻
tgrandgent
tgrandgent

💻
Martin Schuster
Martin Schuster

💻
Ray
Ray

⚠️ 💻
Cristofer Gonzales
Cristofer Gonzales

💻
Raymond Camden
Raymond Camden

📖
Steve Stedman
Steve Stedman

📖
Anthony Ciccarello
Anthony Ciccarello

📖
Bogdan Chadkin
Bogdan Chadkin

💻
Tejas Manohar
Tejas Manohar

💻
Peter deHaan
Peter deHaan

📖
amit777
amit777

💻
Steffen Schuldenzucker
Steffen Schuldenzucker

💻
Pixcell
Pixcell

💻
Jason Etcovitch
Jason Etcovitch

💻
ZC
ZC

📖
Memmie Lenglet
Memmie Lenglet

💻
ilhamdev0
ilhamdev0

📖
一饮一啄皆是人生
一饮一啄皆是人生

📖
Amit Agarwal
Amit Agarwal

📖
Laurin Quast
Laurin Quast

💻
Matt Vague
Matt Vague

💻
Liam Bigelow
Liam Bigelow

💻
Jason Kurian
Jason Kurian

📖
d pham (they/them)
d pham (they/them)

📖
Aleksandr Hovhannisyan
Aleksandr Hovhannisyan

💻
jg-rp
jg-rp

💻
Ameya Apte
Ameya Apte

💻
tbdrz
tbdrz

📖
Santi Albo
Santi Albo

📖 💻
Yahang Wu
Yahang Wu

📖
hongl
hongl

📖
zxx-457
zxx-457

📖
prassie
prassie

📖
Slav Ivanov
Slav Ivanov

💻
Daniel Rosenberg
Daniel Rosenberg

💻
bobgubko
bobgubko

💻
BaNgan
BaNgan

📖
Mahyar Pasarzangene
Mahyar Pasarzangene

📖
Tomáš Hübelbauer
Tomáš Hübelbauer

💻 📖
Jason Garber
Jason Garber

💻
Nick Reilingh
Nick Reilingh

📖
Francisco Soto
Francisco Soto

💻
David LJ
David LJ

📖
Rasmus Wriedt Larsen
Rasmus Wriedt Larsen

📖
Bruno Carvalho
Bruno Carvalho

💻
傅鹏
傅鹏

💻
Joel Hamilton
Joel Hamilton

💻
Max Medve
Max Medve

💻
Cosmin Popovici
Cosmin Popovici

📖
Adam Tanner
Adam Tanner

💻
Guillermo Casal Caro
Guillermo Casal Caro

💻
Josh Soref
Josh Soref

📖
Koen
Koen

💻
Matthieu Bacconnier
Matthieu Bacconnier

📖
Tim van Dam
Tim van Dam

💻
Ed Hanton
Ed Hanton

📖
Vlad GURDIGA
Vlad GURDIGA

📖
裸奔狂甩丁丁
裸奔狂甩丁丁

📖
Ihor Panasiuk
Ihor Panasiuk

💻
Omri Rosner
Omri Rosner

💻
immerrr again
immerrr again

📖
Loo Rong Jie
Loo Rong Jie

💻
MorielHarush
MorielHarush

💻
Peak Twilight
Peak Twilight

💻
Joe Cottam
Joe Cottam

💻
Timmy Braun
Timmy Braun

💻
Tal
Tal

💻
Vladimir Filonov
Vladimir Filonov

💻
spokodev
spokodev

💻

License

MIT © Jun Yang

S
Description
No description provided
Readme MIT
65 MiB
Languages
TypeScript 94.8%
JavaScript 3.8%
Liquid 0.7%
SWIG 0.3%
Handlebars 0.3%