Skip to content

Commit

Permalink
docs: update docs and demo for Value usage, fixes #568
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Dec 13, 2022
1 parent a9f93d7 commit d246558
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 125 deletions.
11 changes: 0 additions & 11 deletions benchmark/demo.js
Expand Up @@ -8,17 +8,6 @@ const engine = new Liquid({
extname: '.liquid'
})

engine.registerTag('header', {
parse: function (token) {
const [key, val] = token.args.split(':')
this[key] = val
},
render: function (ctx) {
const title = this.liquid.evalValue(this.content, ctx)
return `<h1>${title}</h1>`
}
})

function demo () {
console.log(' demo')
console.log('------------------------')
Expand Down
16 changes: 8 additions & 8 deletions demo/nodejs/index.js
@@ -1,4 +1,4 @@
const { Liquid } = require('liquidjs')
const { Liquid, Tag, Value } = require('liquidjs')

const engine = new Liquid({
extname: '.liquid',
Expand All @@ -11,13 +11,13 @@ const engine = new Liquid({
partials: './partials'
})

engine.registerTag('header', {
parse: function (token) {
const [key, val] = token.args.split(':')
this[key] = val
},
render: async function (scope, emitter) {
const title = await this.liquid.evalValue(this.content, scope)
engine.registerTag('header', class HeaderTag extends Tag {
constructor (token, remainTokens, liquid) {
super(token, remainTokens, liquid)
this.value = new Value(token.args, liquid)
}
* render (ctx, emitter) {
const title = yield this.value.value(ctx)
emitter.write(`<h1>${title}</h1>`)
}
})
Expand Down
2 changes: 1 addition & 1 deletion demo/nodejs/todolist.liquid
@@ -1,5 +1,5 @@
{% layout 'html.liquid' %}
{%header content: title | capitalize%}
{%header title | capitalize%}
<ul>
{% for todo in todos %}
{% render 'todo.liquid' with todo, index: forloop.index%}
Expand Down
21 changes: 11 additions & 10 deletions demo/typescript/index.ts
@@ -1,24 +1,25 @@
import { Liquid, TagToken, Context, Emitter } from 'liquidjs'
import { Value, Liquid, TagToken, Context, Emitter, Tag, TopLevelToken } from 'liquidjs'

const engine = new Liquid({
root: __dirname,
extname: '.liquid'
})

engine.registerTag('header', {
parse: function (token: TagToken) {
const [key, val] = token.args.split(':')
this[key] = val
},
render: async function (context: Context, emitter: Emitter) {
const title = await this.liquid.evalValue(this['content'], context)
engine.registerTag('header', class HeaderTag 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(`<h1>${title}</h1>`)
}
})

const ctx = {
const scope = {
todos: ['fork and clone', 'make it better', 'make a pull request'],
title: 'Welcome to liquidjs!'
}

engine.renderFile('todolist', ctx).then(console.log)
engine.renderFile('todolist', scope).then(console.log)
2 changes: 1 addition & 1 deletion demo/typescript/todolist.liquid
@@ -1,4 +1,4 @@
{%header content: title | capitalize%}
{%header title | capitalize%}

<ul>
{% for todo in todos %}
Expand Down
8 changes: 5 additions & 3 deletions demo/typescript/tsconfig.json
@@ -1,7 +1,9 @@
{
"compilerOptions": {
"types": [
"node"
]
"target": "es6",
"moduleResolution": "node",
"types": [
"node"
]
}
}
10 changes: 5 additions & 5 deletions docs/source/tutorials/register-filters-tags.md
Expand Up @@ -6,14 +6,14 @@ title: Register Filters/Tags

```typescript
// Usage: {% upper name %}
import { TagToken, Context, Emitter, TopLevelToken } from 'liquidjs'
import { Value, TagToken, Context, Emitter, TopLevelToken } from 'liquidjs'

engine.registerTag('upper', {
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.str = tagToken.args; // name
this.value = new Value(token.args, liquid)
},
render: function*(ctx: Context) {
const str = yield this.liquid.evalValue(this.str, ctx); // 'alice'
const str = yield this.value.value(ctx); // 'alice'
return str.toUpperCase() // 'ALICE'
}
});
Expand Down Expand Up @@ -41,7 +41,7 @@ engine.registerTag('upper', class UpperTag extends Tag {
});
```

See existing tag implementations here: <https://github.com/harttle/liquidjs/tree/master/src/builtin/tags>
See existing tag implementations here: <https://github.com/harttle/liquidjs/tree/master/src/tags>
See demo example here: https://github.com/harttle/liquidjs/blob/master/demo/typescript/index.ts

## Register Filters
Expand All @@ -58,7 +58,7 @@ Filter arguments will be passed to the registered filter function, for example:
engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2)
```

See existing filter implementations here: <https://github.com/harttle/liquidjs/tree/master/src/builtin/filters>
See existing filter implementations here: <https://github.com/harttle/liquidjs/tree/master/src/filters>

## Unregister Tags/Filters

Expand Down
104 changes: 65 additions & 39 deletions docs/source/tutorials/sync-and-async.md
Expand Up @@ -24,67 +24,93 @@ The synchronous version of methods contains a `Sync` suffix:

## Implement Sync-Compatible Tags

### Requirements
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()`.

All builtin tags are *sync-compatible* and safe to use for both sync and async APIs. To make your custom tag *sync-compatible*, you'll need to avoid return a `Promise`. That means the `render(context, emitter)`:
```typescript
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 builtin tags are implemented this way and safe to use in both sync and async (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 returns a Promise.

- Should not directly `return <Promise>`, and
- Should not be declared as `async`.
## 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`:

```typescript
* 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 <em>sync-compatible</em> tags are also valid tags, will work just fine for asynchronous API calls. When called synchronously, tags that return a <code>Promise</code> will be rendered as <code>[object Promise]</code>.
{% endnote %}

### Await Promises
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` and keep `* render()` instead of `async render()`. e.g.
## Convert LiquidJS async Generator to Promise

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

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

// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', {
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.str = tagToken.args
},
* render: function(ctx: Context) {
// _evalValue will behave synchronously when called by synchronous API
// in which case `ctx.sync == true`
var str = yield this.liquid._evalValue(this.str, ctx)
return str.toUpperCase()
}
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())
}
})
```

See this JSFiddle: <http://jsfiddle.net/ctj364up/6/>.

## Async-only Tags
## Async only Tags

For tags that intended to be used only by async API, or those cannot be implemented synchronously, there's no difference between using generator-base syntax or async syntax. I'll call them *async-only tags*.

For example, if the above `this.liquid._evalValue()` doesn't respect `ctx.sync` and always returns a Promise, even if the tag is implemented using `* render()` and `yield this.liquid._evalValue()`, it will be rendered as `<object Promise>` anyway.

For *async-only tags*, you can use async syntax at will. Be careful some APIs in LiquidJS return Promises and others return Generators. You'll need [toPromise][toPromise] API to convert a Generator to a Promise, for example:
If your tag is intend to be used only asynchronously, it can be declared as `async render()` so you can use `await` in its implementation directly:

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

// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', {
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.str = tagToken.args; // name
},
render: async function(ctx: Context) {
var str = await toPromise(this.liquid._evalValue(this.str, ctx));
// Or use the alternate API that returns a Promise
// var str = await this.liquid.evalValue(this.str, ctx);
return str.toUpperCase()
}
});
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>`)
}
})
```

See this JSFiddle: <http://jsfiddle.net/ctj364up/5/>.

[Liquid]: /api/classes/liquid_.liquid.html
[toPromise]: /api/modules/liquid_.html#toPromise
29 changes: 24 additions & 5 deletions docs/source/zh-cn/tutorials/register-filters-tags.md
Expand Up @@ -8,10 +8,10 @@ title: 注册标签和过滤器
// 使用方式: {% upper name %}
engine.registerTag('upper', {
parse: function(tagToken, remainTokens) {
this.str = tagToken.args; // name
this.value = new Value(token.args, liquid)
},
render: async function(scope, hash) {
var str = await this.liquid.evalValue(this.str, scope); // 'alice'
render: function*(scope, hash) {
const str = yield this.value.value(ctx); // 'alice'
return str.toUpperCase() // 'Alice'
}
});
Expand All @@ -20,7 +20,26 @@ engine.registerTag('upper', {
* `parse`: 从 `remainTokens` 中读取后续的标签/输出/HTML,直到找到你期望的结束标签。
* `render`: 把 scope 数据和此前解析得到的 Token 结合,输出 HTML 字符串。

查看已有的标签实现:<https://github.com/harttle/liquidjs/tree/master/src/builtin/tags>
对于更复杂的标签实现,可以提供一个继承自 `Tag` 的类:

```typescript
// Usage: {% upper name:"alice" %}
import { Hash, Tag, TagToken, Context, Emitter, TopLevelToken, Liquid } from 'liquidjs'

engine.registerTag('upper', class UpperTag extends Tag {
private hash: Hash
constructor(tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
this.hash = new Hash(tagToken.args)
}
* render(ctx: Context) {
const hash = yield this.hash.render();
return hash.name.toUpperCase() // 'ALICE'
}
});
```

可以参考已有的标签实现:<https://github.com/harttle/liquidjs/tree/master/src/tags>

## 注册过滤器

Expand All @@ -36,7 +55,7 @@ engine.registerFilter('upper', v => v.toUpperCase())
engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2)
```

查看已有的过滤器实现:<https://github.com/harttle/liquidjs/tree/master/src/builtin/filters>。对于复杂的标签,也可以用一个类来实现:
查看已有的过滤器实现:<https://github.com/harttle/liquidjs/tree/master/src/filters>。对于复杂的标签,也可以用一个类来实现:

```typescript
// Usage: {% upper name:"alice" %}
Expand Down

0 comments on commit d246558

Please sign in to comment.