diff --git a/docs/source/tutorials/render-tag-content.md b/docs/source/tutorials/render-tag-content.md
index 33195b090..cf655207f 100644
--- a/docs/source/tutorials/render-tag-content.md
+++ b/docs/source/tutorials/render-tag-content.md
@@ -22,7 +22,7 @@ Expected output:
```
-Firstly, [register][register-tags] a tag named `wrap` and parse the content into `this.tpls`. Here in `parse(tagToken, remainTokens)`:
+Firstly, [register][register-tags] a tag named `wrap` and parse the content into `this.tpls`. In the tag `constructor(tagToken, remainTokens, liquid)`:
- `tagToken` is current token `{%raw%}{% wrap %}{%endraw%}`, and
- `remainTokens` is an array of all tokens following `{%raw%}{% wrap %}{%endraw%}` until the end of this template file.
@@ -30,11 +30,14 @@ Firstly, [register][register-tags] a tag named `wrap` and parse the content into
Basically, what we need to do is take/`.shift()` enough tags from `remainTokens` until we get an `endwrap` token (the name can be arbitrary, but by convention it should be `endwrap`). And if there's no `endwrap` until the end of the template file, we need to throw a tag-not-closed `Error`.
```javascript
-engine.registerTag('wrap', {
- parse(tagToken, remainTokens) {
- this.tpls = []
+const { Tag } = require('liquidjs')
+
+engine.registerTag('wrap', class WrapTag extends Tag {
+ tpls = []
+ constructor(tagToken, remainTokens, liquid) {
+ super(tagToken, remainTokens, liquid)
let closed = false
- while(remainTokens.length) {
+ while (remainTokens.length) {
let token = remainTokens.shift()
// we got the end tag! stop taking tokens
if (token.name === 'endwrap') {
@@ -44,11 +47,11 @@ engine.registerTag('wrap', {
// parse token into template
// parseToken() may consume more than 1 tokens
// e.g. {% if %}...{% endif %}
- let tpl = this.liquid.parser.parseToken(token, remainTokens)
+ let tpl = liquid.parser.parseToken(token, remainTokens)
this.tpls.push(tpl)
}
if (!closed) throw new Error(`tag ${tagToken.getText()} not closed`)
- },
+ }
* render(context, emitter) {
emitter.write("
")
yield this.liquid.renderer.renderTemplates(this.tpls, context, emitter)
@@ -57,16 +60,17 @@ engine.registerTag('wrap', {
})
```
-`.renderTemplates()` can be async; we need `yield` to wait for it to complete. For more details on async in LiquidJS, see [Sync and Async][async]. Other parts of the `render()` method are quite straightforward. Here's a JSFiddle version:
+`.renderTemplates()` can be async; we need `yield` to wait for it to complete. For more details on async in LiquidJS, see [Sync and Async][async]. Here's a JSFiddle version:
## Using ParseStream
-When it comes to complex tags like [for][for] and [if][if], the `parse()` can be very complicated. There's a [ParseStream][ParseStream] utility to organize the `parse()` in event-based style. Following is a re-written `parse()` using `ParseStream` that does exactly the same as the example above.
+For more complex tags such as [for][for] and [if][if], constructor parsing can get unwieldy. [ParseStream][ParseStream] offers an event-based API for this. The constructor below is equivalent to the example above:
```javascript
-parse(tagToken, remainTokens) {
- this.tpls = []
- this.liquid.parser.parseStream(remainTokens)
+tpls = []
+constructor(tagToken, remainTokens, liquid) {
+ super(tagToken, remainTokens, liquid)
+ liquid.parser.parseStream(remainTokens)
.on('template', tpl => this.tpls.push(tpl))
// note that we cannot use arrow function because we need `this`
.on('tag:endwrap', function () { this.stop() })
@@ -103,15 +107,18 @@ As you've noticed, there's an additional `repeat.i` in the context of `repeat`.
Each time we enter a new *Context*, we need to push a new *Scope*. And when we finish rendering and exit the *Context*, we pop the *Scope* from the *Context*. As you can see in the following implementation:
```javascript
-engine.registerTag('repeat', {
- parse(tagToken, remainTokens) {
- this.tpls = []
- this.liquid.parser.parseStream(remainTokens)
+const { Tag } = require('liquidjs')
+
+engine.registerTag('repeat', class RepeatTag extends Tag {
+ tpls = []
+ constructor(tagToken, remainTokens, liquid) {
+ super(tagToken, remainTokens, liquid)
+ liquid.parser.parseStream(remainTokens)
.on('template', tpl => this.tpls.push(tpl))
.on('tag:endrepeat', function () { this.stop() })
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
.start()
- },
+ }
* render(context, emitter) {
const repeat = { i: 1 }
context.push({ repeat })
@@ -123,7 +130,7 @@ engine.registerTag('repeat', {
})
```
-The `parse()` is exactly the same as `wrap` tag, we repeat the content simply by calling `.renderTemplates(this.tpls)` twice during `render()`. Here's the JSFiddle:
+The constructor is the same as in the `wrap` tag; we repeat the content by calling `.renderTemplates(this.tpls)` twice during `render()`. Here's the JSFiddle:
{% note warn Use Push & Pop in Pairs %}
`context.push()` and `context.pop()` have to be used in pairs. Failing to `pop()` the *Scope* you pushed will leak the *Scope* to latter templates and may corrupt the *Context* stack.
diff --git a/docs/source/tutorials/security-model.md b/docs/source/tutorials/security-model.md
index 9555742fb..87098b347 100644
--- a/docs/source/tutorials/security-model.md
+++ b/docs/source/tutorials/security-model.md
@@ -12,10 +12,6 @@ The built-in limits are cooperative safeguards, not strict runtime isolation.
- They do **not** sandbox JavaScript execution.
- They should be combined with process/container limits and request timeouts for defense in depth.
-LiquidJS does **not** enforce memory or CPU budgets inside the engine. Major template engines take the same approach: byte-level heap tracking is unreliable in garbage-collected runtimes (non-deterministic GC, accounting overhead) and does not map cleanly to real process memory. [Jinja2](https://jinja.palletsprojects.com/en/stable/sandbox/) relies on `sys.setrecursionlimit`, `SandboxedEnvironment` for access control, and advises OS/process limits (`ulimit`, cgroups). [Twig](https://twig.symfony.com/doc/3.x/api.html#security-policy) documents a `SecurityPolicy` for tags/filters/methods and explicitly leaves resource limits to PHP (`memory_limit`, execution timeouts). Handlebars and EJS provide no render budgets; Node.js users typically combine [`vm.Script` timeouts](https://nodejs.org/api/vm.html) or [`worker_threads`](https://nodejs.org/api/worker_threads.html) with process isolation for untrusted templates.
-
-For LiquidJS in production, prefer **external** controls: Node.js `vm` or worker threads (or packages such as [`isolated-vm`](https://www.npmjs.com/package/isolated-vm) when stronger isolation is required), separate processes or containers, OS/container memory and CPU quotas, and request timeouts — not in-engine heap tracking.
-
## Limits at a glance
- [parseLimit][parseLimit]: limit total template size per `parse()` call.
@@ -53,7 +49,7 @@ Each template node (the `for` tag, literal `order: `, output `{{i}}`, and so on)
[maxDepth][maxDepth] limits how deeply `{% render %}`, `{% include %}`, and `{% layout %}` can nest. Defaults to `128`.
-Memory-heavy templates (for example exponential `concat` in a loop) are not capped by LiquidJS. Mitigate them with process/container memory limits, output size checks after render, or template restrictions — the same pattern Jinja2 and Twig recommend for heap and CPU.
+The `memoryLimit` option was removed; memory usage is not capped in-engine.
## `ownPropertyOnly` and scope data