A JavaScript and TypeScript engine for the Liquid template language.
import { Template } from "liquidscript";
const template = Template.fromString("Hello, {{ you }}!");
// Sync
console.log(template.renderSync({ you: "World" })); // Hello, World!
console.log(template.renderSync({ you: "Liquid" })); // Hello, Liquid!
// Async
template.render({ you: "World" }).then(console.log); // Hello, World!
template.render({ you: "Liquid" }).then(console.log); // Hello, Liquid!
// Or, using await
(async () => {
console.log(await template.render({ you: "World" })); // Hello, World!
console.log(await template.render({ you: "Liquid" })); // Hello, Liquid!
})();
- Documentation: https://jg-rp.github.io/liquidscript/
- API documentation: https://jg-rp.github.io/liquidscript/api
- Filter reference: https://jg-rp.github.io/liquidscript/language/filters
- Tag reference: https://jg-rp.github.io/liquidscript/language/tags
- Change log: https://github.com/jg-rp/liquidscript/blob/main/CHANGELOG.md
- NPM: https://www.npmjs.com/package/liquidscript
- Issue tracker: https://github.com/jg-rp/liquidscript/issues
LiquidScript currently depends on Decimal.js for decimal arithmetic, and luxon for timezone aware date/times and some limited parsing of date and time strings.
Both of these dependencies are considered an implementation detail and might be replace with lighter-weight alternatives later.
LiquidScript is written in TypeScript, compiled to JavaScript using Babel and bundled using Rollup. The following included bundles target defaults, not IE 11, maintained node versions, not node 12
. See Browserslist.
Bundle | Description |
---|---|
liquidscript.cjs.js |
A CommonJS formatted bundle |
liquidscript.esm.js |
An ECMAScript module formatted bundle |
liquidscript.iife.bundle.js |
A bundle formatted as an Immediately Invoked Function Expression, including dependencies. |
liquidscript.iife.bundle.min.js |
A minified bundle formatted as an Immediately Invoked Function Expression, including dependencies. |
liquidscript.iife.js |
A bundle formatted as an Immediately Invoked Function Expression, excluding dependencies. |
liquidscript.iife.min.js |
A minified bundle formatted as an Immediately Invoked Function Expression, excluding dependencies. |
Some excellent JavaScript implementations of Liquid already exist. To meet some rather specific requirements, LiquidScript has been developed with the following goals.
-
Maintain a very strict policy of compatibility with Ruby Liquid and, by extension, Python Liquid. Given an equivalent render context, a template rendered with LiquidScript should produce the same output as when rendered with Ruby Liquid, and vice versa. See golden-liquid. Most notably:
- Floats with a single trailing zero must retain that zero upon output.
- Built-in math filters must handle integers and floats appropriately. For example, the
divided_by
filter should perform integer division if both arguments are integers, and regular division otherwise. - Built-in math filters must do decimal arithmetic. See Decimal.js dependency.
- Built-in filters must reject excess or otherwise invalid arguments with an error.
-
It should be possible to extend LiquidScript (without forking) to include features commonly found in other template languages. Like template inheritance, expressions that use logical
not
and inline conditional statements, for example. -
Construct and expose a syntax tree for every parsed template, facilitating template static analysis and performance optimizations.
-
Offer fine-grained control of template context globals. Pin globals to an environment, template or loader.
-
Offer an asynchronous API, including handling of render context promises and asynchronous drops.
-
Offer HTML and XML auto-escaping, with facilities to mark text as "safe". See Auto Escape.
-
Drops (arbitrary objects added to a render context) must not expose their methods unless explicitly whitelisted. See drop protocol.
You can run the benchmark using yarn benchmark
from the root of the source tree. On my development machine we get the following results.
templates per iteration: 60
rounds (best of): 3
simulated IO time: 50ms
parse ...
renderSync ...
parse & renderSync ...
render ...
render + parse with IO ...
parse: 63.15 i/s - 300 in 4.75s
renderSync: 213.65 i/s - 1000 in 4.68s
parse & renderSync: 44.36 i/s - 250 in 5.64s
render: 109.45 i/s - 500 in 4.57s
render + parse with IO: 14.91 i/s - 50 in 3.35s
The benchmark workload has been carefully matched to that of the reference implementation, although it's not clear what, if any, overhead their benchmark includes.
When the same benchmark is run using LiquidJS, it shows that, for parsing templates, LiquidJS is faster than LiquidScript, but LiquidScript is faster at rendering templates.
Compared to Python Liquid, which includes a comparable benchmark, LiquidScript is as much as two or three times faster for synchronous operations. Python Liquid's benchmark does not currently include figures for asynchronously rendering templates.