docs: website for LiquidJS

This commit is contained in:
harttle
2020-03-28 17:21:07 +08:00
parent 0633dbeabb
commit 3e42d6b1b0
422 changed files with 14509 additions and 50335 deletions
+55
View File
@@ -0,0 +1,55 @@
---
title: Caching
---
In a typical website project, we'll have a directory of view templates and they'll be rendered multiple times. In production environment the template files are not likely to be changed over time (other than re-deployments). Thus it makes sense to cache the file contents and the parsed templates (in a kind of AST) to improve performance.
LiquidJS provides multiple ways to cache the parsed templates to improve performance.
## Programmaticly
The [.parse()][parse], [.parseFile()][parseFile], [.parseFileSync()][parseFileSync] APIs are used to parse templates from string or files. The result template can be then rendered multiple times with different context.
Parse from string:
```javascript
var tpl = engine.parse('{{name | capitalize}}');
engine.renderSync(tpl, {name: 'alice'}) // 'Alice'
engine.renderSync(tpl, {name: 'bob'}) // 'Bob'
```
Parse from file:
```javascript
var tpl = engine.parseFileSync('hello'); // contents of `hello.liquid`: {{name}}
engine.renderSync(tpl, {name: 'alice'}) // 'Alice'
engine.renderSync(tpl, {name: 'bob'}) // 'Bob'
```
The template string/file is parsed only once and renderd multiple times using different context. Templates for different files can be stored into a `Map` and can be retrieved directly for subsequent renders.
## The `cache` Option
The [cache option][cache] can be set to instruct liquidjs to use cached parsed templates each time you call [renderFile][renderFile] or [renderFileSync][renderFileSync].
```javascript
var { Liquid } = require('liquidjs');
var engine = new Liquid({
cache: true
});
// liquidjs parses the hello.liquid, then renders it with {name: 'alice'}
engine.renderFileSync('hello', {name: 'alice'})
// liquidjs finds the cached template, then renders it with {name: 'bob'}
engine.renderFileSync('hello', {name: 'bob'})
```
[parse]: ../api/classes/liquid_.liquid.html#parse
[cache]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-cache
[parseFile]: ../api/classes/liquid_.liquid.html#parseFile
[parseFileSync]: ../api/classes/liquid_.liquid.html#parseFileSync
[renderFile]: ../api/classes/liquid_.liquid.html#renderFile
[renderFileSync]: ../api/classes/liquid_.liquid.html#renderFilesync
@@ -0,0 +1,39 @@
---
title: Contribution Guideline
---
## Show Me Your Code
**Code Style**: LiquidJS applies [standard](https://github.com/standard/eslint-config-standard) and [@typescript-eslint/recommended](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/src/configs/recommended.json) rules, make sure it's still valid before commit:
```bash
npm run lint
```
**Testing**: Make sure test cases pass with your patch merged:
```bash
npm test
```
**Commit Message**: Please align to [the Angular Commit Message Guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits), especially note the [type identifier](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#type), on which semantic-release bot depends.
## Star on Github 👉 [![harttle/liquidjs](https://img.shields.io/github/stars/harttle/liquidjs?style=flat-square)][liquidjs]
This is the easiest way to support us: boost its rank and expose it to more people, which in turn makes it better.
## Financial Support
LiquidJS is Open Source and Free and **without** capitalists support and **without** any ADs. To help it live and thrive, consider contribute on [Open Collective][oc] or [Patreon][pt]. To acknowledge your contribution, your name and avatar will be listed here and on [Github README][liquidjs].
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/tiers/backer.svg?avatarHeight=72"></object>
[![Become a Patron!](../icon/[email protected])](https://www.patreon.com/bePatron?u=32321060)
[oc]: https://opencollective.com/liquidjs/
[pt]: https://www.patreon.com/harttle
[shopify/liquid]: https://shopify.github.io/liquid/
[caniuse-promises]: http://caniuse.com/#feat=promises
[pp]: https://github.com/taylorhakes/promise-polyfill
[tutorial]: https://shopify.github.io/liquid/basics/introduction/
[liquidjs]: https://github.com/harttle/liquidjs
+29
View File
@@ -0,0 +1,29 @@
---
title: Migrate to LiquidJS 9
---
LiquidJS 9 has some fundamental improvements, including bugfixes, new features and performance improvement due to higher target(see #137). There're also some breaking changes.
## Features
* Sync rendering: renderSync, parseAndRenderSync, renderFileSync
* New utils: Expression
## Fixes
* Rewrite boolean expression evaluation order, [#130](https://github.com/harttle/liquidjs/issues/130);
* `break` and `continue` tags omitting output before them, [#123](https://github.com/harttle/liquidjs/issues/123);
* Fixes errors in React.js demo during yarn install, [#145](https://github.com/harttle/liquidjs/issues/145);
* Promise typed Drops are not await-ed some times.
## Performance
* Performance Improvements due to targeting to Node.js 8, see [#137](https://github.com/harttle/liquidjs/issues/137);
* Memory footprint is reduced by 57.5%, see [#202](https://github.com/harttle/liquidjs/pull/202);
* Render performance is improved by 100.3%, see [#205](https://github.com/harttle/liquidjs/pull/205).
## BREAKING CHANGES
* LiquidJS no longer has a default export, use `import {Liquid} from 'liquidjs'` instead. The `window.Liquid` for the UMD bundle is also changed to `window.liquidjs.Liquid`;
* The duplicate static method `Liquid.evalValue` is removed, use the instance method `liquid.evalValue` instead;
* Shipped to Node.js 8, the CJS bundle (main entry in Node.js) nolonger supports Node.js &leq; 6. ESM (dist/liquid.esm.js) and UMD (dist/liquid.js, dist/liquid.min.js) bundles are not affected.
+21
View File
@@ -0,0 +1,21 @@
---
title: Operators
---
LiquidJS operators are very simple and different. There're 2 types of operators supported:
* Comparison operators: `==`, `!=`, `>`, `<`, `>=`, `<=`
* Logic operators: `or`, `and`, `contains`
Thus numerical operators are not supported and you cannot even plus two numbers like this `{% raw %}{{a + b}}{% endraw %}`, instead we need a filter `{% raw %}{{ a | plus: b}}{% endraw %}`. Actually `+` is a valid variable name in LiquidJS.
## Precedence
1. Comparison operators. All comparison operations have the same precedence and higher than logic operators.
2. Logic operators. All logic operators have the same precedence.
## Associativity
Logic operators are evaluated from right to left, see [shopify docs][operator-order].
[operator-order]: https://help.shopify.com/en/themes/liquid/basics/operators#order-of-operations
+79
View File
@@ -0,0 +1,79 @@
---
title: Overview
---
LiquidJS is a simple, expressive, safe and shopify compatible template engine in pure JavaScript. The purpose of this repo is to provide a standard Liquid implementation for the JavaScript community.
## LiquidJS in Node.js
Install via npm:
```bash
npm install --save liquidjs
```
```javascript
var { Liquid } = require('liquidjs');
var engine = new Liquid();
engine
.parseAndRender('{{name | capitalize}}', {name: 'alice'})
.then(console.log); // outputs 'Alice'
```
{% note info Working Demo %} Here's a working demo for LiquidJS usage in Node.js: <a href="https://github.com/harttle/liquidjs/blob/master/demo/nodejs/" target="_blank">liquidjs/demo/nodejs/</a>.{% endnote %}
Type definitions for LiquidJS are also exported and published, which makes it more enjoyable for TypeScript projects:
```typescript
import { Liquid } from 'liquidjs';
const engine = new Liquid();
engine
.parseAndRender('{{name | capitalize}}', {name: 'alice'})
.then(console.log); // outputs 'Alice'
```
{% note info Working Demo %} Here's a working demo for LiquidJS usage in TypeScript: <a href="https://github.com/harttle/liquidjs/blob/master/demo/typescript/" target="_blank">liquidjs/demo/typescript/</a>.{% endnote %}
## LiquidJS in Browsers
Pre-built UMD bundles are also available and included in the npm package:
```html
<!--for production-->
<script src="//unpkg.com/liquidjs/dist/liquid.min.js"></script>
<!--for development-->
<script src="//unpkg.com/liquidjs/dist/liquid.js"></script>
```
Or from jsDelivr CDN:
```html
<!--for production-->
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.min.js"></script>
<!--for development-->
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.js"></script>
```
{% note info Working Demo %} Here's a living demo on jsFiddle: <a href="https://jsfiddle.net/x43eb0z6/" target="_blank">jsfiddle.net/x43eb0z6</a>, and the source code is also available in <a href="https://github.com/harttle/liquidjs/blob/master/demo/browser/" target="_blank">liquidjs/demo/browser/</a>.{% endnote %}
{% note warn Compatibility %} You may need a <a href="https://github.com/taylorhakes/promise-polyfill" target="_blank">Promise polyfill</a> for legacy browsers like IE and Android UC, see <a href="http://caniuse.com/#feat=promises" target="_blank">caniuse statistics</a>. {% endnote %}
## LiquidJS in CLI
LiquidJS is also available from CLI:
```bash
echo '{{"hello" | capitalize}}' | npx liquidjs
```
If you pass a path to a JSON file or a JSON string as the first argument, it will be used as the context for your template.
```bash
echo 'Hello, {{ name }}.' | npx liquidjs '{"name": "Snake"}'
```
## Miscellaneous
A ReactJS demo is also added by [@stevenanthonyrevo](https://github.com/stevenanthonyrevo), see [liquidjs/demo/reactjs/](https://github.com/harttle/liquidjs/blob/master/demo/reactjs/).
@@ -0,0 +1,53 @@
---
title: Partials and Layouts
---
## Render Partials
For the following template files:
```
// file: color.liquid
color: '{{ color }}' shape: '{{ shape }}'
// file: theme.liquid
{% assign shape = 'circle' %}
{% render 'color' %}
{% render 'color' with 'red' %}
{% render 'color', color: 'yellow', shape: 'square' %}
```
The output will be:
```
color: '' shape: 'circle'
color: 'red' shape: 'circle'
color: 'yellow' shape: 'square'
```
More details please refer to the [render](../tags/render.html) tag.
## Layout Templates (Extends)
For the following template files:
```
// file: default-layout.liquid
Header
{% block content %}My default content{% endblock %}
Footer
// file: page.liquid
{% layout "default-layout" %}
{% block content %}My page content{% endblock %}
```
The output of `page.liquid`:
```
Header
My page content
Footer
```
More details please refer to the [layout](../tags/layout.html) tag.
+47
View File
@@ -0,0 +1,47 @@
---
title: Plugins
---
A number of tags and filters can be encapsulated into a **plugin**, which will be typically installed via npm. This article provides information about how to create and use a plugin.
## Write a Plugin
A liquidjs plugin is simple function which takes the [Liquid class][liquid] as the first parameter and the Liquid instance for `this`. We can call liquidjs APIs on `this` to make certain changes, especially [register filters and tags][register].
Now we'll make a plugin to upper case every letter of the input, save the following snippet to `upup.js`:
```javascript
/**
* Inside the plugin function, `this` refers to the Liquid instance.
*
* @param Liquid: provides facilities to implement tags and filters.
*/
module.exports = function (Liquid) {
this.registerFilter('upup', x => x.toUpperCase());
}
```
## Use a Plugin
Simply pass the plugin function into the `.plugin()` method:
```javascript
const engine = new Liquid()
engine.plugin(require('./upup.js'));
engine
.parseAndRender('{{ "foo" | upup }}')
.then(console.log) // outputs "FOO"
```
## Plugin List
Since this library excludes certain features that are available on the Shopify platform but not on the [Shopify/liquid](https://github.com/Shopify/liquid/) repo, see <https://github.com/harttle/liquidjs#differences-and-limitations>.
Here's a list of plugins that backfill those features. Feel free to add yours, this file is publicly editable.
* Sections Tags (WIP): https://github.com/harttle/liquidjs-section-tags
* Color Filters: https://github.com/harttle/liquidjs-color-filters
[liquid]: ../api/classes/liquid_.liquid.html
[register]: /harttle/liquidjs/wiki/Register-Filters-Tags
@@ -0,0 +1,39 @@
---
title: Register Filters/Tags
---
## Register Tags
```javascript
// Usage: {% upper name%}
engine.registerTag('upper', {
parse: function(tagToken, remainTokens) {
this.str = tagToken.args; // name
},
render: async function(scope, hash) {
var str = await this.liquid.evalValue(this.str, scope); // 'alice'
return str.toUpperCase() // 'Alice'
}
});
```
* `parse`: Read tokens from `remainTokens` until your end token.
* `render`: Combine scope data with your parsed tokens into HTML string.
See existing tag implementations here: <https://github.com/harttle/liquidjs/tree/master/src/builtin/tags>
## Register Filters
```javascript
// Usage: {{ name | upper }}
engine.registerFilter('upper', v => v.toUpperCase())
```
Filter arguments will be passed to the registered filter function, for example:
```javascript
// Usage: {{ 1 | add: 2, 3 }}
engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2)
```
See existing filter implementations here: <https://github.com/harttle/liquidjs/tree/master/src/builtin/filters>
+108
View File
@@ -0,0 +1,108 @@
---
title: Render Files
---
For a typical project there could be a directory of template files, you'll need to set the [template root][root] and call [renderFile][renderFile] or [renderFileSync][renderFileSync] to render a specific file.
## Render a File
For example you have a directory of templates like this:
```
.
├── index.js
└── views/
├── hello.liquid
└── world.liquid
```
`hello.liquid` contains a single line {%raw%}`name: {{name}}`{%endraw%}.
Now save the following contents into `index.js`:
```javascript
var engine = new Liquid({
root: path.resolve(__dirname, 'views/'), // root for layouts/includes lookup
extname: '.liquid' // used for layouts/includes, defaults ""
});
engine
.renderFile("hello", {name: 'alice'}) // will read and render `views/hello.liquid`
.then(console.log) // outputs "Alice"
```
Run `node index.js` and you'll get output like this:
```
> node index.js
name: alice
```
## Template Lookup
Template files names passed to [renderFile][renderFile], [parseFile][parseFile], [renderFileSync][renderFileSync], [parseFileSync][parseFileSync] APIs,
and [include][include], [layout][layout] tags are resolved against [the root option][root].
It can be a string-typed path (see above example), or a list of root directories, in which case templates will be looked up in that order. e.g.
```javascript
var engine = new Liquid({
root: ['views/', 'views/partials/'],
extname: '.liquid'
});
```
{% note tip Relative Paths %}Relative paths in <code>root</code> will be resolved against <code>cwd()</code>.{% endnote %}
When `{% raw %}{% render "foo" %}{% endraw %}` is renderd or `liquid.renderFile('foo')` is called, the following files will be looked up and the first existing file will be used:
- `cwd()`/views/foo.liquid
- `cwd()`/views/partials/foo.liquid
If none of the above files exists, an `ENOENT` error will be throwed. Here's a demo for Node.js: [demo/nodejs](https://github.com/harttle/liquidjs/tree/master/demo/nodejs).
When LiquidJS is used in browser, say current location is <https://example.com/bar/index.html>, only the first `root` will be used and the file to be fetched is:
- <https://example.com/bar/foo.liquid>
If fetch fails, a 404/500 error or network failures for example, an `ENOENT` error will be throwed.
Here's a demo for browsers: [demo/browser](https://github.com/harttle/liquidjs/tree/master/demo/browser).
## Abstract File System
LiquidJS defines an abstract file system interface in [src/fs/ifs.ts][ifs] and the default implementation is [src/fs/node.ts][fs-node] for Node.js and [src/fs/browser.ts][fs-browser] for the browser bundle.
The `Liquid` constructor provides a [fs][fs] option to specify the file system implementation. It's supposed to be used to define customized template fetching logic, i.e. fetch template from a database table, like:
```javascript
var engine = new Liquid({
fs: {
readFileSync (file) {
return db.model('Template').findByIdSync(file).text
},
await readFile (file) {
const template = await db.model('Template').findById(file)
return template.text
},
existsSync () {
return true
},
await exists () {
return true
},
resolve(root, file, ext) {
return file
}
}
});
```
[fs]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-fs
[ifs]: https://github.com/harttle/liquidjs/blob/master/src/fs/ifs.ts
[fs-node]: https://github.com/harttle/liquidjs/blob/master/src/fs/node.ts
[fs-browser]: https://github.com/harttle/liquidjs/blob/master/src/fs/browser.ts
[layout]: https://help.shopify.com/en/themes/liquid/tags/theme-tags#layout
[include]: https://help.shopify.com/themes/liquid/tags/theme-tags#include
[renderFile]: ../api/classes/liquid_.liquid.html#renderFile
[renderFileSync]: ../api/classes/liquid_.liquid.html#renderFilesync
[parseFile]: ../api/classes/liquid_.liquid.html#parseFile
[parseFileSync]: ../api/classes/liquid_.liquid.html#parseFileSync
[root]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-root
+52
View File
@@ -0,0 +1,52 @@
---
title: Basic Syntax
---
LiquidJS syntax is relatively simple. There're 2 types of markups in LiquidJS:
- **Tags**. A tag consists of a tag name and optional arguments wrapped between `{%raw%}{%{%endraw%}` and `%}`.
- **Outputs**. An output consists of a value and a list of filters, which is optional, wrapped between `{%raw%}{{{%endraw%}` and `}}`.
## Outputs
**Outputs** are used to output variables, which can be transformed by filters, into HTML. The following template will insert the value of `username` into the input's value:
```liquid
<input type="text" name="user" value="{{username}}">
```
Values in output can be transformed by **filter**s before output. To append a string after the variable:
```liquid
{{ username | append: ", welcome to LiquidJS!" }}
```
Filters can be chained:
```liquid
{{ username | append: ", welcome to LiquidJS!" | capitalize }}
```
A complete list of filters supported by LiquidJS can be found [here](../filters/overview.html).
## Tags
**Tags** are used to control the template rendering process, manipulating template variables, inter-op with other templates, etc. For example `assign` can be used to define a variable which can be later used in the template:
```liquid
{% assign foo = "FOO" %}
```
Typically tags appear in pairs with a start tag and a corresponding end tag. For example:
```liquid
{% if foo == "FOO" %}
Variable `foo` equals "FOO"
{% else %}
Variable `foo` not equals "FOO"
{% endif %}
```
A complete list of tags supported by LiquidJS can be found [here](../tags/overview.html).
[shopify/liquid]: https://github.com/Shopify/liquid
+27
View File
@@ -0,0 +1,27 @@
---
title: Truthy and Falsy
---
Though [Liquid][sl] is platform-independent, there're [certain differences][diff] with [the Ruby version][ruby], one of which is the `truthy` value.
## The Truth Table
According to [Shopify document](https://shopify.github.io/liquid/basics/truthy-and-falsy/) everything other than `false` and `nil` is truthy. But in JavaScript we have a totally different type system, we have types like `undefined` and we don't differentiate `integer` and `float`, thus things are slightly different:
value | truthy | falsy
--- | --- | ---
`true` | ✔️ |
`false` | | ✔️
`null` | | ✔️
`undefined` | | ✔️
`string` | ✔️ |
`empty string` | ✔️ |
`0` | ✔️ |
`integer` | ✔️ |
`float` | ✔️ |
`array` | ✔️ |
`empty array` | ✔️ |
[ruby]: https://shopify.github.io/liquid
[sl]: https://www.npmjs.com/package/liquidjs
[diff]: https://github.com/harttle/liquidjs#differences-and-limitations
+70
View File
@@ -0,0 +1,70 @@
---
title: Use in Express.js
---
LiquidJS is compatible to the [express template engines](https://expressjs.com/en/resources/template-engines.html). You can set liquidjs instance to the [view engine][express-views] option:
```javascript
var { Liquid } = require('liquidjs');
var engine = new Liquid();
// register liquid engine
app.engine('liquid', engine.express());
app.set('views', './views'); // specify the views directory
app.set('view engine', 'liquid'); // set liquid to default
```
{% note info Working Demo %} Here's a working demo for LiquidJS usage in Express.js: <a href="https://github.com/harttle/liquidjs/blob/master/demo/express/" target="_blank">liquidjs/demo/express/</a>.{% endnote %}
## Template Lookup
The [root][root] option will continue to work as templates root, as you can see in [Render A Template File][render-a-file]. Additionally, the [`views`][express-views] option in express.js (as shown above) will also be respected. Say you have a template directory like:
```
.
├── views1/
│ └── hello.liquid
└── views2/
└── world.liquid
```
And you're setting template root for liquidjs to `views1` and expressjs to `views2`:
```javascript
var { Liquid } = require('liquidjs');
var engine = new Liquid({
root: './views1/'
});
app.engine('liquid', engine.express());
app.set('views', './views2'); // specify the views directory
app.set('view engine', 'liquid'); // set liquid to default
```
Both of `hello.liquid` and `world.liquid` can be resolved and rendered:
```javascript
res.render('hello')
res.render('world')
```
## Caching
Simply setting the [cache option][cache] to true will enable template caching, as explained in [Caching][Caching]. It's recommended to enable cache in production environment, which can be done by:
```javascript
var { Liquid } = require('liquidjs');
var engine = new Liquid({
cache: process.env.NODE_ENV === 'production'
});
```
[cache]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-cache
[express-views]: http://expressjs.com/en/guide/using-template-engines.html
[parseFile]: ../api/classes/liquid_.liquid.html#parseFile
[parseFileSync]: ../api/classes/liquid_.liquid.html#parseFileSync
[layout]: https://help.shopify.com/en/themes/liquid/tags/theme-tags#layout
[include]: https://help.shopify.com/themes/liquid/tags/theme-tags#include
[root]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-root
[render-a-file]: ./render-a-file.html
[Caching]: ./caching.html
@@ -0,0 +1,56 @@
---
title: Whitespace Control
---
To keep source code neat and indented, we're adding spaces to our templates. LiquidJS offers whitespace control capabilities to eliminate these unwanted whitespaces in output HTML.
## via Markups
By default, all tags and output markups lines will generate a NL (`\n`), and whitespaces if there's any indentation. For example:
```liquid
{% author = "harttle" %}
{{ author }}
```
Outputs (note the blank link):
```
harttle
```
We can include hyphens in your tag syntax (`{% raw %}{{-{% endraw %}`, `-}}`, `{% raw %}{%-{% endraw %}`, `-%}`) to strip whitespace from left or right. For example:
```liquid
{% assign author = "harttle" -%}
{{ author }}
```
Outputs:
```
harttle
```
In this case, the `-%}` strips the whitespace from the right side of the `assign` tag.
## via Options
Alternatively, LiquidJS provides these per engine options to enable whitespace control without sweeping changes of your templates:
* `trimTagLeft`
* `trimTagRight`
* `trimValueRight`
* `trimValueRight`
[LiquidJS][liquidjs] will **NOT** trim any whitespace by default, aka. above options all default to `false`. For details of these options, see the [options][options].
## Greedy Mode
In greedy mode (enabled by the [greedy option][greedy]), all consecutive whitespace chars (including `\n`) will be trimmed. Greedy mode is enabled by default to be compliant with [shopify/liquid][shopify/liquid].
[shopify/liquid]: https://github.com/Shopify/liquid
[liquidjs]: https://github.com/harttle/liquidjs
[options]: ../api/interfaces/liquid_options_.liquidoptions.html
[greedy]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-greedy