{% for todo in todos %}
{% render 'todo.liquid' with todo, index: forloop.index%}
diff --git a/demo/typescript/index.ts b/demo/typescript/index.ts
index d0b5a2773..611f5361e 100644
--- a/demo/typescript/index.ts
+++ b/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(`
${title}
`)
}
})
-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)
diff --git a/demo/typescript/todolist.liquid b/demo/typescript/todolist.liquid
index cc7ef4972..d452e1fc8 100644
--- a/demo/typescript/todolist.liquid
+++ b/demo/typescript/todolist.liquid
@@ -1,4 +1,4 @@
-{%header content: title | capitalize%}
+{%header title | capitalize%}
{% for todo in todos %}
diff --git a/demo/typescript/tsconfig.json b/demo/typescript/tsconfig.json
index 07f29e72e..273db946d 100644
--- a/demo/typescript/tsconfig.json
+++ b/demo/typescript/tsconfig.json
@@ -1,7 +1,9 @@
{
"compilerOptions": {
- "types": [
- "node"
- ]
+ "target": "es6",
+ "moduleResolution": "node",
+ "types": [
+ "node"
+ ]
}
}
\ No newline at end of file
diff --git a/docs/source/tutorials/register-filters-tags.md b/docs/source/tutorials/register-filters-tags.md
index 3ceb12d0d..906973e8f 100644
--- a/docs/source/tutorials/register-filters-tags.md
+++ b/docs/source/tutorials/register-filters-tags.md
@@ -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'
}
});
@@ -41,7 +41,7 @@ engine.registerTag('upper', class UpperTag extends Tag {
});
```
-See existing tag implementations here:
+See existing tag implementations here:
See demo example here: https://github.com/harttle/liquidjs/blob/master/demo/typescript/index.ts
## Register Filters
@@ -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:
+See existing filter implementations here:
## Unregister Tags/Filters
diff --git a/docs/source/tutorials/sync-and-async.md b/docs/source/tutorials/sync-and-async.md
index 1929dd9a9..dbca383b4 100644
--- a/docs/source/tutorials/sync-and-async.md
+++ b/docs/source/tutorials/sync-and-async.md
@@ -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'
-- Should not directly `return `, and
-- Should not be declared as `async`.
+// 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 `, and
+- do not call any APIs that returns 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`:
+
+```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 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 %}
-### 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: .
+## 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 `