mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-16 12:50:38 -07:00
docs: add tutorials for custom filters and tags
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: 过滤器里访问上下文
|
||||
---
|
||||
|
||||
在 [注册过滤器和标签][register-filters] 里介绍过,可以在函数参数里直接获得过滤器的参数:
|
||||
|
||||
```javascript
|
||||
// Usage: {{ 1 | add: 2, 3 }}
|
||||
// Output: 6
|
||||
engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2)
|
||||
```
|
||||
|
||||
但有些过滤器还需要访问当前上下文的变量,比如把 URL 路径转换为完整的 URL 时,需要访问上下文的 `origin` 变量:
|
||||
|
||||
```javascript
|
||||
// Usage: {{ '/index.html' | fullURL }}
|
||||
// Scope: { origin: "https://liquidjs.com" }
|
||||
// Output: https://liquidjs.com/index.html
|
||||
|
||||
engine.registerFilter('fullURL', function (path) {
|
||||
const origin = this.context.get(['origin'])
|
||||
return new URL(path, origin).toString()
|
||||
})
|
||||
```
|
||||
|
||||
见这个 JSFiddle:<http://jsfiddle.net/ctj364up/1/>。
|
||||
|
||||
{% note warn 箭头函数 %}
|
||||
在箭头函数里 `this` 会绑定到当前 JavaScript 上下文,你需要用 `function(){}` 来替代 `()=>{}` 语法,才能正确地访问 `this.context`。
|
||||
{% endnote %}
|
||||
|
||||
[register-filters]: /tutorials/register-filters-tags.html
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: 参数解析
|
||||
---
|
||||
|
||||
## 访问原始参数
|
||||
|
||||
在 [注册过滤器和标签][register-tags] 中提到,可以通过 `tagToken.args` 来得到标签的原始参数字符串。例如:
|
||||
|
||||
```javascript
|
||||
// Usage: {% random foo bar coo %}
|
||||
// Output: "foo", "bar" or "coo"
|
||||
engine.registerTag('random', {
|
||||
parse(tagToken) {
|
||||
// tagToken.args === "foo bar coo"
|
||||
this.items = tagToken.args.split(' ')
|
||||
},
|
||||
render(context, emitter) {
|
||||
// get a random index
|
||||
const index = Math.floor(this.items.length * Math.random())
|
||||
// output that item
|
||||
emitter.write(this.items[index])
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
见这个 JSFiddle:<http://jsfiddle.net/ctj364up/2/>。
|
||||
|
||||
## 解析参数的值
|
||||
|
||||
除了静态的参数字符串之外,我们更希望把动态的值传递给标签。LiquidJS 中的值可以是字面量(字符串、数字等,也可以是当前上下文的变量。
|
||||
|
||||
下面是修改过的模板,也包含三个值用来随机。但它们表示的是值而不是静态的字符串。第一个是字符串字面量,第二个是标识符(表示变量),第三个是属性访问表达式,包含两个标识符。
|
||||
|
||||
```liquid
|
||||
{% random "foo" bar obj.coo %}
|
||||
```
|
||||
|
||||
解析这么多种情况会很麻烦,但 LiquidJS 提供了 [Tokenizer][Tokenizer] 类来处理这种情况。
|
||||
|
||||
```javascript
|
||||
const { Liquid, Tokenizer, evalToken } = require('liquidjs')
|
||||
|
||||
engine.registerTag('random', {
|
||||
parse(tagToken) {
|
||||
const tokenizer = new Tokenizer(tagToken.args)
|
||||
this.items = []
|
||||
while (!tokenizer.end()) {
|
||||
// here readValue() returns a LiteralToken or PropertyAccessToken
|
||||
this.items.push(tokenizer.readValue())
|
||||
}
|
||||
},
|
||||
* render(context, emitter) {
|
||||
const index = Math.floor(this.items.length * Math.random())
|
||||
const token = this.items[index]
|
||||
// in LiquidJS, we use yield to wait for async call
|
||||
const value = yield evalToken(token, context)
|
||||
emitter.write(value)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
用上下文 `{ bar: "bar", obj: { coo: "coo" } }` 来调用这个标签可以得到上第一个例子一样的效果。见这个 JSFiddle:<http://jsfiddle.net/ctj364up/3/>.
|
||||
|
||||
{% note info 异步和 Promise %}
|
||||
在 LiquidJS 里异步用生成器实现,这样同样一份标签的实现也可以用于同步的 API 比如 `renderSync()`,`parseAndRenderSync()`,`renderFileSync()`。如果要在标签实现里等待 Promise,只需要把 `await somePromise` 换成 `yield somePromise`,并保留 `* render()` 不要改成 `async render()`。更多细节请参考 <a href="/tutorials/sync-and-async.html">Sync and Async</a>。
|
||||
{% endnote %}
|
||||
|
||||
## 把键值对解析为命名参数
|
||||
|
||||
当参数很多时或者有可选参数时,使用命名参数语法会很方便。这时参数由无序的键值对构成,LiquidJS 中的 [Hash][Hash] 类就是来处理这种情况的。
|
||||
|
||||
```liquid
|
||||
{% random from:2, to:max %}
|
||||
```
|
||||
|
||||
上面的例子用来产生 [2, max] 范围内的随机数。我们要用 `Hash` 来解析 `from` 和 `to` 参数。
|
||||
|
||||
```javascript
|
||||
const { Liquid, Hash } = require('liquidjs')
|
||||
|
||||
engine.registerTag('random', {
|
||||
parse(tagToken) {
|
||||
// 解析参数结果,存到 `this.args` 里
|
||||
this.args = new Hash(tagToken.args)
|
||||
},
|
||||
* render(context, emitter) {
|
||||
// 在当前 `context` 下计算参数的值
|
||||
const {from, to} = yield this.args.render(context)
|
||||
const length = to - from + 1
|
||||
const value = from + Math.floor(length * Math.random())
|
||||
emitter.write(value)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
在 `{ max: 10 }` 上下文上渲染 `{% random from:2, to:max %}` 将会得到 [2, 10] 范围内的随机数。见这个 JSFiddle:<http://jsfiddle.net/ctj364up/4/>。
|
||||
|
||||
[register-tags]: /tutorials/register-filters-tags.html
|
||||
[Tokenizer]: /api/classes/parser_tokenizer_.tokenizer.html
|
||||
[Hash]: /api/classes/template_tag_hash_.hash.html
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: 同步和异步
|
||||
---
|
||||
|
||||
LiquidJS 支持同步调用也支持异步调用,支持 Promise。为了同异步复用一套标签和过滤器,LiquidJS 标签用生成器来实现。
|
||||
|
||||
## 同异步 API
|
||||
|
||||
[Liquid][Liquid] 上主要的方法都支持同步和异步,下面这些方法返回 `Promise`:
|
||||
|
||||
- `render()`
|
||||
- `renderFile()`
|
||||
- `parseFile()`
|
||||
- `parseAndRender()`
|
||||
- `evalValue()`
|
||||
|
||||
它们的同步版本带一个 `Sync` 后缀:
|
||||
|
||||
- `renderSync()`
|
||||
- `renderFileSync()`
|
||||
- `parseFileSync()`
|
||||
- `parseAndRenderSync()`
|
||||
- `evalValueSync()`
|
||||
|
||||
## 如何实现兼容同步的标签
|
||||
|
||||
### 要求
|
||||
|
||||
所有内置标签都兼容同步,可以安全地用于同步或异步 API。为了让你的自定义标签页支持同步,你的标签不能返回 `Promise`,这意味着你的 `render(context, emitter)` 函数:
|
||||
|
||||
- 不能直接 `return <Promise>`,
|
||||
- 也不能声明为 `async`。
|
||||
|
||||
{% note info 不兼容同步的标签 %}
|
||||
不兼容同步的标签也仍然是合法标签,在异步 API 下也会正常运行。被同步调用时,返回 <code>Promise</code> 的标签会被渲染成 <code>[object Promise]</code>。
|
||||
{% endnote %}
|
||||
|
||||
### 等待 Promise
|
||||
但 LiquidJS 是支持 `Promise` 的,你仍然可以调用返回 `Promise` 的方法并等它 resolve。只需要把 `await` 换成 `yield` 并保留 `* render()` 不要改成 `async render()`。例如:
|
||||
|
||||
```typescript
|
||||
import { TagToken, Context, Emitter, TopLevelToken } from 'liquidjs'
|
||||
|
||||
// Usage: {% upper "alice" %}
|
||||
// Output: ALICE
|
||||
engine.registerTag('upper', {
|
||||
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
||||
this.str = tagToken.args
|
||||
},
|
||||
* render: function(ctx: Context) {
|
||||
// 同步调用时 `ctx.sync == true`,`_evalValue()` 会同步地执行
|
||||
var str = yield this.liquid._evalValue(this.str, ctx)
|
||||
return str.toUpperCase()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
见这个 JSFiddle:<http://jsfiddle.net/ctj364up/6/>。
|
||||
|
||||
## 只支持异步的标签
|
||||
|
||||
对于只用于异步 API 的标签,或者只能实现为异步的标签,使用生成器语法和 async 语法并没有区别。
|
||||
|
||||
例如,如果上面的 `this.liquid._evalValue()` 不会检查 `ctx.sync` 而且总是返回一个 `Promise`,那么即使这个标签用 `* render()` 和 `yield this.liquid._evalValue()` 实现,最终也会渲染成 `<object Promise>`。
|
||||
|
||||
这时可以直接使用 async 语法。注意有些 LiquidJS API 会返回 `Promise`,有些会返回生成器。你需要用 [toPromise][toPromise] API 来把生成器转换为 `Promise`,比如:
|
||||
|
||||
```typescript
|
||||
import { TagToken, Context, Emitter, TopLevelToken, toPromise } 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()
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
见这个 JSFiddle:<http://jsfiddle.net/ctj364up/5/>。
|
||||
|
||||
[Liquid]: /api/classes/liquid_.liquid.html
|
||||
[toPromise]: /api/modules/liquid_.html#toPromise
|
||||
Reference in New Issue
Block a user