Files
ed15a52c26 docs: revisit wording & style for liquidjs.com (#906)
* docs: polish theme, playground, and reference pages

Improve readability of the docs site with updated light/dark tokens, shared
code-block styling, and playground editors that follow system color scheme.
Skip CookieHub on localhost, serve the browser bundle from theme source, and
use backtick titles on filter/tag reference pages for consistent navigation.

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

* docs: highlight npx in bash blocks and polish English copy

Use Prism insertBefore for CLI commands like npx, tighten tutorial and reference wording, and keep YAML titles free of backticks so sidebar and page headings stay correct.

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

* docs: restore lowercase filter and tag titles

Titles should match actual filter/tag identifiers (e.g. abs, append), not capitalized English labels.

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

---------

Co-authored-by: Cursor <[email protected]>
2026-06-08 00:26:52 +08:00

4.0 KiB

title
title
Sync and Async

LiquidJS supports both synchronous and asynchronous evaluation, and can be used with Promises. To reuse the same set of tag/filter implementations in both sync and async modes, LiquidJS tags are implemented as generators.

Sync and Async API

All major methods on Liquid support both sync and async. These methods return Promises:

  • render()
  • renderFile()
  • parseFile()
  • parseAndRender()
  • evalValue()

The synchronous version of methods contains a Sync suffix:

  • renderSync()
  • renderFileSync()
  • parseFileSync()
  • parseAndRenderSync()
  • evalValueSync()

Implement Sync-Compatible Tags

LiquidJS uses a generator-based async implementation to support both async and sync in one piece of tag implementation. For example, below UpperTag can be used in both engine.renderSync() and engine.render().

import { TagToken, Context, Emitter, TopLevelToken, Value, Tag, Liquid } from 'liquidjs'

// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', class UpperTag extends Tag {
  private value: Value
  constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
    super(token, remainTokens, liquid)
    this.value = new Value(token.args, liquid)
  }
  * render (ctx: Context, emitter: Emitter) {
    const title = yield this.value.value(ctx)
    emitter.write(title.toUpperCase())
  }
})

All built-in tags are implemented this way and are safe to use in both sync and async modes (I'll call it sync-compatible). To make your custom tag sync-compatible, you'll need to:

  • declare render function as * render(), in which
  • do not directly return <Promise>, and
  • do not call any APIs that return a Promise.

Call APIs that return a Promise

But LiquidJS is Promise-friendly, right? You can still call Promise-based functions and wait for that Promise within tag implementations. Just replace await with yield. e.g. we're calling fs.readFile() which returns a Promise:

  * render (ctx: Context, emitter: Emitter) {
    const file = yield this.value.value(ctx)
    const title = yield fs.readFile(file, 'utf8')
    emitter.write(title.toUpperCase())
  }

Now that this * render() calls an API that returns a Promise, so it's no longer sync-compatible.

{% note info Non Sync-Compatible Tags %} Non sync-compatible tags are also valid tags, will work just fine for asynchronous API calls. When called synchronously, tags that return a Promise will be rendered as [object Promise]. {% endnote %}

Convert LiquidJS async Generator to Promise

You can convert a Generator to Promise by toPromise, for example:

import { TagToken, Context, Emitter, TopLevelToken, Value, Tag, Liquid, toPromise } from 'liquidjs'

// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', class UpperTag extends Tag {
  private value: Value
  constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
    super(token, remainTokens, liquid)
    this.value = new Value(token.args, liquid)
  }
  async render (ctx: Context, emitter: Emitter) {
    const title = await toPromise(this.value.value(ctx))
    emitter.write(title.toUpperCase())
  }
})

Async only Tags

If your tag is intended to be used only asynchronously, it can be declared as async render() so you can use await in its implementation directly:

import { toPromise, TagToken, Context, Emitter, TopLevelToken, Value, Tag, Liquid } from 'liquidjs'

// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', class UpperTag extends Tag {
  private value: Value
  constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
    super(token, remainTokens, liquid)
    this.value = new Value(token.args, liquid)
  }
  async render (ctx: Context, emitter: Emitter) {
    const title = await toPromise(this.value.value(ctx))
    emitter.write(`<h1>${title}</h1>`)
  }
})