mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 04:10:40 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
463a165e5e | ||
|
|
f88490cd3c | ||
|
|
e69a51025e | ||
|
|
ace5309c08 | ||
|
|
5a90c5bf74 | ||
|
|
f6e047867e | ||
|
|
c467630e0b | ||
|
|
b377dad9e3 | ||
|
|
ecc04c99c5 | ||
|
|
9e024ff2bc | ||
|
|
eec381ec72 | ||
|
|
c7216c6358 | ||
|
|
fe78890d35 | ||
|
|
d5771ba42c | ||
|
|
776d235fca | ||
|
|
a56af6bbfb | ||
|
|
1af8553651 | ||
|
|
049685b9a0 | ||
|
|
c503cb23df | ||
|
|
01014edc49 | ||
|
|
27e579e0e2 | ||
|
|
d3e3fecf53 | ||
|
|
33009bb988 | ||
|
|
aa955173d4 | ||
|
|
15b78ebd9d | ||
|
|
575f3e2338 | ||
|
|
7dfb620d30 | ||
|
|
21b78d9ba0 | ||
|
|
5d077f461a | ||
|
|
13343040c6 | ||
|
|
ca3240c2c4 | ||
|
|
2b0c5696bc | ||
|
|
dab8a29070 | ||
|
|
f395295721 | ||
|
|
200f6648aa | ||
|
|
31aa4ec318 | ||
|
|
231574903f | ||
|
|
dbc0497386 | ||
|
|
ab5e245fba | ||
|
|
78ce5662df | ||
|
|
8b880f65a2 | ||
|
|
d605158f96 | ||
|
|
1a44b908ce | ||
|
|
a0817e5dd7 | ||
|
|
703675e1e0 | ||
|
|
819d060a92 | ||
|
|
c3d7ecbde1 | ||
|
|
8bdb70878f | ||
|
|
2e1537d50c | ||
|
|
d4afbc038f | ||
|
|
58d03890b4 | ||
|
|
e3fb47a90b | ||
|
|
e4ad93ee8c | ||
|
|
0bdf6792d1 | ||
|
|
7e99efc513 | ||
|
|
527858fc14 | ||
|
|
9da41c8a37 | ||
|
|
2717dae127 | ||
|
|
8db6c7b28d | ||
|
|
298db97b84 | ||
|
|
bd8e2b0f96 | ||
|
|
117eaf57c3 | ||
|
|
1e69d86b0c | ||
|
|
cc6b66216d | ||
|
|
ca82416dea | ||
|
|
394d5a1ae2 | ||
|
|
371da3da74 | ||
|
|
7814285479 | ||
|
|
53824a2d00 | ||
|
|
9966d846b5 | ||
|
|
8c27a84059 | ||
|
|
388d0fbbc4 | ||
|
|
d71d0a0c20 | ||
|
|
191a0130d2 | ||
|
|
2c3ebaca0c | ||
|
|
7677f848c0 | ||
|
|
c7d2e1f1f4 | ||
|
|
c756191f49 | ||
|
|
e94e386c46 | ||
|
|
b7c782b10a | ||
|
|
49d3f64873 | ||
|
|
be3463c5f9 | ||
|
|
fde9924ee6 | ||
|
|
5b2ea63b14 | ||
|
|
23279a816a | ||
|
|
2c14a9541d | ||
|
|
c474649748 | ||
|
|
fad17cfbb3 | ||
|
|
6322acb88c | ||
|
|
95fc705b5d | ||
|
|
6801552fe6 | ||
|
|
6023d9ba10 | ||
|
|
cc4628d804 | ||
|
|
69286c6909 | ||
|
|
6c114267a5 | ||
|
|
e2ef236f68 | ||
|
|
80c2280ee6 | ||
|
|
44f6b520d5 | ||
|
|
3d71d9e35a | ||
|
|
124f4c4485 | ||
|
|
432d1c7ccb | ||
|
|
4b42e63601 | ||
|
|
f809c716eb | ||
|
|
aebeae9e1b | ||
|
|
564972ba74 | ||
|
|
d0c3cc8b47 | ||
|
|
b5b2a98d5e | ||
|
|
8894cbfe6e | ||
|
|
c58a116513 | ||
|
|
d4df3a0b53 | ||
|
|
9cfa43b8ae | ||
|
|
df6d62fd59 | ||
|
|
2438f4b018 | ||
|
|
8321a8e982 | ||
|
|
1de6adf308 | ||
|
|
a525f45b46 | ||
|
|
529e6d769b | ||
|
|
822ba0be0f | ||
|
|
a3455ebd0b | ||
|
|
24a19c092a | ||
|
|
fd5ef474c3 | ||
|
|
4f6b88c154 | ||
|
|
6dc7fada72 | ||
|
|
3b9c7d6fbe | ||
|
|
92bfc65e0b | ||
|
|
8348036d45 | ||
|
|
afeef1d745 | ||
|
|
3b5eb6664f | ||
|
|
68c4cfcfb6 | ||
|
|
dd325362f9 | ||
|
|
b9ae479b65 | ||
|
|
0cb520d2d1 | ||
|
|
1689576913 | ||
|
|
aea34418de | ||
|
|
4358c9630f | ||
|
|
9a96b4aba3 | ||
|
|
70fdb7af48 | ||
|
|
5099597634 | ||
|
|
d742de79ba | ||
|
|
f0e2e07f0b | ||
|
|
4e5dbe120d | ||
|
|
6a1db537d2 | ||
|
|
9517f82caf | ||
|
|
10e8c8ff7e | ||
|
|
fb787e8847 | ||
|
|
9012133e07 | ||
|
|
abaf4af465 | ||
|
|
8273c17dab | ||
|
|
24f5346084 | ||
|
|
6b9f872bcc | ||
|
|
91c721e650 | ||
|
|
500d397a5a | ||
|
|
2e004eddf6 | ||
|
|
e3bfe86181 | ||
|
|
804947ebb9 | ||
|
|
4fa5313acc | ||
|
|
f7313fe563 | ||
|
|
1c64ff051e | ||
|
|
c470d1fe9b | ||
|
|
cdceb25d00 | ||
|
|
cc9b28307c | ||
|
|
f2ddd6e459 | ||
|
|
ecc40a6012 | ||
|
|
659cfde8e1 | ||
|
|
0acfb8de4d | ||
|
|
c4b2bfb29b | ||
|
|
0d41f7467e | ||
|
|
1d35e3fcfb | ||
|
|
04c874b174 | ||
|
|
29f42847a5 | ||
|
|
426863a9b4 | ||
|
|
8f9639f1d6 | ||
|
|
3e130e01a7 | ||
|
|
3e3d84a5ed | ||
|
|
69f1a394b1 |
@@ -360,6 +360,141 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mems",
|
||||
"name": "Memmie Lenglet",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/729275?v=4",
|
||||
"profile": "https://memmie.lenglet.name",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ilhamdev0",
|
||||
"name": "ilhamdev0",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/57636145?v=4",
|
||||
"profile": "https://github.com/ilhamdev0",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "c412216887",
|
||||
"name": "一饮一啄皆是人生",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/29691650?v=4",
|
||||
"profile": "https://github.com/c412216887",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "labnol",
|
||||
"name": "Amit Agarwal",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1344071?v=4",
|
||||
"profile": "https://digitalinspiration.com/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "n1ru4l",
|
||||
"name": "Laurin Quast",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/14338007?v=4",
|
||||
"profile": "https://n1ru4l.cloud/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mattvague",
|
||||
"name": "Matt Vague",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/64985?v=4",
|
||||
"profile": "https://github.com/mattvague",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bglw",
|
||||
"name": "Liam Bigelow",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/40188355?v=4",
|
||||
"profile": "https://github.com/bglw",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "JaKXz",
|
||||
"name": "Jason Kurian",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2642545?v=4",
|
||||
"profile": "https://about.me/jasonkurian",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dphm",
|
||||
"name": "d pham (they/them)",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1707217?v=4",
|
||||
"profile": "https://github.com/dphm",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "AleksandrHovhannisyan",
|
||||
"name": "Aleksandr Hovhannisyan",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/19352442?v=4",
|
||||
"profile": "https://www.aleksandrhovhannisyan.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jg-rp",
|
||||
"name": "jg-rp",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/72664870?v=4",
|
||||
"profile": "https://github.com/jg-rp",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ameyaapte1",
|
||||
"name": "Ameya Apte",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/16054747?v=4",
|
||||
"profile": "https://github.com/ameyaapte1",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "tbdrz",
|
||||
"name": "tbdrz",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/50599116?v=4",
|
||||
"profile": "https://github.com/tbdrz",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "santialbo",
|
||||
"name": "Santi Albo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1557563?v=4",
|
||||
"profile": "http://santialbo.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "YahangWu",
|
||||
"name": "Yahang Wu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/12295975?v=4",
|
||||
"profile": "https://github.com/YahangWu",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Check
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
release:
|
||||
name: Check
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
- name: Test
|
||||
run: |
|
||||
npm run lint
|
||||
npm run test
|
||||
- name: Coverage
|
||||
uses: coverallsapp/[email protected]
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Performance
|
||||
run: |
|
||||
npm run perf:diff
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
docs:
|
||||
name: Docs
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build:docs
|
||||
- name: Publish
|
||||
uses: JamesIves/[email protected]
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: docs/public
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Release
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run build:dist
|
||||
- name: Test
|
||||
run: |
|
||||
npm run lint
|
||||
npm run test
|
||||
- name: Coverage
|
||||
uses: coverallsapp/[email protected]
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npx semantic-release
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
require: "ts-node/register/transpile-only",
|
||||
reporter: "spec"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"exclude": ["dist", "test"],
|
||||
"reporter": ["html", "lcov"]
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
- "8"
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
name: 'Linting'
|
||||
script:
|
||||
- npm run lint
|
||||
- name: 'Coverage'
|
||||
script: npm run coverage-coveralls
|
||||
- stage: release
|
||||
name: 'Publish'
|
||||
if: branch = master
|
||||
script: npm run build
|
||||
deploy:
|
||||
provider: script
|
||||
skip_cleanup: true
|
||||
script: npx semantic-release
|
||||
- name: 'Document'
|
||||
if: branch = master
|
||||
script: npm run build-docs
|
||||
deploy:
|
||||
skip_cleanup: true
|
||||
provider: pages
|
||||
github_token: $GH_TOKEN
|
||||
local_dir: docs/public
|
||||
+253
@@ -1,3 +1,256 @@
|
||||
# [9.37.0](https://github.com/harttle/liquidjs/compare/v9.36.2...v9.37.0) (2022-04-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* support integer arithmetic for `divided_by`, closes [#465](https://github.com/harttle/liquidjs/issues/465) ([e69a510](https://github.com/harttle/liquidjs/commit/e69a51025efa7dec7d60d0067200a1466988ebbc))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* automatic output escaping, closes [#500](https://github.com/harttle/liquidjs/issues/500) ([f88490c](https://github.com/harttle/liquidjs/commit/f88490cd3cd0b5316c43c3ca76837544d99ce0b0))
|
||||
|
||||
## [9.36.2](https://github.com/harttle/liquidjs/compare/v9.36.1...v9.36.2) (2022-04-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* lazy createRequire to allow exceptions being catched, fixes [#497](https://github.com/harttle/liquidjs/issues/497) ([b377dad](https://github.com/harttle/liquidjs/commit/b377dad9e34c3de3e0eedc1ee6f17a0e9d919669))
|
||||
|
||||
## [9.36.1](https://github.com/harttle/liquidjs/compare/v9.36.0...v9.36.1) (2022-04-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* contains operator does not support Drop, fixes [#492](https://github.com/harttle/liquidjs/issues/492) ([9e024ff](https://github.com/harttle/liquidjs/commit/9e024ff2bcf17e7ac19c718389d4cef39b8a51f7))
|
||||
* responsive header ([a56af6b](https://github.com/harttle/liquidjs/commit/a56af6bbfb5b698db55fbeb87e8e0872688fbe19))
|
||||
* use `createRequire` for ESM, fixes [#334](https://github.com/harttle/liquidjs/issues/334) ([eec381e](https://github.com/harttle/liquidjs/commit/eec381ec72db3858452799b7a3264e240be3044d))
|
||||
|
||||
# [9.36.0](https://github.com/harttle/liquidjs/compare/v9.35.2...v9.36.0) (2022-03-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Access array item by negative index, closes [#486](https://github.com/harttle/liquidjs/issues/486) ([049685b](https://github.com/harttle/liquidjs/commit/049685b9a0271ba03875e24ff2f6c7870cae62a7))
|
||||
* allow strip filter with specified char, closes [#390](https://github.com/harttle/liquidjs/issues/390) ([c503cb2](https://github.com/harttle/liquidjs/commit/c503cb23dfbdd6f146d6dea16a84eab3df1f7aa9))
|
||||
* appropriate error for malformed filters, fixes [#271](https://github.com/harttle/liquidjs/issues/271) ([01014ed](https://github.com/harttle/liquidjs/commit/01014edc491e12d38981045442da9faee598cdf7))
|
||||
|
||||
## [9.35.2](https://github.com/harttle/liquidjs/compare/v9.35.1...v9.35.2) (2022-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* corner case for concat filter without argument, [#481](https://github.com/harttle/liquidjs/issues/481) ([aa95517](https://github.com/harttle/liquidjs/commit/aa955173d4c7adc585e862934429f1f4c5f64969))
|
||||
* export all builtin tags from LiquidJS, [#464](https://github.com/harttle/liquidjs/issues/464) ([33009bb](https://github.com/harttle/liquidjs/commit/33009bb988eb74c58f390992750d91b967cb3428))
|
||||
|
||||
## [9.35.1](https://github.com/harttle/liquidjs/compare/v9.35.0...v9.35.1) (2022-02-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* some filters throw on nil input, see [#481](https://github.com/harttle/liquidjs/issues/481) ([7dfb620](https://github.com/harttle/liquidjs/commit/7dfb620d30f8818685e1cfb8e7492313a0d036ab))
|
||||
|
||||
# [9.35.0](https://github.com/harttle/liquidjs/compare/v9.34.1...v9.35.0) (2022-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `url_encode` throws on undefined value, fixes [#479](https://github.com/harttle/liquidjs/issues/479) ([ca3240c](https://github.com/harttle/liquidjs/commit/ca3240c2c4d157095d2ebe0024d0c71bc5e435f8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* expose all tags/filters and TimezoneDate, closes [#464](https://github.com/harttle/liquidjs/issues/464) ([dab8a29](https://github.com/harttle/liquidjs/commit/dab8a29070b2508f2e6532717b7663966f610bec))
|
||||
|
||||
## [9.34.1](https://github.com/harttle/liquidjs/compare/v9.34.0...v9.34.1) (2022-02-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* array output now join with "" instead of "," ([ab5e245](https://github.com/harttle/liquidjs/commit/ab5e245fba9a0f6936275319d4e5e25aadb0f7c1))
|
||||
* sort filter unexpectedly modifies original array, [#475](https://github.com/harttle/liquidjs/issues/475) ([dbc0497](https://github.com/harttle/liquidjs/commit/dbc049738633b1b6f578d9d20f830b548ba67a22))
|
||||
|
||||
# [9.34.0](https://github.com/harttle/liquidjs/compare/v9.33.1...v9.34.0) (2022-01-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* where-filter null handling ([#457](https://github.com/harttle/liquidjs/issues/457)) ([9da41c8](https://github.com/harttle/liquidjs/commit/9da41c8a37d9c49ee4a16d5bd520fd6ae01c14ec))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* `ownPropertyOnly` option to protect prototype, [#454](https://github.com/harttle/liquidjs/issues/454) ([7e99efc](https://github.com/harttle/liquidjs/commit/7e99efc5131e20cf3f59e1fc2c371a15aa4109db))
|
||||
|
||||
## [9.33.1](https://github.com/harttle/liquidjs/compare/v9.33.0...v9.33.1) (2022-01-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* liquidjs.version on npm package ([53824a2](https://github.com/harttle/liquidjs/commit/53824a2d00f978214fcd83a48118b7900cf97449))
|
||||
|
||||
# [9.33.0](https://github.com/harttle/liquidjs/compare/v9.32.1...v9.33.0) (2022-01-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support `offset:continue`, see [#439](https://github.com/harttle/liquidjs/issues/439) ([8c27a84](https://github.com/harttle/liquidjs/commit/8c27a84059384ae730eb0fa1524df04e122e27a0))
|
||||
* support Jekyll-like include syntax, see [#441](https://github.com/harttle/liquidjs/issues/441) ([388d0fb](https://github.com/harttle/liquidjs/commit/388d0fbbc42fe8cd69faba61c1dc29e9bb5ec2d0))
|
||||
|
||||
## [9.32.1](https://github.com/harttle/liquidjs/compare/v9.32.0...v9.32.1) (2022-01-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove limit on operator char length ([7677f84](https://github.com/harttle/liquidjs/commit/7677f848c0c57335dd1c4f1e3ec251fbf9b1663f))
|
||||
|
||||
# [9.32.0](https://github.com/harttle/liquidjs/compare/v9.31.0...v9.32.0) (2022-01-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support allow_false for `default` filter, see [#435](https://github.com/harttle/liquidjs/issues/435) ([c756191](https://github.com/harttle/liquidjs/commit/c756191f49f9c2b823048367abfdf0adf2bdb875))
|
||||
|
||||
# [9.31.0](https://github.com/harttle/liquidjs/compare/v9.30.0...v9.31.0) (2021-12-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* implement `liquid` and `echo` tags, see [#428](https://github.com/harttle/liquidjs/issues/428) ([fde9924](https://github.com/harttle/liquidjs/commit/fde9924ee622efae4c013d2aa01c6d705c8d5f46))
|
||||
|
||||
# [9.30.0](https://github.com/harttle/liquidjs/compare/v9.29.0...v9.30.0) (2021-12-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support jekyll-like include, see [#433](https://github.com/harttle/liquidjs/issues/433) ([23279a8](https://github.com/harttle/liquidjs/commit/23279a816a0582ade7f3b15c1c65c74bc147d134))
|
||||
|
||||
# [9.29.0](https://github.com/harttle/liquidjs/compare/v9.28.6...v9.29.0) (2021-12-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* customize globals & strictVariables when calling render, see [#432](https://github.com/harttle/liquidjs/issues/432) ([6801552](https://github.com/harttle/liquidjs/commit/6801552fe6829770cbbfdda051731c8b466ed9ec))
|
||||
|
||||
## [9.28.6](https://github.com/harttle/liquidjs/compare/v9.28.5...v9.28.6) (2021-12-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* size filter does not respect Objects, fixes [#385](https://github.com/harttle/liquidjs/issues/385) ([6c11426](https://github.com/harttle/liquidjs/commit/6c114267a526ef764dfd9bd94de199d2932ad91a))
|
||||
* throws when using `preserveTimezones` on Node.js, fixes [#431](https://github.com/harttle/liquidjs/issues/431) ([e2ef236](https://github.com/harttle/liquidjs/commit/e2ef236f68273b72a0b1293b0d13728cdb9aa4b8))
|
||||
|
||||
## [9.28.5](https://github.com/harttle/liquidjs/compare/v9.28.4...v9.28.5) (2021-11-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* always allow './' and '../' to be relative, even on windows ([44f6b52](https://github.com/harttle/liquidjs/commit/44f6b520d53ba984ecb5fc430d70f698837d1802))
|
||||
|
||||
## [9.28.4](https://github.com/harttle/liquidjs/compare/v9.28.3...v9.28.4) (2021-10-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow `{%render%}` to reassign argument, [#404](https://github.com/harttle/liquidjs/issues/404) ([124f4c4](https://github.com/harttle/liquidjs/commit/124f4c4485270a5fdfca610808a56ecd98d98417))
|
||||
|
||||
## [9.28.3](https://github.com/harttle/liquidjs/compare/v9.28.2...v9.28.3) (2021-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* relative root (by default) yields LookupError, fixes [#419](https://github.com/harttle/liquidjs/issues/419), [#424](https://github.com/harttle/liquidjs/issues/424), also related to [#395](https://github.com/harttle/liquidjs/issues/395) ([aebeae9](https://github.com/harttle/liquidjs/commit/aebeae9e1bbb8472af7788dfd09a08cb6de58e1c))
|
||||
|
||||
## [9.28.2](https://github.com/harttle/liquidjs/compare/v9.28.1...v9.28.2) (2021-10-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cache ongoing parseFile() calls, fixes [#416](https://github.com/harttle/liquidjs/issues/416) ([8894cbf](https://github.com/harttle/liquidjs/commit/8894cbfe6e0dbad4c07439adcefb6b3e2056be11))
|
||||
|
||||
## [9.28.1](https://github.com/harttle/liquidjs/compare/v9.28.0...v9.28.1) (2021-10-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* hardcoded '/' in normalized `options.fs`, fixes [#412](https://github.com/harttle/liquidjs/issues/412), [#408](https://github.com/harttle/liquidjs/issues/408) ([9cfa43b](https://github.com/harttle/liquidjs/commit/9cfa43b8aee6a980f39d99f1cdb2be730ca21731))
|
||||
|
||||
# [9.28.0](https://github.com/harttle/liquidjs/compare/v9.27.1...v9.28.0) (2021-10-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* skip root check for renderFile() ([822ba0b](https://github.com/harttle/liquidjs/commit/822ba0be0f1cfbedd50376aff8ac49eee71bd48c))
|
||||
* support timezoneOffset for date from scope, [#401](https://github.com/harttle/liquidjs/issues/401) ([fd5ef47](https://github.com/harttle/liquidjs/commit/fd5ef474c36212e6a2446012dcd26bca93f84c7b))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* `relativeReference` for render/include/layout, [#395](https://github.com/harttle/liquidjs/issues/395) ([a3455eb](https://github.com/harttle/liquidjs/commit/a3455ebd0b207141c34630c0af44d917db2ca1dd))
|
||||
* implement `forloop.name` as found in ruby shopify/liquid ([6dc7fad](https://github.com/harttle/liquidjs/commit/6dc7fada72467418806c1ee4bd7eaf3003690fe6))
|
||||
|
||||
## [9.27.1](https://github.com/harttle/liquidjs/compare/v9.27.0...v9.27.1) (2021-10-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* directory info in lookupError message, [#395](https://github.com/harttle/liquidjs/issues/395) ([92bfc65](https://github.com/harttle/liquidjs/commit/92bfc65e0b1d937c00a8368b272223c702132d23))
|
||||
|
||||
# [9.27.0](https://github.com/harttle/liquidjs/compare/v9.26.0...v9.27.0) (2021-10-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove "stream" dependency in browser bundles, [#396](https://github.com/harttle/liquidjs/issues/396) ([3b5eb66](https://github.com/harttle/liquidjs/commit/3b5eb6664f673c29d74cb7645e01dcbdf43c8343))
|
||||
* renderToNodeStream() now emit 'error' event instead of throw ([afeef1d](https://github.com/harttle/liquidjs/commit/afeef1d7450b2799b3441b0241d2466b892a27ff))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `layouts`, `partials` apart from `root`, [#395](https://github.com/harttle/liquidjs/issues/395) ([b9ae479](https://github.com/harttle/liquidjs/commit/b9ae479b653a34fadb98c324c4683dd1fdd31af1))
|
||||
* renderFileToNodeStream(filepath, scope) ([68c4cfc](https://github.com/harttle/liquidjs/commit/68c4cfcfb647c22225dd6edede53ad7a5d7c4485))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* make the most of streamed rendering ([aea3441](https://github.com/harttle/liquidjs/commit/aea34418de24cb85ea1acddf68c3683ce7fc9fa8))
|
||||
|
||||
# [9.26.0](https://github.com/harttle/liquidjs/compare/v9.25.1...v9.26.0) (2021-09-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* orderedFilterParameters, closes [#312](https://github.com/harttle/liquidjs/issues/312) ([10e8c8f](https://github.com/harttle/liquidjs/commit/10e8c8ff7e1cca6df43087953cd8daf4bd618563))
|
||||
* stream rendering, closed [#361](https://github.com/harttle/liquidjs/issues/361) fixes [#360](https://github.com/harttle/liquidjs/issues/360) ([9012133](https://github.com/harttle/liquidjs/commit/9012133e0717b1813c6a74a6a282f43ba14d0ada))
|
||||
* timezoneOffset option to specify output timezone, see [#375](https://github.com/harttle/liquidjs/issues/375) ([6b9f872](https://github.com/harttle/liquidjs/commit/6b9f872bccb4b0c636dc7be2088cafa9bc6c900a))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* improve performance by 4x by simplified parseFile ([24f5346](https://github.com/harttle/liquidjs/commit/24f534608489fccc155f30bbaf37397c46278da6))
|
||||
* parse filenames in parse() insteadof render() ([8273c17](https://github.com/harttle/liquidjs/commit/8273c17dab3dc09858330ce45e3617a650e7fcaa))
|
||||
|
||||
## [9.25.1](https://github.com/harttle/liquidjs/compare/v9.25.0...v9.25.1) (2021-06-20)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* add cross-engines benchmark ([cdceb25](https://github.com/harttle/liquidjs/commit/cdceb25d007b3d30a85e51ac538e12297c73bfcf))
|
||||
|
||||
# [9.25.0](https://github.com/harttle/liquidjs/compare/v9.24.2...v9.25.0) (2021-05-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* when tag with multiple values ([8f9639f](https://github.com/harttle/liquidjs/commit/8f9639f))
|
||||
|
||||
## [9.24.2](https://github.com/harttle/liquidjs/compare/v9.24.1...v9.24.2) (2021-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* operator boundary not correctly recognized, fixes [#342](https://github.com/harttle/liquidjs/issues/342) ([3e3d84a](https://github.com/harttle/liquidjs/commit/3e3d84a))
|
||||
|
||||
## [9.24.1](https://github.com/harttle/liquidjs/compare/v9.24.0...v9.24.1) (2021-05-01)
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
# liquidjs
|
||||
[](https://www.npmjs.org/package/liquidjs)
|
||||
[](https://www.npmjs.org/package/liquidjs)
|
||||
[](https://coveralls.io/github/harttle/liquidjs?branch=master)
|
||||
[](https://travis-ci.org/harttle/liquidjs)
|
||||
[](https://github.com/harttle/liquidjs/issues)
|
||||
[](https://github.com/harttle/liquidjs/graphs/contributors)
|
||||
[](https://opencollective.com/liquidjs)
|
||||
[](https://david-dm.org/harttle/liquidjs)
|
||||
[](https://david-dm.org/harttle/liquidjs?type=dev)
|
||||
[](https://github.com/harttle/liquidjs/blob/master/LICENSE)
|
||||
[](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits)
|
||||
[](https://github.com/harttle/liquidjs)
|
||||
[](https://www.npmjs.org/package/liquidjs)
|
||||
[](https://www.npmjs.org/package/liquidjs)
|
||||
[](https://coveralls.io/github/harttle/liquidjs?branch=master)
|
||||
[](https://github.com/harttle/liquidjs/actions/workflows/check.yml?query=branch%3Amaster)
|
||||
[](https://github.com/harttle/liquidjs/blob/master/LICENSE)
|
||||
[](https://github.com/harttle/liquidjs)
|
||||
|
||||
A simple, expressive, safe and [Shopify][shopify/liquid] compatible template engine in pure JavaScript.
|
||||
A simple, expressive and safe [Shopify][shopify/liquid] / Github Pages compatible template engine in pure JavaScript.
|
||||
**The purpose of this repo** is to provide a standard Liquid implementation for the JavaScript community so that [Jekyll sites](https://jekyllrb.com), [Github Pages](https://pages.github.com/) and [Shopify templates](https://themes.shopify.com/) can be ported to Node.js without pain.
|
||||
|
||||
* [Documentation][doc]
|
||||
@@ -21,12 +15,25 @@ A simple, expressive, safe and [Shopify][shopify/liquid] compatible template eng
|
||||
|
||||
<p align="center"><a href="https://liquidjs.com"><img height="155px" width="155px" src="https://liquidjs.com/icon/mstile-310x310.png" alt="logo"></a></p>
|
||||
|
||||
## What's it like?
|
||||
|
||||
Basically there're two types of Liquid syntax: tags enclosed by `{% %}` and outputs enclosed by `{{ }}`. A Liquid template looks like:
|
||||
|
||||
```liquid
|
||||
{% if username %}
|
||||
{{ username | append: ", welcome to LiquidJS!" | capitalize }}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
[A live demo](https://liquidjs.com/playground.html) is also available and here's a [quick tutorial](https://liquidjs.com/tutorials/intro-to-liquid.html) for Liquid syntax.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Install from npm in Node.js:
|
||||
|
||||
```bash
|
||||
npm install --save liquidjs
|
||||
npm install liquidjs
|
||||
```
|
||||
|
||||
Or use the UMD bundle from jsDelivr:
|
||||
@@ -44,12 +51,19 @@ More details, refer to [The Setup Guide][setup].
|
||||
* [react-liquid](https://github.com/aquibm/react-liquid#readme): Liquid templating language component for React
|
||||
* [@11ty/eleventy](https://www.npmjs.com/package/@11ty/eleventy): A simpler static site generator. An alternative to Jekyll. Written in JavaScript. Transforms a directory of templates (of varying types) into HTML.
|
||||
|
||||
## Backers
|
||||
## Financial Support
|
||||
|
||||
If you love LiquidJS or your company is using LiquidJS? Please consider [support us on Open Collective or Patreon][financial-support].
|
||||
|
||||

|
||||
### Backers
|
||||
If you personally like LiquidJS and find it's useful to you, you can become a backer!
|
||||
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/backers.svg?avatarHeight=72"></object>
|
||||
|
||||
### Sponsors
|
||||
If LiquidJS is benefiting your business/company, please sponsor us to make it better!
|
||||
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/sponsors.svg?avatarHeight=72"></object>
|
||||
## Contributors ✨
|
||||
|
||||
Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to these wonderful people:
|
||||
@@ -107,6 +121,25 @@ Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to
|
||||
<td align="center"><a href="https://github.com/Pixcell"><img src="https://avatars0.githubusercontent.com/u/4005291?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pixcell</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=Pixcell" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://jasonet.co"><img src="https://avatars.githubusercontent.com/u/10660468?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jason Etcovitch</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=JasonEtco" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/kayuapi"><img src="https://avatars.githubusercontent.com/u/10304328?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ZC</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=kayuapi" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://memmie.lenglet.name"><img src="https://avatars.githubusercontent.com/u/729275?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Memmie Lenglet</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=mems" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ilhamdev0"><img src="https://avatars.githubusercontent.com/u/57636145?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ilhamdev0</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=ilhamdev0" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/c412216887"><img src="https://avatars.githubusercontent.com/u/29691650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>一饮一啄皆是人生</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=c412216887" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://digitalinspiration.com/"><img src="https://avatars.githubusercontent.com/u/1344071?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Amit Agarwal</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=labnol" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://n1ru4l.cloud/"><img src="https://avatars.githubusercontent.com/u/14338007?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Laurin Quast</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=n1ru4l" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mattvague"><img src="https://avatars.githubusercontent.com/u/64985?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matt Vague</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=mattvague" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/bglw"><img src="https://avatars.githubusercontent.com/u/40188355?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Liam Bigelow</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=bglw" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://about.me/jasonkurian"><img src="https://avatars.githubusercontent.com/u/2642545?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jason Kurian</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=JaKXz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/dphm"><img src="https://avatars.githubusercontent.com/u/1707217?v=4?s=100" width="100px;" alt=""/><br /><sub><b>d pham (they/them)</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=dphm" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.aleksandrhovhannisyan.com/"><img src="https://avatars.githubusercontent.com/u/19352442?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aleksandr Hovhannisyan</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=AleksandrHovhannisyan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/jg-rp"><img src="https://avatars.githubusercontent.com/u/72664870?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jg-rp</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=jg-rp" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/ameyaapte1"><img src="https://avatars.githubusercontent.com/u/16054747?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ameya Apte</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=ameyaapte1" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/tbdrz"><img src="https://avatars.githubusercontent.com/u/50599116?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tbdrz</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=tbdrz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://santialbo.com"><img src="https://avatars.githubusercontent.com/u/1557563?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Santi Albo</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=santialbo" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/YahangWu"><img src="https://avatars.githubusercontent.com/u/12295975?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yahang Wu</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=YahangWu" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const Benchmark = require('benchmark')
|
||||
const data = require('./data/todolist.json')
|
||||
const path = require('path')
|
||||
|
||||
const engines = {
|
||||
handlebars: require('./engines/handlebars'),
|
||||
liquid: require('./engines/liquid'),
|
||||
react: require('./engines/react'),
|
||||
swig: require('./engines/swig')
|
||||
}
|
||||
|
||||
function crossEngines () {
|
||||
console.log(' cross engines')
|
||||
console.log('------------------------')
|
||||
return new Promise(resolve => {
|
||||
const suit = new Benchmark.Suite('cross engines')
|
||||
|
||||
for (const [name, { load, render }] of Object.entries(engines)) {
|
||||
const tpl = load(path.resolve(__dirname, `templates/todolist`))
|
||||
suit.add(name, () => render(tpl, data))
|
||||
}
|
||||
|
||||
suit.on('cycle', event => console.log(String(event.target)))
|
||||
suit.on('complete', resolve)
|
||||
suit.run({ async: true })
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { crossEngines }
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"categories": [{
|
||||
"color": "red",
|
||||
"id": "CAT1",
|
||||
"title": "work"
|
||||
}, {
|
||||
"color": "red",
|
||||
"id": "CAT2",
|
||||
"title": "emergency"
|
||||
}, {
|
||||
"color": "green",
|
||||
"id": "CAT3",
|
||||
"title": "sport"
|
||||
}],
|
||||
"todos": [{
|
||||
"title": "fork and clone",
|
||||
"id": "TODO1",
|
||||
"category": "work"
|
||||
}, {
|
||||
"title": "make it better",
|
||||
"id": "TODO2",
|
||||
"category": "sport"
|
||||
}, {
|
||||
"title": "make a pull request",
|
||||
"id": "TODO3",
|
||||
"category": "work"
|
||||
}]
|
||||
}
|
||||
Regular → Executable
+6
-18
@@ -1,8 +1,10 @@
|
||||
const Benchmark = require('benchmark')
|
||||
const { Liquid } = require('..')
|
||||
const ctx = require('./data/todolist.json')
|
||||
const { resolve } = require('path')
|
||||
|
||||
const engine = new Liquid({
|
||||
root: __dirname,
|
||||
root: resolve(__dirname, 'templates'),
|
||||
extname: '.liquid'
|
||||
})
|
||||
|
||||
@@ -17,28 +19,14 @@ engine.registerTag('header', {
|
||||
}
|
||||
})
|
||||
|
||||
const ctx = {
|
||||
todos: ['fork and clone', 'make it better', 'make a pull request'],
|
||||
title: 'Welcome to liquidjs!'
|
||||
}
|
||||
|
||||
const template = `
|
||||
{%header content: "welcome to liquid" | capitalize%}
|
||||
|
||||
<ul>
|
||||
{% for todo in todos %}
|
||||
<li>{{forloop.index}} - {{todo}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
`
|
||||
|
||||
function demo () {
|
||||
console.log('--- demo ---')
|
||||
console.log(' demo')
|
||||
console.log('------------------------')
|
||||
return new Promise(resolve => {
|
||||
new Benchmark.Suite('demo')
|
||||
.add('demo', {
|
||||
defer: true,
|
||||
fn: d => engine.parseAndRender(template, ctx).then(x => d.resolve(x))
|
||||
fn: d => engine.renderFile('todolist', ctx).then(x => d.resolve(x))
|
||||
})
|
||||
.on('cycle', event => console.log(String(event.target)))
|
||||
.on('complete', resolve)
|
||||
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env node
|
||||
const { performance } = require('perf_hooks')
|
||||
const path = require('path')
|
||||
|
||||
const FILE_LOCAL = process.argv[2]
|
||||
const FILE_LATEST = process.argv[3]
|
||||
console.log(`Local: ${FILE_LOCAL}`)
|
||||
console.log(`Latest: ${FILE_LATEST}`)
|
||||
|
||||
const { createEngine } = require('./engines/create-liquid')
|
||||
const local = createEngine(require(path.resolve(__dirname, '..', FILE_LOCAL)))
|
||||
const latest = createEngine(require(path.resolve(__dirname, '..', FILE_LOCAL)))
|
||||
const data = require('./data/todolist.json')
|
||||
const tpl = path.resolve(__dirname, `templates/todolist`)
|
||||
|
||||
const begin = performance.now()
|
||||
const tplLocal = local.load(tpl)
|
||||
const tplLatest = latest.load(tpl)
|
||||
const tasks = [
|
||||
{
|
||||
cycles: 0,
|
||||
time: 0,
|
||||
fn: () => latest.render(tplLatest, data)
|
||||
},
|
||||
{
|
||||
cycles: 0,
|
||||
time: 0,
|
||||
fn: () => local.render(tplLocal, data)
|
||||
}
|
||||
]
|
||||
while (performance.now() - begin < 20e3) {
|
||||
const task = tasks[Math.floor(Math.random() * 2)]
|
||||
task.time -= performance.now()
|
||||
task.fn()
|
||||
task.time += performance.now()
|
||||
task.cycles++
|
||||
}
|
||||
|
||||
const [ latestResult, localResult ] = tasks.map(task => {
|
||||
task.perf = task.cycles * 1000 / task.time
|
||||
return task
|
||||
})
|
||||
const diff = (localResult.perf - latestResult.perf) / latestResult.perf
|
||||
|
||||
console.log(`Local: ${localResult.perf.toFixed(3)} ops/s (${localResult.cycles} cycles)`)
|
||||
console.log(`Latest: ${latestResult.perf.toFixed(3)} ops/s (${latestResult.cycles} cycles)`)
|
||||
console.log(`Diff: ${(diff * 100).toFixed(3)}%`)
|
||||
|
||||
const THRESHOLD_PERCENT = -3
|
||||
process.exit(diff * 100 < THRESHOLD_PERCENT ? 1 : 0)
|
||||
@@ -0,0 +1,17 @@
|
||||
const { readFileSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
|
||||
function createEngine (pkg) {
|
||||
const liquid = new pkg.Liquid({
|
||||
root: join(__dirname, '../templates'),
|
||||
cache: true,
|
||||
extname: '.liquid'
|
||||
})
|
||||
liquid.registerFilter('url', path => `http://example.com${path}`)
|
||||
return {
|
||||
load: path => liquid.parse(readFileSync(path + '.liquid', 'utf8')),
|
||||
render: (tpl, data) => liquid.renderSync(tpl, data)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { createEngine }
|
||||
@@ -0,0 +1,33 @@
|
||||
const handlebars = require('handlebars')
|
||||
const { readFileSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
|
||||
handlebars.registerPartial(
|
||||
'todo-icon',
|
||||
readFileSync(join(__dirname, '../templates/todo-icon.hbs'), 'utf8')
|
||||
)
|
||||
|
||||
handlebars.registerHelper('concat', function (...args) {
|
||||
return args.filter(x => typeof x === 'string').join('')
|
||||
})
|
||||
|
||||
handlebars.registerHelper('url', function (path) {
|
||||
return `http://example.com${path}`
|
||||
})
|
||||
|
||||
handlebars.registerHelper('inc', function (num) {
|
||||
return Number(num) + 1
|
||||
})
|
||||
|
||||
handlebars.registerHelper('capitalize', function (str) {
|
||||
return str[0].toUpperCase() + str.slice(1).toLowerCase()
|
||||
})
|
||||
|
||||
handlebars.registerHelper('upcase', function (str) {
|
||||
return str.toUpperCase()
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
load: path => handlebars.compile(readFileSync(path + '.hbs', 'utf8')),
|
||||
render: (tpl, data) => tpl(data)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
const { createEngine } = require('./create-liquid')
|
||||
|
||||
module.exports = createEngine(require('../../dist/liquid.node.cjs'))
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
const { readFileSync } = require('fs')
|
||||
const React = require('react')
|
||||
const ReactDOMServer = require('react-dom/server')
|
||||
const babel = require('@babel/core')
|
||||
const requireFromString = require('require-from-string')
|
||||
|
||||
const babelConfig = {
|
||||
presets: ['@babel/preset-react', '@babel/preset-env']
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
load: path => {
|
||||
const src = readFileSync(path + '.jsx', 'utf8')
|
||||
const transformed = babel.transform(src, babelConfig)
|
||||
return requireFromString(transformed.code)
|
||||
},
|
||||
render: (Component, data) => {
|
||||
return ReactDOMServer.renderToString(React.createElement(Component, data))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
const swig = require('swig')
|
||||
|
||||
swig.setFilter('url', function (path) {
|
||||
return `http://example.com${path}`
|
||||
})
|
||||
|
||||
swig.setFilter('prepend', function (input, arg) {
|
||||
return arg + input
|
||||
})
|
||||
|
||||
swig.setFilter('append', function (input, arg) {
|
||||
return input + arg
|
||||
})
|
||||
|
||||
swig.setFilter('inc', function (num) {
|
||||
return Number(num) + 1
|
||||
})
|
||||
|
||||
swig.setFilter('capitalize', function (str) {
|
||||
return str[0].toUpperCase() + str.slice(1).toLowerCase()
|
||||
})
|
||||
|
||||
swig.setFilter('upcase', function (str) {
|
||||
return str.toUpperCase()
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
load: path => swig.compileFile(path + '.swig'),
|
||||
render: (tpl, data) => tpl(data)
|
||||
}
|
||||
Regular → Executable
+2
@@ -3,6 +3,7 @@ const { tag } = require('./tag')
|
||||
const { demo } = require('./demo')
|
||||
const { layout } = require('./layout')
|
||||
const { memory } = require('./memory')
|
||||
const { crossEngines } = require('./cross-engines')
|
||||
|
||||
async function main () {
|
||||
await output()
|
||||
@@ -10,6 +11,7 @@ async function main () {
|
||||
await demo()
|
||||
await layout()
|
||||
await memory()
|
||||
await crossEngines()
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
Regular → Executable
+2
-1
@@ -18,7 +18,8 @@ const template = `
|
||||
`
|
||||
|
||||
function layout () {
|
||||
console.log('--- layout ---')
|
||||
console.log(' layout')
|
||||
console.log('------------------------')
|
||||
return new Promise(resolve => {
|
||||
new Benchmark.Suite('layout')
|
||||
.add('cache=false', {
|
||||
|
||||
Regular → Executable
+7
-6
@@ -13,7 +13,8 @@ const engine = new Liquid(engineOptions)
|
||||
const SAMPLE_COUNT = 1024
|
||||
|
||||
function memory () {
|
||||
console.log('--- memory ---')
|
||||
console.log(' memory')
|
||||
console.log('------------------------')
|
||||
html()
|
||||
todolist()
|
||||
}
|
||||
@@ -29,11 +30,11 @@ function html () {
|
||||
templates.push(engine.parse(str))
|
||||
}
|
||||
const diff1 = (getHeapStatistics().used_heap_size - base) / SAMPLE_COUNT
|
||||
console.log(`${h(str.length)} lorem-html before GC x ${h(diff1)}/tpl (${SAMPLE_COUNT} instances sampled)`)
|
||||
console.log(`[lorem-html ${h(str.length)}][before GC] ${h(diff1)}/tpl (${SAMPLE_COUNT} runs sampled)`)
|
||||
|
||||
global.gc()
|
||||
const diff2 = (getHeapStatistics().used_heap_size - base) / SAMPLE_COUNT
|
||||
console.log(`${h(str.length)} lorem-html after GC x ${h(diff2)}/tpl (${SAMPLE_COUNT} instances sampled)`)
|
||||
console.log(`[lorem-html ${h(str.length)}][after GC] ${h(diff2)}/tpl (${SAMPLE_COUNT} runs sampled)`)
|
||||
}
|
||||
|
||||
function todolist () {
|
||||
@@ -47,15 +48,15 @@ function todolist () {
|
||||
templates.push(engine.parse(str))
|
||||
}
|
||||
const diff1 = (getHeapStatistics().used_heap_size - base) / SAMPLE_COUNT
|
||||
console.log(`${h(str.length)} todolist before GC x ${h(diff1)}/tpl (${SAMPLE_COUNT} instances sampled)`)
|
||||
console.log(`[todolist ${h(str.length)}][before GC] ${h(diff1)}/tpl (${SAMPLE_COUNT} runs sampled)`)
|
||||
|
||||
global.gc()
|
||||
const diff2 = (getHeapStatistics().used_heap_size - base) / SAMPLE_COUNT
|
||||
console.log(`${h(str.length)} todolist after GC x ${h(diff2)}/tpl (${SAMPLE_COUNT} instances sampled)`)
|
||||
console.log(`[todolist ${h(str.length)}][after GC] ${h(diff2)}/tpl (${SAMPLE_COUNT} runs sampled)`)
|
||||
}
|
||||
|
||||
function h (size) {
|
||||
return (size / 1024).toFixed(3) + ' kB'
|
||||
return (size / 1024).toFixed(3) + ' KB'
|
||||
}
|
||||
|
||||
module.exports = { memory }
|
||||
|
||||
Regular → Executable
+2
-1
@@ -4,7 +4,8 @@ const { Liquid } = require('..')
|
||||
const liquid = new Liquid()
|
||||
|
||||
function output () {
|
||||
console.log('--- output ---')
|
||||
console.log(' output')
|
||||
console.log('------------------------')
|
||||
return new Promise(resolve => {
|
||||
new Benchmark.Suite('output')
|
||||
.add('literal', test('{{false}}{{"foo"}}{{32.322}}'))
|
||||
|
||||
Generated
+3380
File diff suppressed because it is too large
Load Diff
Executable
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "benchmark",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node --expose-gc index",
|
||||
"engines": "node -e 'require(\"./cross-engines\").crossEngines()'"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.13.14",
|
||||
"@babel/preset-env": "^7.13.12",
|
||||
"@babel/preset-react": "^7.13.13",
|
||||
"handlebars": "^4.7.7",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"require-from-string": "^2.0.2",
|
||||
"swig": "^1.4.2"
|
||||
}
|
||||
}
|
||||
Regular → Executable
+2
-1
@@ -4,7 +4,8 @@ const { Liquid } = require('..')
|
||||
const liquid = new Liquid()
|
||||
|
||||
function tag () {
|
||||
console.log('--- tag ---')
|
||||
console.log(' tag')
|
||||
console.log('------------------------')
|
||||
return new Promise(resolve => {
|
||||
new Benchmark.Suite('tag')
|
||||
.add('if', test('{% if "foobar" %}foo{% endif %}'))
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<img title="risus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices" src="http://images.example.com/{{id}}.png">
|
||||
@@ -0,0 +1 @@
|
||||
<img title="risus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices" src="http://images.example.com/{{id}}.png">
|
||||
@@ -0,0 +1 @@
|
||||
<img title="risus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices" src="http://images.example.com/{{todo.id}}.png">
|
||||
@@ -0,0 +1,44 @@
|
||||
<section class="todo-list">
|
||||
<h1>
|
||||
posuere cubilia Curae; Vestibulum hendrerit malesuada odio. Fusce ut elit
|
||||
ut augue sollicitudin blandit. Phasellus volutpat lorem. Duis non pede et
|
||||
neque luctus tincidunt. Duis interdum tempus elit.
|
||||
<small>Aenean metus. Vestibulum ac lacus. Vivamus porttitor, massa ut.</small>
|
||||
</h1>
|
||||
<a href={{url "/add"}} class="todo-add"><i class="fa fa-plus-square"></i>Add Todo</a>
|
||||
<ul class="filter-category">
|
||||
{{#each categories }}
|
||||
<li style="background: {{ color }}">
|
||||
<a href="/todos/category/{{ id }}">{{ title }}</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{#each todos }}
|
||||
<div>
|
||||
<h2>
|
||||
Todo {{inc @index}}:
|
||||
<span class="todo-item content" data-todo-index="{{@index}}">
|
||||
{{ upcase (concat 'TODO: ' title) }}
|
||||
</span>
|
||||
</h2>
|
||||
{{> todo-icon id=id }}
|
||||
|
||||
<p class="description">
|
||||
Purus eu mi. Proin commodo, massa commodo dapibus elementum,
|
||||
libero lacus pulvinar eros, ut tincidunt nisl elit ut velit. Cras rutrum
|
||||
porta purus. Vivamus lorem. Sed turpis enim, faucibus quis, pharetra in,
|
||||
sagittis sed, magna. Curabitur ultricies felis ut libero. Nullam tincidunt
|
||||
enim eu nibh. Nunc eget ipsum in sem facilisis convallis. Proin fermentum
|
||||
</p>
|
||||
|
||||
<div class="todo-meta">
|
||||
{{# if category }}
|
||||
<span>{{ capitalize category }}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<a class="btn btn-primary" href={{ url (concat "/edit/" id) }}><i class="fa fa-pencil"></i> Edit</a>
|
||||
<a class="btn btn-default"><i class="fa fa-check"></i> Check</a>
|
||||
<a class="btn ben-danger" href={{ url (concat "/delete/" id) }}><i class="fa fa-trash-o"</i> Trash</a>
|
||||
</div>
|
||||
{{/each}}
|
||||
</section>
|
||||
@@ -0,0 +1,57 @@
|
||||
// eslint-disable-next-line
|
||||
const React = require('react')
|
||||
|
||||
function capitalize (str) {
|
||||
return str[0].toUpperCase() + str.slice(1).toLowerCase()
|
||||
}
|
||||
function url (path) {
|
||||
return `http://example.com${path}`
|
||||
}
|
||||
function renderTodoIcon (id) {
|
||||
return <img title="risus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices" src="http://images.example.com/{id}.png"/>
|
||||
}
|
||||
|
||||
module.exports = ({ categories, todos }) => {
|
||||
return <section className="todo-list">
|
||||
<h1>
|
||||
posuere cubilia Curae; Vestibulum hendrerit malesuada odio. Fusce ut elit
|
||||
ut augue sollicitudin blandit. Phasellus volutpat lorem. Duis non pede et
|
||||
neque luctus tincidunt. Duis interdum tempus elit.
|
||||
<small>Aenean metus. Vestibulum ac lacus. Vivamus porttitor, massa ut.</small>
|
||||
</h1>
|
||||
<a href={url('/add')} className="todo-add"><i className="fa fa-plus-square"></i>Add Todo</a>
|
||||
<ul className="filter-category">
|
||||
{categories.map((item, i) => {
|
||||
return <li key={i} style={{ background: item.color }}>
|
||||
<a href={'/todos/category/' + item.id }>{ item.title }</a>
|
||||
</li>
|
||||
})}
|
||||
</ul>
|
||||
{todos.map((todo, index0) => {
|
||||
return <div key={index0}>
|
||||
<h2>
|
||||
Todo { index0 + 1 }:
|
||||
<span className="todo-item content" data-todo-index="{{ forloop.index0 }}">
|
||||
{ ('TODO: ' + todo.title).toUpperCase() }
|
||||
</span>
|
||||
</h2>
|
||||
{ renderTodoIcon(todo.id) }
|
||||
|
||||
<p className="description">
|
||||
Purus eu mi. Proin commodo, massa commodo dapibus elementum,
|
||||
libero lacus pulvinar eros, ut tincidunt nisl elit ut velit. Cras rutrum
|
||||
porta purus. Vivamus lorem. Sed turpis enim, faucibus quis, pharetra in,
|
||||
sagittis sed, magna. Curabitur ultricies felis ut libero. Nullam tincidunt
|
||||
enim eu nibh. Nunc eget ipsum in sem facilisis convallis. Proin fermentum
|
||||
</p>
|
||||
|
||||
<div className="todo-meta">
|
||||
{ todo.category ? <span>{capitalize(todo.category)}</span> : '' }
|
||||
</div>
|
||||
<a className="btn btn-primary" href={url('/edit/' + todo.id)}><i className="fa fa-pencil"></i> Edit</a>
|
||||
<a className="btn btn-default"><i className="fa fa-check"></i> Check</a>
|
||||
<a className="btn ben-danger" href={url('/delete/' + todo.id)}><i className="fa fa-trash-o"></i> Trash</a>
|
||||
</div>
|
||||
})}
|
||||
</section>
|
||||
}
|
||||
@@ -7,8 +7,7 @@
|
||||
</h1>
|
||||
<a href={{"/add" | url}} class="todo-add"><i class="fa fa-plus-square"></i>Add Todo</a>
|
||||
<ul class="filter-category">
|
||||
{% for i in (0..categories.length) %}
|
||||
{% assign item=categories[i] %}
|
||||
{% for item in categories %}
|
||||
<li style="background: {{ item.color }}">
|
||||
<a href="/todos/category/{{ item.id }}">{{ item.title }}</a>
|
||||
</li>
|
||||
@@ -22,11 +21,7 @@
|
||||
{{ todo.title | prepend: 'TODO: ' | upcase }}
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<img
|
||||
title="risus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices"
|
||||
src="{% include "todo-icon", id=todo.id }} %}"
|
||||
>
|
||||
{% render "todo-icon", id:todo.id %}
|
||||
|
||||
<p class="description">
|
||||
Purus eu mi. Proin commodo, massa commodo dapibus elementum,
|
||||
@@ -38,12 +33,12 @@
|
||||
|
||||
<div class="todo-meta">
|
||||
{% if todo.category %}
|
||||
<span>{{ todo.category.title | capitalize }}</span>
|
||||
<span>{{ todo.category | capitalize }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a class="btn btn-primary" href={{"/edit/" | append todo.id | url }}><i class="fa fa-pencil"></i> Edit</a>
|
||||
<a class="btn btn-primary" href={{"/edit/" | append: todo.id | url }}><i class="fa fa-pencil"></i> Edit</a>
|
||||
<a class="btn btn-default"><i class="fa fa-check"></i> Check</a>
|
||||
<a class="btn ben-danger" href={{"/delete/" | append todo.id | url }}><i class="fa fa-trash-o"</i> Trash</a>
|
||||
<a class="btn ben-danger" href={{"/delete/" | append: todo.id | url }}><i class="fa fa-trash-o"</i> Trash</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<section class="todo-list">
|
||||
<h1>
|
||||
posuere cubilia Curae; Vestibulum hendrerit malesuada odio. Fusce ut elit
|
||||
ut augue sollicitudin blandit. Phasellus volutpat lorem. Duis non pede et
|
||||
neque luctus tincidunt. Duis interdum tempus elit.
|
||||
<small>Aenean metus. Vestibulum ac lacus. Vivamus porttitor, massa ut.</small>
|
||||
</h1>
|
||||
<a href={{"/add" | url}} class="todo-add"><i class="fa fa-plus-square"></i>Add Todo</a>
|
||||
<ul class="filter-category">
|
||||
{% for item in categories %}
|
||||
<li style="background: {{ item.color }}">
|
||||
<a href="/todos/category/{{ item.id }}">{{ item.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% for todo in todos %}
|
||||
<div>
|
||||
<h2>
|
||||
Todo {{ loop.index }}:
|
||||
<span class="todo-item content" data-todo-index="{{ loop.index0 }}">
|
||||
{{ todo.title | prepend('TODO: ') | upcase }}
|
||||
</span>
|
||||
</h2>
|
||||
{% include "todo-icon.swig" %}
|
||||
|
||||
<p class="description">
|
||||
Purus eu mi. Proin commodo, massa commodo dapibus elementum,
|
||||
libero lacus pulvinar eros, ut tincidunt nisl elit ut velit. Cras rutrum
|
||||
porta purus. Vivamus lorem. Sed turpis enim, faucibus quis, pharetra in,
|
||||
sagittis sed, magna. Curabitur ultricies felis ut libero. Nullam tincidunt
|
||||
enim eu nibh. Nunc eget ipsum in sem facilisis convallis. Proin fermentum
|
||||
</p>
|
||||
|
||||
<div class="todo-meta">
|
||||
{% if todo.category %}
|
||||
<span>{{ todo.category | capitalize }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a class="btn btn-primary" href={{"/edit/" | append(todo.id) | url }}><i class="fa fa-pencil"></i> Edit</a>
|
||||
<a class="btn btn-default"><i class="fa fa-check"></i> Check</a>
|
||||
<a class="btn ben-danger" href={{"/delete/" | append(todo.id) | url }}><i class="fa fa-trash-o"</i> Trash</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
+4
-6
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
rm -rf docs/source/api docs/source/zh-cn/api
|
||||
typedoc ./src --gitRevision master --out docs/source/api --plugin typedoc-plugin-markdown --theme vuepress
|
||||
typedoc ./src --gitRevision master --out docs/source/api --plugin typedoc-plugin-markdown --ignoreCompilerErrors true --theme vuepress
|
||||
rm docs/source/api/README.md
|
||||
|
||||
for file in $(find docs/source/api -name "*.md"); do
|
||||
@@ -9,8 +11,6 @@ for file in $(find docs/source/api -name "*.md"); do
|
||||
-e 's/\(\]([^_)]*\)\/_/\1\//g' \
|
||||
-e 's/\(\]([^)]*\.\)md/\1html/g' \
|
||||
-e 's/\](_/\](/g' \
|
||||
-e 's/{%/{% raw %}{%{% endraw %}/g' \
|
||||
-e 's/{{/{% raw %}{{{% endraw %}/g' \
|
||||
-e '1 s/"/\"/g' \
|
||||
-e '1 s/</\</g' \
|
||||
-e '1 s/>/\>/g' \
|
||||
@@ -18,9 +18,7 @@ for file in $(find docs/source/api -name "*.md"); do
|
||||
-e '1 s/^# \(.*\)/---\ntitle: "\1"\nauto: true\n---/' \
|
||||
$file
|
||||
target=${file/\/_/\/}
|
||||
if [ "$file" != "$target" ]; then
|
||||
mv $file $target
|
||||
fi
|
||||
cat $file | ./bin/escape-nunjucks.js > $target
|
||||
done
|
||||
|
||||
cp -r docs/source/api docs/source/zh-cn/api
|
||||
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd docs
|
||||
cp ../CHANGELOG.md source/tutorials/changelog.md
|
||||
sed -i \
|
||||
-e 's/{%/{% raw %}{%{% endraw %}/g' \
|
||||
-e 's/{{/{% raw %}{{{% endraw %}/g' \
|
||||
-e '1 s/"/\"/g' \
|
||||
-e '1 s/</\</g' \
|
||||
-e '1 s/>/\>/g' \
|
||||
source/tutorials/changelog.md
|
||||
cp source/tutorials/changelog.md source/zh-cn/tutorials/changelog.md
|
||||
|
||||
sed -i '1i ---\ntitle: Changelog\nauto: true\n---\n' source/tutorials/changelog.md
|
||||
sed -i '1i ---\ntitle: 更新日志\nauto: true\n---\n' source/zh-cn/tutorials/changelog.md
|
||||
+3
-13
@@ -1,26 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
./bin/build-contributors.sh
|
||||
./bin/build-apidoc.sh
|
||||
./bin/build-changelog.sh
|
||||
|
||||
cd docs
|
||||
npm ci
|
||||
npm run build
|
||||
cp CNAME public/
|
||||
|
||||
cp ../CHANGELOG.md source/tutorials/changelog.md
|
||||
sed -i \
|
||||
-e 's/{%/{% raw %}{%{% endraw %}/g' \
|
||||
-e 's/{{/{% raw %}{{{% endraw %}/g' \
|
||||
-e '1 s/"/\"/g' \
|
||||
-e '1 s/</\</g' \
|
||||
-e '1 s/>/\>/g' \
|
||||
source/tutorials/changelog.md
|
||||
cp source/tutorials/changelog.md source/zh-cn/tutorials/changelog.md
|
||||
|
||||
sed -i '1i ---\ntitle: Changelog\nauto: true\n---\n' source/tutorials/changelog.md
|
||||
sed -i '1i ---\ntitle: 更新日志\nauto: true\n---\n' source/zh-cn/tutorials/changelog.md
|
||||
|
||||
if [ "$HEXO_ALGOLIA_INDEXING_KEY" != "" ]; then
|
||||
npm run index
|
||||
fi
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// In Hexo, content inside markdown code/code fences,
|
||||
// will not go through nunjucks.
|
||||
// So we're escaping special chars outside code only.
|
||||
let backTick = 0
|
||||
process.stdin.on('data', (buffer) => {
|
||||
for (const c of buffer.toString()) {
|
||||
if (c === '`') {
|
||||
backTick++
|
||||
}
|
||||
const shouldEscape = '{%%}'.includes(c) && backTick % 2 === 0
|
||||
if (shouldEscape) {
|
||||
const code = c.charCodeAt(0).toString(16)
|
||||
process.stdout.write(`&#x${code};`)
|
||||
} else process.stdout.write(c)
|
||||
}
|
||||
})
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION_LATEST=$(cat package.json | grep '"version":' | awk -F'"' '{print $4}')
|
||||
FILE_LOCAL=dist/liquid.node.cjs.js
|
||||
FILE_LATEST=dist/liquid.node.cjs.$VERSION_LATEST.js
|
||||
URL_LATEST=https://unpkg.com/liquidjs@$VERSION_LATEST/dist/liquid.node.cjs.js
|
||||
|
||||
if [ ! -f $FILE_LATEST ]; then
|
||||
curl $URL_LATEST > $FILE_LATEST
|
||||
fi
|
||||
|
||||
# if [ ! -f $FILE_LOCAL ]; then
|
||||
BUNDLES=cjs npm run build:dist
|
||||
# fi
|
||||
|
||||
exec node benchmark/diff.js $FILE_LOCAL $FILE_LATEST
|
||||
@@ -0,0 +1,9 @@
|
||||
# LiquidJS for Node.js
|
||||
|
||||
## Get Started
|
||||
|
||||
```bash
|
||||
cd demo/nodejs
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Liquid } from 'liquidjs'
|
||||
|
||||
const engine = new Liquid({
|
||||
extname: '.liquid',
|
||||
globals: { title: 'LiquidJS Demo' },
|
||||
// root files for `.render()` and `.parse()`
|
||||
root: process.cwd(),
|
||||
// layout files for `{% layout %}`
|
||||
layouts: process.cwd() + '/layouts',
|
||||
// partial files for `{% include %}` and `{% render %}`
|
||||
partials: process.cwd() + '/partials'
|
||||
})
|
||||
|
||||
const ctx = {
|
||||
todos: ['fork and clone', 'make it better', 'make a pull request']
|
||||
}
|
||||
|
||||
async function main () {
|
||||
console.log('==========renderFile===========')
|
||||
const html = await engine.renderFile('todolist', ctx)
|
||||
console.log(html)
|
||||
|
||||
console.log('===========Streamed===========')
|
||||
const tpls = await engine.parseFile('todolist')
|
||||
engine.renderToNodeStream(tpls, ctx)
|
||||
.on('data', data => process.stdout.write(data))
|
||||
.on('end', () => console.log(''))
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "liquidjs-demo-nodejs",
|
||||
"description": "Node.js demo for liquidjs",
|
||||
"private": true,
|
||||
"main": "index.mjs",
|
||||
"scripts": {
|
||||
"start": "node index.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"hello-liquid": "^1.0.0",
|
||||
"liquidjs": "^9.28.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<li>{{index}} - {{todo}}</li>
|
||||
@@ -0,0 +1,7 @@
|
||||
{% layout 'html.liquid' %}
|
||||
<h1>{% include "hello-liquid" %}</h1>
|
||||
<ul>
|
||||
{% for todo in todos %}
|
||||
{% render 'todo.liquid' with todo, index: forloop.index%}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
+20
-5
@@ -1,9 +1,14 @@
|
||||
const { Liquid } = require('liquidjs')
|
||||
|
||||
const engine = new Liquid({
|
||||
root: __dirname,
|
||||
extname: '.liquid',
|
||||
globals: {title: 'LiquidJS Demo'}
|
||||
globals: { title: 'LiquidJS Demo' },
|
||||
// root files for `.render()` and `.parse()`
|
||||
root: __dirname,
|
||||
// layout files for `{% layout %}`
|
||||
layouts: './layouts',
|
||||
// partial files for `{% include %}` and `{% render %}`
|
||||
partials: './partials'
|
||||
})
|
||||
|
||||
engine.registerTag('header', {
|
||||
@@ -21,6 +26,16 @@ const ctx = {
|
||||
todos: ['fork and clone', 'make it better', 'make a pull request']
|
||||
}
|
||||
|
||||
engine.renderFile('todolist', ctx)
|
||||
.then(console.log)
|
||||
.catch(err => console.error(err.stack))
|
||||
async function main () {
|
||||
console.log('==========renderFile===========')
|
||||
const html = await engine.renderFile('todolist', ctx)
|
||||
console.log(html)
|
||||
|
||||
console.log('===========Streamed===========')
|
||||
const tpls = await engine.parseFile('todolist')
|
||||
engine.renderToNodeStream(tpls, ctx)
|
||||
.on('data', data => process.stdout.write(data))
|
||||
.on('end', () => console.log(''))
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<head><meta name="title" content="{{title}}"></head>
|
||||
<body>
|
||||
{% block %}{% endblock %}
|
||||
{% render "footer.liquid" %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -7,6 +7,6 @@
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"liquidjs": "*"
|
||||
"liquidjs": "^9.28.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<small>{{title}}</small>
|
||||
@@ -0,0 +1 @@
|
||||
<li>{{index}} - {{todo}}</li>
|
||||
@@ -2,6 +2,6 @@
|
||||
{%header content: title | capitalize%}
|
||||
<ul>
|
||||
{% for todo in todos %}
|
||||
<li>{{forloop.index}} - {{todo}}</li>
|
||||
{% render 'todo.liquid' with todo, index: forloop.index%}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -21,5 +21,4 @@ const ctx = {
|
||||
title: 'Welcome to liquidjs!'
|
||||
}
|
||||
|
||||
// console.log('isTruthy:', isTruthy('a string here'));
|
||||
engine.renderFile('todolist', ctx).then(console.log)
|
||||
|
||||
+9
-3
@@ -1,8 +1,10 @@
|
||||
title: LiquidJS
|
||||
subtitle: "A simple, expressive and safe template engine."
|
||||
description: "LiquidJS is a simple, expressive and safe template engine."
|
||||
description: "LiquidJS is a simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript."
|
||||
author: Harttle
|
||||
language: en
|
||||
language:
|
||||
- en
|
||||
- zh-cn
|
||||
timezone: UTC
|
||||
|
||||
url: https://liquidjs.com
|
||||
@@ -19,8 +21,12 @@ pretty_urls:
|
||||
theme: navy
|
||||
|
||||
highlight:
|
||||
enable: false
|
||||
prismjs:
|
||||
enable: true
|
||||
line_number: false
|
||||
preprocess: true
|
||||
line_number: true
|
||||
tab_replace: ''
|
||||
|
||||
algolia:
|
||||
applicationID: 0X19J927JZ
|
||||
|
||||
Generated
+5687
-578
File diff suppressed because it is too large
Load Diff
+6
-5
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"hexo": {
|
||||
"version": "4.2.0"
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "hexo generate",
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "^0.22.0",
|
||||
"hexo": "^4.2.0",
|
||||
"hexo": "^5.4.0",
|
||||
"hexo-algolia": "^1.3.1",
|
||||
"hexo-clean-css": "^1.0.0",
|
||||
"hexo-filter-nofollow": "^2.0.2",
|
||||
@@ -21,15 +21,16 @@
|
||||
"hexo-generator-feed": "^2.0.0",
|
||||
"hexo-generator-sitemap": "^2.0.0",
|
||||
"hexo-renderer-marked": "^2.0.0",
|
||||
"hexo-renderer-pug": "^1.0.0",
|
||||
"hexo-renderer-pug": "^2.0.0",
|
||||
"hexo-renderer-stylus": "^1.0.0",
|
||||
"hexo-renderer-swig": "^1.1.0",
|
||||
"hexo-server": "^1.0.0",
|
||||
"hexo-uglify": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"all-contributors-cli": "^6.8.1",
|
||||
"eslint": "^6.0.1",
|
||||
"eslint-config-hexo": "^4.0.0",
|
||||
"all-contributors-cli": "^6.8.1"
|
||||
"eslint-config-hexo": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
|
||||
@@ -37,7 +37,12 @@ hexo.extend.helper.register('page_nav', function () {
|
||||
})
|
||||
|
||||
hexo.extend.helper.register('raw', function (filepath) {
|
||||
const content = readFileSync(resolve(this.view_dir, filepath))
|
||||
const content = readFileSync(resolve(this.view_dir, filepath), 'utf8')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
return content
|
||||
})
|
||||
|
||||
|
||||
@@ -19,5 +19,5 @@ hexo.extend.tag.register('note', (args, text) => {
|
||||
}, true)
|
||||
|
||||
hexo.extend.tag.register('since', (args, text) => {
|
||||
return `<p class="since">Added in: ${text.trim()}</p>`
|
||||
return `<p class="since">${text.trim()}</p>`
|
||||
}, true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
tutorials: /tutorials/setup.html
|
||||
tutorials: /tutorials/intro-to-liquid.html
|
||||
tags: /tags/overview.html
|
||||
filters: /filters/overview.html
|
||||
playground: /playground.html
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
-
|
||||
url: https://github.com/harttle/liquidjs/pull/5
|
||||
date: '2016-09-12'
|
||||
title:
|
||||
zh-cn: '异步化:重写了解析器和渲染器,使 LiquidJS 可以提供 Promise API 并支持异步的自定义 Tag 实现。'
|
||||
en: 'Async spike: rewrite the parser and render to support Promise, which enables async tags.'
|
||||
-
|
||||
url: https://opencollective.com/liquidjs/#section-contribute
|
||||
date: '2020-02-26'
|
||||
@@ -22,3 +16,9 @@
|
||||
title:
|
||||
zh-cn: '性能提升:引入 AST 并重新设计 Token 类型系统,使渲染性能平均提升 100.3%。'
|
||||
en: 'Performance Boost: a simple AST to improve render performance by 100.3%.'
|
||||
-
|
||||
url: https://github.com/harttle/liquidjs/milestone/3?closed=1
|
||||
date: '2021-09-30'
|
||||
title:
|
||||
zh-cn: '流式渲染:4 倍渲染速度,并增加了对流式渲染的支持。'
|
||||
en: 'Streamed Rendering: now render is 4x faster and support streamed rendering.'
|
||||
|
||||
@@ -9,6 +9,10 @@ tutorials:
|
||||
advanced:
|
||||
caching: caching.html
|
||||
registeration: register-filters-tags.html
|
||||
access_scope_in_filters: access-scope-in-filters.html
|
||||
parse_parameters: parse-parameters.html
|
||||
render_tag_content: render-tag-content.html
|
||||
sync_and_async: sync-and-async.html
|
||||
whitespace: whitespace-control.html
|
||||
plugins: plugins.html
|
||||
operators: operators.html
|
||||
@@ -28,6 +32,7 @@ filters:
|
||||
capitalize: capitalize.html
|
||||
ceil: ceil.html
|
||||
compact: compact.html
|
||||
concat: concat.html
|
||||
date: date.html
|
||||
default: default.html
|
||||
divided_by: divided_by.html
|
||||
@@ -46,6 +51,7 @@ filters:
|
||||
newline_to_br: newline_to_br.html
|
||||
plus: plus.html
|
||||
prepend: prepend.html
|
||||
raw: raw.html
|
||||
remove: remove.html
|
||||
remove_first: remove_first.html
|
||||
replace: replace.html
|
||||
@@ -78,15 +84,20 @@ tags:
|
||||
comment: comment.html
|
||||
cycle: cycle.html
|
||||
decrement: decrement.html
|
||||
echo: echo.html
|
||||
else: if.html
|
||||
elsif: if.html
|
||||
for: for.html
|
||||
if: if.html
|
||||
include: include.html
|
||||
increment: increment.html
|
||||
layout: layout.html
|
||||
liquid: liquid.html
|
||||
raw: raw.html
|
||||
render: render.html
|
||||
tablerow: tablerow.html
|
||||
unless: unless.html
|
||||
when: case.html
|
||||
|
||||
api:
|
||||
quick_entry:
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
title: concat
|
||||
---
|
||||
|
||||
{% since %}v2.0.0{% endsince %}
|
||||
|
||||
Concatenates (joins together) multiple arrays. The resulting array contains all the items from the input arrays.
|
||||
|
||||
Input
|
||||
|
||||
@@ -16,6 +16,10 @@ Output
|
||||
Fri, Jul 17, 15
|
||||
```
|
||||
|
||||
{% note info TimeZone %}
|
||||
Date will be converted to local time before output. To avoid that, you can set `timezoneOffset` LiquidJS option to `0`, its default value is your local timezone offset which can be obtained by `new Date().getTimezoneOffset()`.
|
||||
{% endnote %}
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ article.published_at | date: "%Y" }}
|
||||
@@ -38,6 +42,10 @@ Output
|
||||
Mar 14, 16
|
||||
```
|
||||
|
||||
{% note info Timestamp Strings %}
|
||||
Note that LiquidJS is using JavaScript [Date][newDate] to parse the input string, that means [IETF-compliant RFC 2822 timestamps](https://datatracker.ietf.org/doc/html/rfc2822#page-14) and strings in [a version of ISO8601](https://www.ecma-international.org/ecma-262/11.0/#sec-date.parse) are supported.
|
||||
{% endnote %}
|
||||
|
||||
To get the current time, pass the special word `"now"` (or `"today"`) to `date`:
|
||||
|
||||
Input
|
||||
|
||||
@@ -44,4 +44,23 @@ Output
|
||||
2.99
|
||||
```
|
||||
|
||||
## Allowing `false`
|
||||
|
||||
{% since %}v9.32.0{% endsince %}
|
||||
|
||||
To allow variables to return `false` instead of the default value, you can use the `allow_false` parameter.
|
||||
|
||||
Input
|
||||
|
||||
```liquid
|
||||
{% assign display_price = false %}
|
||||
{{ display_price | default: true, allow_false: true }}
|
||||
```
|
||||
|
||||
Output
|
||||
|
||||
```text
|
||||
false
|
||||
```
|
||||
|
||||
[falsy]: ../tutorials/truthy-and-falsy.html
|
||||
|
||||
@@ -26,6 +26,23 @@ Output
|
||||
1.6666666666666667
|
||||
```
|
||||
|
||||
{% note info Integer Arithmetic %}Since JavaScript doesn't differentiate integers and floats, LiquidJS is not capable of integer arithmetic and the return type is always `number`, the string representation of which depends on its value.{% endnote %}
|
||||
In JavaScript, float and integer shares the same type `number` and we cannot tell the difference. For example:
|
||||
|
||||
```javascript
|
||||
// always true
|
||||
5.0 === 5
|
||||
```
|
||||
|
||||
You'll need to pass another `integerArithmetic` argument to enforce integer divide:
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ 5 | divided_by: 3, true }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1
|
||||
```
|
||||
|
||||
[floor]: ./floor.html
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Filters
|
||||
description: Description and demo for each Liquid filter
|
||||
---
|
||||
|
||||
LiquidJS implements business-logic independent filters that are typically implemented in [shopify/liquid][shopify/liquid]. This section contains the specification and demoes for all the filters implemented by LiquidJS.
|
||||
@@ -8,10 +9,11 @@ There's 40+ filters supported by LiquidJS. These filters can be categorized into
|
||||
|
||||
Categories | Filters
|
||||
--- | ---
|
||||
Math | plus, minus, modulo, times, floor, ceil, round, divided_by, abs
|
||||
Math | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
|
||||
String | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, remove, remove_first, truncate, truncatewords
|
||||
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
|
||||
Array | slice, map, sort, sort_natural, uniq, where, first, last, join, reverse
|
||||
Array | slice, map, sort, sort_natural, uniq, where, first, last, join, reverse, concat, compact, size
|
||||
Date | date
|
||||
Misc | default, json, raw
|
||||
|
||||
[shopify/liquid]: https://github.com/Shopify/liquid
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: raw
|
||||
---
|
||||
|
||||
{% since %}v9.37.0{% endsince %}
|
||||
|
||||
Liquid filter that directly returns the value of the variable. Useful when [outputEscape](/api/interfaces/liquid_options_.liquidoptions.html#Optional-outputEscape) is set.
|
||||
|
||||
{% note info Auto escape %}
|
||||
By default `outputEscape` is not set. That means LiquidJS output is not escaped by default, thus `raw` filter is not useful until `outputEscape` is set.
|
||||
{% endnote %}
|
||||
|
||||
Input (`outputEscape` not set)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
<
|
||||
```
|
||||
|
||||
Input (`outputEscape="escape"`)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
<
|
||||
```
|
||||
|
||||
Input (`outputEscape="json"`)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
"<"
|
||||
```
|
||||
|
||||
Input (`outputEscape="escape"`)
|
||||
```liquid
|
||||
{{ "<" | raw }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
<
|
||||
```
|
||||
@@ -14,5 +14,5 @@ Input
|
||||
|
||||
Output
|
||||
```text
|
||||
|
||||
ants, bugs, bees```
|
||||
ants, bugs, bees
|
||||
```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
layout: index
|
||||
description: LiquidJS is a simple, expressive and safe template engine.
|
||||
description: LiquidJS is a simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript.
|
||||
subtitle: A simple, expressive and safe template engine.
|
||||
---
|
||||
ul#intro-feature-list
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"short_name": "LiquidJS",
|
||||
"name": "LiquidJS",
|
||||
"description": "A simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript.",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon/apple-touch-icon-57x57.png",
|
||||
@@ -23,6 +24,33 @@
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"shortcuts" : [
|
||||
{
|
||||
"name": "Tutorials",
|
||||
"url": "/tutorials/intro-to-liquid.html",
|
||||
"description": "A set of articles describing how to use LiquidJS"
|
||||
},
|
||||
{
|
||||
"name": "Tags",
|
||||
"url": "/tags/overview.html",
|
||||
"description": "Description and demo for each Liquid tag"
|
||||
},
|
||||
{
|
||||
"name": "Filters",
|
||||
"url": "/filters/overview.html",
|
||||
"description": "Description and demo for each Liquid filter"
|
||||
},
|
||||
{
|
||||
"name": "Playground",
|
||||
"url": "/playground.html",
|
||||
"description": "An online code editor to try out and share Liquid templates"
|
||||
},
|
||||
{
|
||||
"name": "API",
|
||||
"url": "/api/classes/liquid_.liquid.html",
|
||||
"description": "TypeScript doc for LiquidJS classes and interfaces"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"theme_color": "#0f83ce",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
---
|
||||
layout: playground
|
||||
title: Playground
|
||||
description: An online code editor to try out and share Liquid templates
|
||||
---
|
||||
|
||||
+9
-2
@@ -9,6 +9,7 @@ const urlsToCache = [
|
||||
'/fonts/icomoon.ttf?e8nnma'
|
||||
]
|
||||
const blackList = [
|
||||
/chrome-extension:/,
|
||||
/google-analytics.com.*collect/
|
||||
]
|
||||
|
||||
@@ -29,8 +30,14 @@ self.addEventListener('fetch', event => {
|
||||
if (
|
||||
event.request.method !== 'GET' || blackList.some(regex => regex.exec(event.request.url))
|
||||
) return network(event.request)
|
||||
const pn = networkAndSave(event.request)
|
||||
event.respondWith(cache(event.request).then(res => res || pn).catch(() => pn))
|
||||
if (event.request.url.match(/\.githubusercontent\.com/)) {
|
||||
// cache first
|
||||
const pn = networkAndSave(event.request)
|
||||
event.respondWith(cache(event.request).then(res => res || pn).catch(() => pn))
|
||||
} else {
|
||||
// network first
|
||||
event.respondWith(networkAndSave(event.request).catch(() => cache(event.request)))
|
||||
}
|
||||
})
|
||||
|
||||
function cache (req) {
|
||||
|
||||
@@ -12,7 +12,7 @@ Input
|
||||
{% case handle %}
|
||||
{% when "cake" %}
|
||||
This is a cake
|
||||
{% when "cookie" %}
|
||||
{% when "cookie", "biscuit" %}
|
||||
This is a cookie
|
||||
{% else %}
|
||||
This is not a cake nor a cookie
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: Echo
|
||||
---
|
||||
|
||||
{% since %}v9.31.0{% endsince %}
|
||||
|
||||
Outputs an expression in the rendered HTML. This is identical to wrapping an expression in `{{` and `}}`, but works inside liquid tags and supports filters.
|
||||
|
||||
## echo
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% assign username = 'Bob' %}
|
||||
{% echo username | append: ", welcome to LiquidJS!" | capitalize %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
Bob, welcome to LiquidJS!
|
||||
```
|
||||
+90
-22
@@ -66,17 +66,17 @@ Causes the loop to stop iterating when it encounters the `break` tag.
|
||||
Input
|
||||
```liquid
|
||||
{% for i in (1..5) %}
|
||||
{% if i == 4 %}
|
||||
{%- if i == 4 -%}
|
||||
{% break %}
|
||||
{% else %}
|
||||
{%- else -%}
|
||||
{{ i }}
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1 2 3
|
||||
123
|
||||
```
|
||||
|
||||
### continue
|
||||
@@ -86,17 +86,17 @@ Causes the loop to skip the current iteration when it encounters the `continue`
|
||||
Input
|
||||
```liquid
|
||||
{% for i in (1..5) %}
|
||||
{% if i == 4 %}
|
||||
{% continue %}
|
||||
{% else %}
|
||||
{%- if i == 4 -%}
|
||||
{%- continue -%}
|
||||
{%- else -%}
|
||||
{{ i }}
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1 2 3 5
|
||||
1235
|
||||
```
|
||||
|
||||
### forloop
|
||||
@@ -106,7 +106,7 @@ There's a `forloop` object available inside `for` loops. It's used to indicate t
|
||||
The `forloop.first`, `forloop.last` and `forloop.length` property:
|
||||
|
||||
Input
|
||||
```
|
||||
```liquid
|
||||
{% for i in (1..5) %}
|
||||
{%- if forloop.first == true -%} First
|
||||
{%- elsif forloop.last == true -%} Last
|
||||
@@ -116,7 +116,7 @@ Input
|
||||
```
|
||||
|
||||
Output
|
||||
```
|
||||
```text
|
||||
First
|
||||
5
|
||||
5
|
||||
@@ -152,15 +152,15 @@ Limits the loop to the specified number of iterations.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
<!-- if array = [1,2,3,4,5,6] -->
|
||||
<!-- for array = [1,2,3,4,5,6] -->
|
||||
{% for item in array limit:2 %}
|
||||
{{ item }}
|
||||
{{- item -}}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1 2
|
||||
12
|
||||
```
|
||||
|
||||
### offset
|
||||
@@ -169,15 +169,57 @@ Begins the loop at the specified index.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
<!-- if array = [1,2,3,4,5,6] -->
|
||||
<!-- for array = [1,2,3,4,5,6] -->
|
||||
{% for item in array offset:2 %}
|
||||
{{ item }}
|
||||
{{- item -}}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
3 4 5 6
|
||||
3456
|
||||
```
|
||||
|
||||
#### offset:continue
|
||||
|
||||
{% since %}v9.33.0{% endsince %}
|
||||
|
||||
Offset value can be `continue` to continue previous loop. For example:
|
||||
|
||||
Input
|
||||
```liquid
|
||||
<!-- for array = [1,2,3,4,5,6] -->
|
||||
{% for item in array limit:2 %}
|
||||
{{- item -}}
|
||||
{% endfor%}
|
||||
{% for item in array offset:continue %}
|
||||
{{- item -}}
|
||||
{% endfor%}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
12
|
||||
3456
|
||||
```
|
||||
|
||||
For the same variable name (`"item"` in this case) and same collection (`"array"` in this case), there's one position record. That means you can start a new loop with a different variable name:
|
||||
|
||||
Input
|
||||
```liquid
|
||||
<!-- for array = [1,2,3,4,5,6] -->
|
||||
{% for item in array limit:2 %}
|
||||
{{- item -}}
|
||||
{% endfor%}
|
||||
{% for item2 in array offset:continue %}
|
||||
{{- item2 -}}
|
||||
{% endfor%}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
12
|
||||
123456
|
||||
```
|
||||
|
||||
### range
|
||||
@@ -187,19 +229,19 @@ Defines a range of numbers to loop through. The range can be defined by both lit
|
||||
Input
|
||||
```liquid
|
||||
{% for i in (3..5) %}
|
||||
{{ i }}
|
||||
{% endfor %}
|
||||
{{- i -}}
|
||||
{% endfor-%}
|
||||
|
||||
{% assign num = 4 %}
|
||||
{% for i in (1..num) %}
|
||||
{{ i }}
|
||||
{{- i -}}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
3 4 5
|
||||
1 2 3 4
|
||||
345
|
||||
1234
|
||||
```
|
||||
|
||||
### reversed
|
||||
@@ -218,3 +260,29 @@ Output
|
||||
```text
|
||||
6 5 4 3 2 1
|
||||
```
|
||||
|
||||
When used with additional parameters, order is important. Leading with `reversed` reverses the order of the loop before executing the other parameters.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% for i in (1..8) reversed limit: 4 %}
|
||||
{{ i }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
8 7 6 5
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% for i in (1..8) limit: 4 reversed %}
|
||||
{{ i }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
4 3 2 1
|
||||
```
|
||||
|
||||
@@ -5,7 +5,7 @@ title: Include
|
||||
{% since %}v1.9.1{% endsince %}
|
||||
|
||||
{% note warn Deprecated %}
|
||||
This tag is deprecated, use <a href="./render.html">render</a> tag instead for better encapsulation.
|
||||
This tag is deprecated, use <a href="./render.html">render</a> tag instead, which contains all the features of `include` and provides better encapsulation.
|
||||
{% endnote %}
|
||||
|
||||
## Include a Template
|
||||
@@ -16,7 +16,7 @@ Renders a partial template from the template [roots][root].
|
||||
{% include 'footer.liquid' %}
|
||||
```
|
||||
|
||||
When the [extname][extname] option is set, the above `.liquid` extension can be omitted and writes:
|
||||
If [extname][extname] option is set, the above `.liquid` extension becomes optional:
|
||||
|
||||
```liquid
|
||||
{% include 'footer' %}
|
||||
@@ -44,5 +44,67 @@ A single object can be passed to a snippet by using the `with...as` syntax:
|
||||
|
||||
In the example above, the `product` variable in the partial template will hold the value of `featured_product` in the parent template.
|
||||
|
||||
## Outputs & Filters
|
||||
|
||||
When filename is specified as literal string, it supports Liquid output and filter syntax. Useful when concatenating strings for a complex filename.
|
||||
|
||||
```liquid
|
||||
{% include "prefix/{{name | append: \".html\"}}" %}
|
||||
```
|
||||
|
||||
{% note info Escaping %}
|
||||
In LiquidJS, `"` within quoted string literals need to be escaped by adding a slash before the quote, e.g. `\"`. Using Jekyll-like filenames can make this easier, see below.
|
||||
{% endnote %}
|
||||
|
||||
## Jekyll-like filenames
|
||||
|
||||
Setting [dynamicPartials][dynamicPartials] to `false` will enable Jekyll-like filenames, where file names are specified as literal string without surrounding quotes. Liquid outputs and filters are also supported within that, for example:
|
||||
|
||||
```liquid
|
||||
{% include prefix/{{ page.my_variable }}/suffix %}
|
||||
```
|
||||
|
||||
This way, you don't need to escape `"` in the filename expression.
|
||||
|
||||
```liquid
|
||||
{% include prefix/{{name | append: ".html"}} %}
|
||||
```
|
||||
|
||||
## Jekyll include
|
||||
|
||||
{% since %}v9.33.0{% endsince %}
|
||||
|
||||
[jekyllInclude][jekyllInclude] is used to enable Jekyll-like include syntax. Defaults to `false`, when set to `true`:
|
||||
|
||||
- Filename will be static: `dynamicPartials` now defaults to `false` (instead of `true`). And you can set `dynamicPartials` back to `true`.
|
||||
- Use `=` instead of `:` to separate parameter key-values.
|
||||
- Parameters are under `include` variable instead of current scope.
|
||||
|
||||
For example, the following template:
|
||||
|
||||
```liquid
|
||||
{% include article.html header="HEADER" content="CONTENT" %}
|
||||
```
|
||||
|
||||
`article.html` with following content:
|
||||
|
||||
```liquid
|
||||
<article>
|
||||
<header>{{include.header}}</header>
|
||||
{{include.content}}
|
||||
</article>
|
||||
```
|
||||
|
||||
Note that we're referencing the first parameter by `include.header` instead of `header`. Will output following:
|
||||
|
||||
```html
|
||||
<article>
|
||||
<header>HEADER</header>
|
||||
CONTENT
|
||||
</article>
|
||||
```
|
||||
|
||||
[extname]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-extname
|
||||
[root]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-root
|
||||
[dynamicPartials]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-dynamicPartials
|
||||
[jekyllInclude]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-jekyllInclude
|
||||
|
||||
+74
-33
@@ -4,21 +4,67 @@ title: Layout
|
||||
|
||||
{% since %}v1.9.1{% endsince %}
|
||||
|
||||
## Using a Layout Template
|
||||
## Using a Layout
|
||||
|
||||
Renders current template inside a layout template from the template [roots][root].
|
||||
Introduce a layout template for the current template to render in. The directory for layout files are defined by [layouts][layouts] or [root][root].
|
||||
|
||||
```liquid
|
||||
{% layout 'footer.liquid' %}
|
||||
// default-layout.liquid
|
||||
Header
|
||||
{% block %}{% endblock %}
|
||||
Footer
|
||||
|
||||
// page.liquid
|
||||
{% layout "default-layout.liquid" %}
|
||||
{% block %}My page content{% endblock %}
|
||||
|
||||
// result
|
||||
Header
|
||||
My page content
|
||||
Footer
|
||||
```
|
||||
|
||||
Whe the [extname][extname] option is set, the above `.liquid` extension can be omitted and writes:
|
||||
If [extname][extname] option is set, the `.liquid` extension becomes optional:
|
||||
|
||||
```liquid
|
||||
{% layout 'footer' %}
|
||||
{% layout 'default-layout' %}
|
||||
```
|
||||
|
||||
When a partial template is rendered by `layout`, the code inside it can access its caller's variables but its parent cannot access variables defined inside a included template.
|
||||
{% note info Scoping %}
|
||||
When a partial template is rendered by <code>layout</code>, its template have access for its caller's variables but not vice versa. Variables defined in layout will be popped out before control returning to its caller.
|
||||
{% endnote %}
|
||||
|
||||
## Multiple Blocks
|
||||
|
||||
The layout file can contain multiple blocks, each with a specified name. The following snippets yield same result as in the above example.
|
||||
|
||||
```liquid
|
||||
// default-layout.liquid
|
||||
{% block header %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
{% block footer %}{% endblock %}
|
||||
|
||||
// page.liquid
|
||||
{% layout "default-layout.liquid" %}
|
||||
{% block header %}Header{% endblock %}
|
||||
{% block content %}My page content{% endblock %}
|
||||
{% block footer %}Footer{% endblock %}
|
||||
```
|
||||
|
||||
## Default Block Contents
|
||||
|
||||
In the above layout files, blocks has empty contents. But it's not necessarily be empty, in which case, the block contents in layout files will be used as default templates. The following snippets are also equivalent to the above examples:
|
||||
|
||||
```liquid
|
||||
// default-layout.liquid
|
||||
{% block header %}Header{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
{% block footer %}Footer{% endblock %}
|
||||
|
||||
// page.liquid
|
||||
{% layout "default-layout.liquid" %}
|
||||
{% block content %}My page content{% endblock %}
|
||||
```
|
||||
|
||||
## Passing Variables
|
||||
|
||||
@@ -29,38 +75,33 @@ Variables defined in current template can be passed to a the layout template by
|
||||
{% layout 'name', my_variable: my_variable, my_other_variable: 'oranges' %}
|
||||
```
|
||||
|
||||
## Blocks
|
||||
## Outputs & Filters
|
||||
|
||||
The layout file can contain multiple `block`s which will be populated by the child template (the caller). For example we have a `default-layout.liquid` file with the following contents:
|
||||
When filename is specified as literal string, it supports Liquid output and filter syntax. Useful when concatenating strings for a complex filename.
|
||||
|
||||
```
|
||||
Header
|
||||
{% block content %}My default content{% endblock %}
|
||||
Footer
|
||||
```liquid
|
||||
{% layout "prefix/{{name | append: \".html\"}}" %}
|
||||
```
|
||||
|
||||
And it's called by a `page.liquid` file with `layout` tag:
|
||||
|
||||
```
|
||||
{% layout "default-layout" %}
|
||||
{% block content %}My page content{% endblock %}
|
||||
```
|
||||
|
||||
The render result of `page.liquid` will be :
|
||||
|
||||
```
|
||||
Header
|
||||
My page content
|
||||
Footer
|
||||
```
|
||||
|
||||
{% note tip Block %}
|
||||
<ul>
|
||||
<li>Multiple blocks can be defined within a layout template;</li>
|
||||
<li>The block name is optional when there's only one block.</li>
|
||||
<li>The block contents will fallback to parent's corresponding block if not provided by child template.</li>
|
||||
</ul>
|
||||
{% note info Escaping %}
|
||||
In LiquidJS, `"` within quoted string literals need to be escaped. Adding a slash before the quote, e.g. `\"`. Using Jekyll-like filenames can make this easier, see below.
|
||||
{% endnote %}
|
||||
|
||||
## Jekyll-like Filenames
|
||||
|
||||
Setting [dynamicPartials][dynamicPartials] to `false` will enable Jekyll-like filenames, file names are specified as literal string. And it also supports Liquid outputs and filters.
|
||||
|
||||
```liquid
|
||||
{% layout prefix/{{ page.my_variable }}/suffix %}
|
||||
```
|
||||
|
||||
This way, you don't need to escape `"` in the filename expression.
|
||||
|
||||
```liquid
|
||||
{% layout prefix/{{name | append: ".html"}} %}
|
||||
```
|
||||
|
||||
[extname]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-extname
|
||||
[root]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-root
|
||||
[layouts]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-layouts
|
||||
[dynamicPartials]: ../api/interfaces/liquid_options_.liquidoptions.html#dynamicPartials
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Liquid
|
||||
---
|
||||
|
||||
{% since %}v9.31.0{% endsince %}
|
||||
|
||||
Encloses multiple tags within one set of delimiters, to allow writing Liquid logic more concisely.
|
||||
|
||||
## liquid
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% liquid
|
||||
assign names = 'Bob, Sally' | split: ', '
|
||||
|
||||
for name in names
|
||||
echo 'Hello, ' | append: name
|
||||
unless forloop.last
|
||||
echo ', '
|
||||
endunless
|
||||
endfor
|
||||
%}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
Hello, Bob, Hello Sally
|
||||
```
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Tags
|
||||
description: Description and demo for each Liquid tag
|
||||
---
|
||||
|
||||
LiquidJS implements business-logic independent tags that are typically implemented in [shopify/liquid][shopify/liquid]. This section contains the specification and demoes for all the tags implemented by LiquidJS.
|
||||
@@ -10,8 +11,8 @@ Category | Purpose | Tags
|
||||
--- | --- | ---
|
||||
Iteration | iterate over a collection | for, cycle, tablerow
|
||||
Control Flow | control the execution branch of template rendering | if, unless, elsif, else, case, when
|
||||
Variable | define and alter variables | assign, increment, decrement
|
||||
Variable | define and alter variables | assign, increment, decrement, capture, echo
|
||||
File | include another template or extend a layout template | render, include, layout
|
||||
Language | temporarily disable LiquidJS syntax | raw, comment
|
||||
Language | temporarily disable LiquidJS syntax | raw, comment, liquid
|
||||
|
||||
[shopify/liquid]: https://github.com/Shopify/liquid
|
||||
|
||||
@@ -4,26 +4,33 @@ title: Render
|
||||
|
||||
{% since %}v9.2.0{% endsince %}
|
||||
|
||||
## Basic Usage
|
||||
## Render a Template
|
||||
|
||||
### Render a Template
|
||||
|
||||
Renders a partial template from the template [root][root]s.
|
||||
Render a partial template from partials directory specified by [partials][partials] or [root][root].
|
||||
|
||||
```liquid
|
||||
// index.liquid
|
||||
Contents
|
||||
{% render 'footer.liquid' %}
|
||||
|
||||
// footer.liquid
|
||||
Footer
|
||||
|
||||
// result
|
||||
Contents
|
||||
Footer
|
||||
```
|
||||
|
||||
When the [extname][extname] option is set, the above `.liquid` extension can be omitted and writes:
|
||||
If [extname][extname] option is set, the above `.liquid` extension becomes optional:
|
||||
|
||||
```liquid
|
||||
{% render 'footer' %}
|
||||
```
|
||||
|
||||
{% note info Variable Scope %}
|
||||
When a partial template is rendered, the code inside it can't access its parent's variables and its variables won't be accessible by its parent. This encapsulation helps make theme code easier to understand and maintain.{% endnote %}
|
||||
When a partial template is rendered, the code inside it can't access its parent's variables and its variables won't be accessible by its parent. This encapsulation makes partials easier to understand and maintain.{% endnote %}
|
||||
|
||||
### Passing Variables
|
||||
## Passing Variables
|
||||
|
||||
Variables defined in parent's scope can be passed to a the partial template by listing them as parameters on the render tag:
|
||||
|
||||
@@ -34,6 +41,32 @@ Variables defined in parent's scope can be passed to a the partial template by l
|
||||
|
||||
[globals][globals] don't need to be passed down. They are accessible from all files.
|
||||
|
||||
## Outputs & Filters
|
||||
|
||||
When filename is specified as literal string, it supports Liquid output and filter syntax. Useful when concatenating strings for a complex filename.
|
||||
|
||||
```liquid
|
||||
{% render "prefix/{{name | append: \".html\"}}" %}
|
||||
```
|
||||
|
||||
{% note info Escaping %}
|
||||
In LiquidJS, `"` within quoted string literals need to be escaped. Adding a slash before the quote, e.g. `\"`. Using Jekyll-like filenames can make this easier, see below.
|
||||
{% endnote %}
|
||||
|
||||
## Jekyll-like Filenames
|
||||
|
||||
Setting [dynamicPartials][dynamicPartials] to `false` will enable Jekyll-like filenames, file names are specified as literal string. And it also supports Liquid outputs and filters.
|
||||
|
||||
```liquid
|
||||
{% render prefix/{{ page.my_variable }}/suffix %}
|
||||
```
|
||||
|
||||
This way, you don't need to escape `"` in the filename expression.
|
||||
|
||||
```liquid
|
||||
{% render prefix/{{name | append: ".html"}} %}
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
### The `with` Parameter
|
||||
@@ -63,4 +96,6 @@ In the example above, the partial template will be rendered once for each `varia
|
||||
[forloop]: ./for.html
|
||||
[extname]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-extname
|
||||
[root]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-root
|
||||
[partials]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-partials
|
||||
[globals]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-globals
|
||||
[dynamicPartials]: ../api/interfaces/liquid_options_.liquidoptions.html#dynamicPartials
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Access Scope in Filters
|
||||
---
|
||||
|
||||
As covered in [Register Filters/Tags][register-filters], we can access filter arguments directly in filter function like:
|
||||
|
||||
```javascript
|
||||
// Usage: {{ 1 | add: 2, 3 }}
|
||||
// Output: 6
|
||||
engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2)
|
||||
```
|
||||
|
||||
When it comes to stateful filters, for example transform a URL path to full URL, we'll need to access a `origin` in current scope:
|
||||
|
||||
```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()
|
||||
})
|
||||
```
|
||||
|
||||
See this JSFiddle: <http://jsfiddle.net/ctj364up/1/>
|
||||
|
||||
{% note warn Arrow Functions %}
|
||||
<code>this</code> in arrow functions is bound to current JavaScript context, you'll need to use <code>function(){}</code> instead of <code>()=>{}</code> syntax to access <code>this.context</code> correctly.
|
||||
{% endnote %}
|
||||
|
||||
[register-filters]: /tutorials/register-filters-tags.html
|
||||
@@ -2,6 +2,10 @@
|
||||
title: Contribution Guideline
|
||||
---
|
||||
|
||||
## 👉👉👉 Star LiquidJS [][liquidjs]
|
||||
|
||||
Starring LiquidJS is the most important and easiest way to support us: boost its rank and expose it to more people, which in turn makes it better.
|
||||
|
||||
## 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:
|
||||
@@ -18,19 +22,23 @@ 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 👉 [][liquidjs]
|
||||
|
||||
This is the easiest way to support us: boost its rank and expose it to more people, which in turn makes it better.
|
||||
**Backward-Compatibility**: please be backward-compatible. LiquidJS is used by multiple layers of softwares, including underlying libraries, compilers, site generators and Web servers. It's not easy to do a major upgrade for most of them.
|
||||
|
||||
## 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].
|
||||
LiquidJS is Open Source and Free. To help it live and thrive, please consider contribute on [Open Collective][oc] or [Patreon][pt]. To acknowledge your contribution, your name and avatar will be listed here, on LiquidJS [homepage](https://liquidjs.com/) and on [Github README][liquidjs].
|
||||
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/tiers/backer.svg?avatarHeight=72"></object>
|
||||
### Backers
|
||||
If you personally like LiquidJS and find it's useful to you, you can become a backer!
|
||||
|
||||
[](https://www.patreon.com/bePatron?u=32321060)
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/backers.svg?avatarHeight=72"></object>
|
||||
|
||||
[oc]: https://opencollective.com/liquidjs/
|
||||
### Sponsors
|
||||
If LiquidJS is benefiting your business/company, please sponsor us to make it better!
|
||||
|
||||
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/sponsors.svg?avatarHeight=72"></object>
|
||||
|
||||
[oc]: https://opencollective.com/liquidjs/contribute/backer-ii-10666/checkout
|
||||
[pt]: https://www.patreon.com/harttle
|
||||
[shopify/liquid]: https://shopify.github.io/liquid/
|
||||
[caniuse-promises]: http://caniuse.com/#feat=promises
|
||||
|
||||
@@ -2,7 +2,25 @@
|
||||
title: Differences with Shopify/liquid
|
||||
---
|
||||
|
||||
All filters and tags in [shopify/liquid](https://github.com/Shopify/liquid) are supposed to be built in LiquidJS, but not those business-logic specific tags/filters which are typically from Shopify themes (see [Plugins List][plugins] in case you're looking for them and feel free to add yours to the list). Though being compatible the Ruby version is one of our priorities, there are still some differences:
|
||||
## Compatibility
|
||||
|
||||
Being compatible the Ruby version is one of our priorities. Liquid language is originally [implemented in Ruby][ruby-liquid] and used by Shopify, Jekyll and thus Github Pages, as you can see it's one of the most popular template engines in Ruby. There're lots of people using LiquidJS to serve their templates originally written for Shopify themes and Jekyll sites.
|
||||
|
||||
So "being compatible" means serve developers from Shopify and Jekyll well:
|
||||
|
||||
- **Well-formed Liquid template should work just fine in LiquidJS**. For example, `forloop.index` should be 1-indexed, `nil` should be rendered as empty string rather than `undefined`, etc. Although some features (e.g. [#236][#236]) are not feasible in JavaScript, at least we're trying to implement all the semantics of Liquid language.
|
||||
- **All filters and tags in [shopify/liquid][ruby-liquid] are supposed to be built in LiquidJS**. But not those business-logic specific tags/filters typically defined by Shopify platform. Those features should be maintained as [plugins][plugins]. For filters/tags that are not business-logic specific, like `{% layout %}`, and extremely useful, feel free to file an issue.
|
||||
|
||||
In the meantime, it's now implemented in JavaScript, that means it has to be more powerful:
|
||||
|
||||
* **Async as first-class citizen**. Filters and tags can be implemented asynchronously by return a `Promise`.
|
||||
* **Also can be sync**. For scenarios that are not I/O intensive, render synchronously can be much faster. You can call synchronous APIs like `.renderSync()` as long as all the filters and tags in template support to be rendered synchronously. All builtin filters/tags support both sync and async render.
|
||||
* **[Abstract file system][afs]**. Along with async feature, LiquidJS can be used to serve templates stored in Databases [#414][#414], on remote HTTP server [#485][#485], and so on.
|
||||
* **Additional tags and filters** like `layout` and `json`.
|
||||
|
||||
## Differences
|
||||
|
||||
Though we're trying to be compatible with the Ruby version, there are still some differences:
|
||||
|
||||
* Truthy and Falsy. All values except `undefined`, `null`, `false` are truthy, whereas in Ruby Liquid all except `nil` and `false` are truthy. See [#26][#26].
|
||||
* Number. In JavaScript we cannot distinguish or convert between `float` and `integer`, see [#59][#59]. And when applied `size` filter, numbers always return 0, which is 8 for integer in ruby, cause they do not have a `length` property.
|
||||
@@ -11,11 +29,20 @@ All filters and tags in [shopify/liquid](https://github.com/Shopify/liquid) are
|
||||
* Iteration order for objects. The iteration order of JavaScript objects, and thus LiquidJS objects, is a combination of the insertion order for string keys, and ascending order for number-like keys, while the iteration order of Ruby Hash is simply the insertion order.
|
||||
* Sort stability. The [sort][sort] stability is also not defined in both shopify/liquid and LiquidJS, but it's [considered stable][stable-sort] for LiquidJS in Node.js 12+ and Google Chrome 70+.
|
||||
* Trailing unmatched characters inside filters are allowed in shopify/liquid but not in LiquidJS. It means filter arguments without a colon like `{%raw%}{{ "a b" | split " "}}{%endraw%}` will throw an error in LiquidJS. This is intended to improve Liquid usability, see [#208][#208] and [#212][#212].
|
||||
* LiquidJS has additional tags: [layout][layout] and corresponding `block` tag.
|
||||
* LiquidJS has additional filters: [json][json].
|
||||
|
||||
[layout]: https://liquidjs.com/tags/layout.html
|
||||
[json]: https://liquidjs.com/filters/json.html
|
||||
[#26]: https://github.com/harttle/liquidjs/pull/26
|
||||
[#59]: https://github.com/harttle/liquidjs/issues/59
|
||||
[#208]: https://github.com/harttle/liquidjs/issues/208
|
||||
[#212]: https://github.com/harttle/liquidjs/issues/212
|
||||
[#236]: https://github.com/harttle/liquidjs/issues/236
|
||||
[#414]: https://github.com/harttle/liquidjs/discussions/414
|
||||
[#485]: https://github.com/harttle/liquidjs/discussions/485
|
||||
[sort]: https://liquidjs.com/filters/sort.html
|
||||
[stable-sort]: https://v8.dev/features/stable-sort
|
||||
[plugins]: ./plugins.html#Plugin-List
|
||||
[ruby-liquid]: https://github.com/Shopify/liquid
|
||||
[afs]: https://liquidjs.com/tutorials/render-file.html#Abstract-File-System
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
---
|
||||
title: Introduction to Liquid Template Language
|
||||
title: The Liquid Template Language
|
||||
describe: A short introduction to the Liquid template language and some simple demos.
|
||||
---
|
||||
|
||||
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. Liquid is originally implemented in Ruby and used by Github Pages, Jekyll and Shopify, see [Differences with Shopify/liquid][diff].
|
||||
LiquidJS is a simple, expressive and safe [Shopify][shopify/liquid] / Github Pages compatible template engine in pure JavaScript. The purpose of this repo is to provide a standard Liquid implementation for the JavaScript community. Liquid is originally implemented in Ruby and used by Github Pages, Jekyll and Shopify, see [Differences with Shopify/liquid][diff].
|
||||
|
||||
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 `}}`.
|
||||
|
||||
{% note info Live Demo %}
|
||||
Before going into the details, here's a live demo to play around: <https://liquidjs.com/playground.html>.
|
||||
{% endnote %}
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Options
|
||||
---
|
||||
|
||||
The [Liquid][liquid] constructor accepts a plain object as options to define the behaviour of LiquidJS. All of these options are optional thus we can specify any of them, for example the `cache` option:
|
||||
The [Liquid][liquid] constructor accepts a plain object as options to define the behavior of LiquidJS. All of these options are optional thus we can specify any of them, for example the `cache` option:
|
||||
|
||||
```javascript
|
||||
const { Liquid } = require('liquidjs')
|
||||
@@ -23,8 +23,20 @@ It's default to `false`. When setting to `true` a default LRU cache of size 1024
|
||||
|
||||
Additionally, it can also be a custom cache implementation. See [Caching][caching] for details.
|
||||
|
||||
## Partials/Layouts
|
||||
|
||||
**root** is used to specify template directories for LiquidJS to lookup and read template files. Can be a single string and an array of strings. See [Render Files][render-file] for details.
|
||||
|
||||
**layouts** is used to specify template directories for LiquidJS to lookup files for `{% layout %}`. Same format as `root` and will default to `root` if not specified.
|
||||
|
||||
**partials** is used to specify template directories for LiquidJS to lookup files for `{% render %}` and `{% include %}`. Same format as `root` and will default to `root` if not specified.
|
||||
|
||||
**relativeReference** is set to `true` by default to allow relative filenames. Note that relatively referenced files are also need to be within corresponding root. For example you can reference another file like `{% render ../foo/bar %}` as long as `../foo/bar` is also within `partials` directory.
|
||||
|
||||
## dynamicPartials
|
||||
|
||||
> Note: for historical reasons, it's named dynamicPartials but it also works for layouts.
|
||||
|
||||
**dynamicPartials** indicates whether or not to treat filename arguments in [include][include], [render][render], [layout][layout] tags as a variable. Defaults to `true`. For example, render the following snippet with scope `{ file: 'foo.html' }` will include the `foo.html`:
|
||||
|
||||
```liquid
|
||||
@@ -40,9 +52,32 @@ Setting `dynamicPartials: false`, LiquidJS will try to include the file named `f
|
||||
{% note warn Common Pitfall %}
|
||||
LiquidJS defaults this option to <code>true</code> to be compatible with shopify/liquid, but if you're from <a href="https://github.com/11ty/eleventy" target="_blank">eleventy</a> it's set to <code>false</code> by default (see <a href="https://www.11ty.dev/docs/languages/liquid/#quoted-include-paths" target="_blank">Quoted Include Paths</a>) which I believe is trying to be compatible with Jekyll.{% endnote %}
|
||||
|
||||
## Jekyll include
|
||||
|
||||
{% since %}v9.33.0{% endsince %}
|
||||
|
||||
[jekyllInclude][jekyllInclude] is used to enable Jekyll-like include syntax. Defaults to `false`, when set to `true`:
|
||||
|
||||
- Filename will be static: `dynamicPartials` now defaults to `false` (instead of `true`). And you can set `dynamicPartials` back to `true`.
|
||||
- Use `=` instead of `:` to separate parameter key-values.
|
||||
- Parameters are under `include` variable instead of current scope.
|
||||
|
||||
For example in the following template, `name.html` is not quoted, `header` and `"HEADER"` are separated by `=`, and the `header` parameter is referenced by `include.header`. More details please check out [include][include].
|
||||
|
||||
```liquid
|
||||
// entry template
|
||||
{% include article.html header="HEADER" content="CONTENT" %}
|
||||
|
||||
// article.html
|
||||
<article>
|
||||
<header>{{include.header}}</header>
|
||||
{{include.content}}
|
||||
</article>
|
||||
```
|
||||
|
||||
## extname
|
||||
|
||||
**extname** defines the default extname to be appended into filenames if the filename has no extname. Defaults to `''` which means it's disabled by default. By setting it to `.liquid`:
|
||||
**extname** defines the default extension name to be appended into filenames if the filename has no extension name. Defaults to `''` which means it's disabled by default. By setting it to `.liquid`:
|
||||
|
||||
```liquid
|
||||
{% render "foo" %} there's no extname, adds `.liquid` and loads foo.liquid
|
||||
@@ -53,10 +88,6 @@ LiquidJS defaults this option to <code>true</code> to be compatible with shopify
|
||||
Before 2.0.1, <code>extname</code> is set to `.liquid` by default. To change that you need to set <code>extname: ''</code> explicitly. See <a href="https://github.com/harttle/liquidjs/issues/41" target="_blank">#41</a> for details.
|
||||
{% endnote %}
|
||||
|
||||
## root
|
||||
|
||||
**root** is used to specify template directories for LiquidJS to lookup and read template files. Can be a single string and an array of strings. See [Render Files][render-file] for details.
|
||||
|
||||
## fs
|
||||
|
||||
**fs** is used to define a custom file system implementation which will be used by LiquidJS to lookup and read template files. See [Abstract File System][abstract-fs] for details.
|
||||
@@ -71,9 +102,15 @@ Before 2.0.1, <code>extname</code> is set to `.liquid` by default. To change tha
|
||||
|
||||
it defaults to false. For example, when set to true, a blank string would evaluate to false with jsTruthy. With Shopify's truthiness, a blank string is true.
|
||||
|
||||
## Date
|
||||
|
||||
**timezoneOffset** is used to specify a different timezone to output dates, your local timezone will be used if not specified. For example, set `timezoneOffset: 0` to output all dates in UTC/GMT 00:00.
|
||||
|
||||
**preserveTimezones** is a boolean effects only literal timestamps. When set to `true`, all literal timestamps will remain the same when output. This is a parser option, so Date objects passed to LiquidJS as data will not be affected. Note that `preserveTimezones` has a higher priority than `timezoneOffset`.
|
||||
|
||||
## Trimming
|
||||
|
||||
**greedy**, **trimOutputLeft**, **trimOutputRight**, **trimTagLeft**, **trimTagRight** options are used to eliminate extra newlines and indents in templates arround Liquid Constructs. See [Whitespace Control][wc] for details.
|
||||
**greedy**, **trimOutputLeft**, **trimOutputRight**, **trimTagLeft**, **trimTagRight** options are used to eliminate extra newlines and indents in templates around Liquid Constructs. See [Whitespace Control][wc] for details.
|
||||
|
||||
## Delimiter
|
||||
|
||||
@@ -91,10 +128,16 @@ it defaults to false. For example, when set to true, a blank string would evalu
|
||||
|
||||
**lenientIf** modifies the behavior of `strictVariables` to allow handling optional variables. If set to `true`, an undefined variable will *not* cause an exception in the following two situations: a) it is the condition to an `if`, `elsif`, or `unless` tag; b) it occurs right before a `default` filter. Irrelevant if `strictVariables` is not set. Defaults to `false`.
|
||||
|
||||
**ownPropertyOnly** hides scope variables from prototypes, useful when you're passing a not sanitized object into LiquidJS or need to hide prototypes from templates. Defaults to `false`.
|
||||
|
||||
{% note info Non-existent Tags %}
|
||||
Non-existent tags always throw errors during pasrsing and this behaviour can not be customized.
|
||||
Non-existent tags always throw errors during parsing and this behavior can not be customized.
|
||||
{% endnote %}
|
||||
|
||||
## Parameter Order
|
||||
|
||||
Parameter orders are ignored by default, for ea `{% for i in (1..8) reversed limit:3 %}` will always perform `limit` before `reversed`, even if `reversed` occurs before `limit`. To make parameter order respected, set **orderedFilterParameters** to `true`. Its default value is `false`.
|
||||
|
||||
[liquid]: ../api/classes/liquid_.liquid.html
|
||||
[caching]: ./caching.html
|
||||
[abstract-fs]: ./render-file.html#Abstract-File-System
|
||||
@@ -105,3 +148,4 @@ Non-existent tags always throw errors during pasrsing and this behaviour can not
|
||||
[layout]: ../tags/layout.html
|
||||
[wc]: ./whitespace-control.html
|
||||
[intro]: ./intro-to-liquid.html
|
||||
[jekyllInclude]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-jekyllInclude
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Parse Parameters
|
||||
---
|
||||
|
||||
## Access Raw Parameters
|
||||
|
||||
As covered in [Register Filters/Tags][register-tags], tag parameters is available on `tagToken.args` as a raw string. For example:
|
||||
|
||||
```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])
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Here's a JSFiddle version: <http://jsfiddle.net/ctj364up/2/>
|
||||
|
||||
## Parse Parameters as Values
|
||||
|
||||
Sometimes we need more dynamic tags and want to pass values to the custom tag instead of static strings. Variables in LiquidJS can be literal (string, number, etc.) or a variable from current context scope.
|
||||
|
||||
The following modified template also contains 3 values to random from, but they're values instead of static strings. The first one is string literal, second one is an identifier, third one is a property access sequence containing two identifiers.
|
||||
|
||||
```liquid
|
||||
{% random "foo" bar obj.coo %}
|
||||
```
|
||||
|
||||
It can be tricky to parse all these cases manually, but there's a [Tokenizer][Tokenizer] class in LiquidJS you can make use of.
|
||||
|
||||
```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)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Calling this tag in scope `{ bar: "bar", obj: { coo: "coo" } }` yields exactly the same result as the first example. See this JSFiddle: <http://jsfiddle.net/ctj364up/3/>
|
||||
|
||||
{% note info Async ans Promises %}
|
||||
Async calls in LiquidJS are implemented by generators directly, for we can call generators in synchronous manner so this tag implementation is also valid for `renderSync()`, `parseAndRenderSync()`, `renderFileSync()`. If you need to await a promise in tag implementation, simply replace `await somePromise` with `yield somePromise` and keep `* render()` instead of `async render()` will do the trick. See <a href="/tutorials/sync-and-async.html">Sync and Async</a> for more details.
|
||||
{% endnote %}
|
||||
|
||||
## Parse Key-Value Pairs as Named Parameters
|
||||
|
||||
Named parameters become very handy when there're optional parameters or lots of parameters, in which case the order of parameters is not important. This is exactly what [Hash][Hash] class is invented for.
|
||||
|
||||
```liquid
|
||||
{% random from:2, to:max %}
|
||||
```
|
||||
|
||||
In the above example, we're trying to generate a random number in the range [2, max]. We'll use `Hash` to parse `from` and `to` parameters.
|
||||
|
||||
```javascript
|
||||
const { Liquid, Hash } = require('liquidjs')
|
||||
|
||||
engine.registerTag('random', {
|
||||
parse(tagToken) {
|
||||
// parse the parameters structure into `this.args`
|
||||
this.args = new Hash(tagToken.args)
|
||||
},
|
||||
* render(context, emitter) {
|
||||
// evaluate the parameters in `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)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Rendering `{% random from:2, to:max %}` in scope `{ max: 10 }` will generate a random number in the range [2, 10]. See this 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
|
||||
@@ -28,7 +28,7 @@ color: 'yellow' shape: 'square'
|
||||
More details please refer to the [render](../tags/render.html) tag.
|
||||
|
||||
{% note tip The ".liquid" Extension %}
|
||||
The ".liquid" extension in <code>layout</code>, <code>render</code> and <code>include</code> an be omitted if Liquid instance is created using `extname: ".liquid"` option. See <a href="./options.html#extname">the extname option</a> for details.
|
||||
The ".liquid" extension in <code>layout</code>, <code>render</code> and <code>include</code> can be omitted if Liquid instance is created using `extname: ".liquid"` option. See <a href="./options.html#extname">the extname option</a> for details.
|
||||
{% endnote %}
|
||||
|
||||
## Layout Templates (Extends)
|
||||
|
||||
@@ -61,4 +61,4 @@ function disabledFilter(name) {
|
||||
}
|
||||
}
|
||||
engine.registerFilter('plus', disabledFilter('plus'));
|
||||
```
|
||||
```
|
||||
@@ -68,7 +68,7 @@ Here's a demo for browsers: [demo/browser](https://github.com/harttle/liquidjs/t
|
||||
|
||||
## 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.
|
||||
LiquidJS defines an abstract file system interface in [src/fs/fs.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:
|
||||
|
||||
@@ -78,14 +78,17 @@ var engine = new Liquid({
|
||||
readFileSync (file) {
|
||||
return db.model('Template').findByIdSync(file).text
|
||||
},
|
||||
await readFile (file) {
|
||||
async readFile (file) {
|
||||
const template = await db.model('Template').findById(file)
|
||||
return template.text
|
||||
},
|
||||
existsSync () {
|
||||
return true
|
||||
},
|
||||
await exists () {
|
||||
exists () {
|
||||
return true
|
||||
},
|
||||
contains () {
|
||||
return true
|
||||
},
|
||||
resolve(root, file, ext) {
|
||||
@@ -95,8 +98,10 @@ var engine = new Liquid({
|
||||
});
|
||||
```
|
||||
|
||||
{% note warn Path Traversal Vulnerability %}The default value of <code>contains()</code> always returns true. That means when specifying an abstract file system, you'll need to provide a proper <code>contains()</code> to avoid expose such vulnerabilities.{% endnote %}
|
||||
|
||||
[fs]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-fs
|
||||
[ifs]: https://github.com/harttle/liquidjs/blob/master/src/fs/ifs.ts
|
||||
[ifs]: https://github.com/harttle/liquidjs/blob/master/src/fs/fs.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
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
title: Render Tag Content
|
||||
---
|
||||
|
||||
Custom tags can have content template and can be nested. This article describes how to implement custom tags that consists of a *begin tag*, an *end tag*, and template content between them.
|
||||
|
||||
## Render Tag Content
|
||||
|
||||
We'll start with a simple tag `wrap` which wraps its content into a `<div class="wrapper"></div>` element:
|
||||
|
||||
```liquid
|
||||
{% wrap %}
|
||||
{{ "hello world!" | capitalize }}
|
||||
{% endwrap %}
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```html
|
||||
<div class='wrapper'>
|
||||
Hello world!
|
||||
</div>
|
||||
```
|
||||
|
||||
Firstly, [register][register-tags] a tag with name `wrap` and parse the content into `this.tpls`. Here in `parse(tagToken, remainTokens)`,
|
||||
|
||||
- `tagToken` is current token `{% wrap %}`, and
|
||||
- `remainTokens` is an array of all tokens following `{% wrap %}` until the end of this template file.
|
||||
|
||||
Basically, what we need to do is take/`.shift()` enough tags from `remainTokens` until we got a `endwrap` token (the name can be arbitrary, but in convention, we need it to be `endwrap`). And if there's no `endwrap` until the end of template file, we need to throw an tag-not-closed `Error`.
|
||||
|
||||
```javascript
|
||||
engine.registerTag('wrap', {
|
||||
parse(tagToken, remainTokens) {
|
||||
this.tpls = []
|
||||
let closed = false
|
||||
while(remainTokens.length) {
|
||||
let token = remainTokens.shift()
|
||||
// we got the end tag! stop taking tokens
|
||||
if (token.name === 'endwrap') {
|
||||
closed = true
|
||||
break
|
||||
}
|
||||
// parse token into template
|
||||
// parseToken() may consume more than 1 tokens
|
||||
// e.g. {% if %}...{% endif %}
|
||||
let tpl = this.liquid.parser.parseToken(token, remainTokens)
|
||||
this.tpls.push(tpl)
|
||||
}
|
||||
if (!closed) throw new Error(`tag ${tagToken.getText()} not closed`)
|
||||
},
|
||||
* render(context, emitter) {
|
||||
emitter.write("<div class='wrapper'>")
|
||||
yield this.liquid.renderer.renderTemplates(this.tpls, context, emitter)
|
||||
emitter.write("</div>")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
`.renderTemplates()` can be async, we need `yield` to wait it complete. More details on async in LiquidJS, please refer to [Sync and Async][async]. Other parts of `render()` method is quite straightforward. Here's a JSFiddle version: <http://jsfiddle.net/por0zcn1/3/>
|
||||
|
||||
## 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` and does exactly the same as above example.
|
||||
|
||||
```javascript
|
||||
parse(tagToken, remainTokens) {
|
||||
this.tpls = []
|
||||
this.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() })
|
||||
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
|
||||
.start()
|
||||
}
|
||||
```
|
||||
|
||||
Here's a JSFiddle version: <http://jsfiddle.net/por0zcn1/4/>. For simplicity, the following examples are implemented using `ParseStream`.
|
||||
|
||||
## Manipulate the Context
|
||||
|
||||
The `wrap` tag above doesn't seem to be very useful, without using that tag we can render the content anyway. Now we're going to implement a `repeat` tag to render the content 2 times (we can also add a [parameter][parameter] to render arbitrary times).
|
||||
|
||||
```liquid
|
||||
{% repeat %}
|
||||
{{ repeat.i }}. {{ "hello world!" | capitalize }}
|
||||
{% endrepeat %}`
|
||||
```
|
||||
|
||||
Expected outputs:
|
||||
|
||||
```html
|
||||
1. Hello world!
|
||||
2. Hello world!
|
||||
```
|
||||
|
||||
As you've noticed, there's an additional `repeat.i` in the context of `repeat`. That is implemented by manipulating the *Context*.
|
||||
|
||||
{% note info Context %}
|
||||
<em>Context</em> defines the value of each variable in Liquid template. In LiquidJS, a `Context` consists of a stack of `Scope`s. A *Scope* is a plain object like the one specified in `engine.render(tpl, scope)`.
|
||||
{% endnote %}
|
||||
|
||||
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)
|
||||
.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 })
|
||||
yield this.liquid.renderer.renderTemplates(this.tpls, context, emitter)
|
||||
repeat.i++
|
||||
yield this.liquid.renderer.renderTemplates(this.tpls, context, emitter)
|
||||
context.pop()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
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: <http://jsfiddle.net/por0zcn1/2/>
|
||||
|
||||
{% 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.
|
||||
{% endnote %}
|
||||
|
||||
[register-tags]: ./register-filters-tags.html
|
||||
[async]: ./sync-and-async.html
|
||||
[for]: ../tags/for.html
|
||||
[if]: ../tags/if.html
|
||||
[ParseStream]: ../api/classes/parser_parse_stream_.parsestream.html
|
||||
[parameter]: ./parse-parameters.html
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: Sync and Async
|
||||
---
|
||||
|
||||
LiquidJS supports both sync and async evaluate, and can be used with Promises. To reuse the same set of tag/filter implementations in both sync and async, LiquidJS tags are implemented as generators.
|
||||
|
||||
## Sync and Async API
|
||||
|
||||
All major methods on [Liquid][Liquid] supports both sync and async. These methods return Promises:
|
||||
|
||||
- `render()`
|
||||
- `renderFile()`
|
||||
- `parseFile()`
|
||||
- `parseAndRender()`
|
||||
- `evalValue()`
|
||||
|
||||
The synchronous version of methods contains a `Sync` suffix:
|
||||
|
||||
- `renderSync()`
|
||||
- `renderFileSync()`
|
||||
- `parseFileSync()`
|
||||
- `parseAndRenderSync()`
|
||||
- `evalValueSync()`
|
||||
|
||||
## Implement Sync-Compatible Tags
|
||||
|
||||
### Requirements
|
||||
|
||||
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)`:
|
||||
|
||||
- Should not directly `return <Promise>`, and
|
||||
- Should not be declared as `async`.
|
||||
|
||||
{% 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.
|
||||
|
||||
```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) {
|
||||
// _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()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
See this JSFiddle: <http://jsfiddle.net/ctj364up/6/>.
|
||||
|
||||
## 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:
|
||||
|
||||
```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()
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
See this JSFiddle: <http://jsfiddle.net/ctj364up/5/>.
|
||||
|
||||
[Liquid]: /api/classes/liquid_.liquid.html
|
||||
[toPromise]: /api/modules/liquid_.html#toPromise
|
||||
@@ -41,7 +41,7 @@ Alternatively, LiquidJS provides these per engine options to enable whitespace c
|
||||
|
||||
* `trimTagLeft`
|
||||
* `trimTagRight`
|
||||
* `trimValueRight`
|
||||
* `trimValueLeft`
|
||||
* `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].
|
||||
@@ -53,4 +53,4 @@ In greedy mode (enabled by the [greedy option][greedy]), all consecutive whitesp
|
||||
[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
|
||||
[greedy]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-greedy
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
title: concat
|
||||
---
|
||||
|
||||
Concatenates (joins together) multiple arrays. The resulting array contains all the items from the input arrays.
|
||||
{% since %}v2.0.0{% endsince %}
|
||||
|
||||
连接多个数组,返回的数组包含所有传入数组的元素。
|
||||
|
||||
输入
|
||||
```liquid
|
||||
@@ -26,7 +28,7 @@ Concatenates (joins together) multiple arrays. The resulting array contains all
|
||||
- potatoes
|
||||
```
|
||||
|
||||
You can string together `concat` filters to join more than two arrays:
|
||||
可以链式地使用 `concat` 过滤器来连接多个数组:
|
||||
|
||||
输入
|
||||
```liquid
|
||||
|
||||
@@ -16,6 +16,10 @@ title: date
|
||||
Fri, Jul 17, 15
|
||||
```
|
||||
|
||||
{% note info 时区 %}
|
||||
日期在输出时会转换为当地时区,设置 `timezoneOffset` LiquidJS 参数可以指定一个不同的时区。或者设置 `preserveTimezones` 为 `true` 来保持字面量时间戳的时区,数据中的日期对象不受此参数的影响。
|
||||
{% endnote %}
|
||||
|
||||
输入
|
||||
```liquid
|
||||
{{ article.published_at | date: "%Y" }}
|
||||
@@ -38,6 +42,10 @@ Fri, Jul 17, 15
|
||||
Mar 14, 16
|
||||
```
|
||||
|
||||
{% note info 时间戳字符串 %}
|
||||
LiquidJS 使用 JavaScript [Date][newDate] 来解析输入字符串,意味着支持 [IETF-compliant RFC 2822 时间戳](https://datatracker.ietf.org/doc/html/rfc2822#page-14) 和 [特定版本的 ISO8601](https://www.ecma-international.org/ecma-262/11.0/#sec-date.parse)。
|
||||
{% endnote %}
|
||||
|
||||
可以用特殊值 `"now"`(或`"today"`)来获取当前时间:
|
||||
|
||||
输入
|
||||
|
||||
@@ -42,4 +42,23 @@ title: default
|
||||
2.99
|
||||
```
|
||||
|
||||
## 允许 `false`
|
||||
|
||||
{% since %}v9.32.0{% endsince %}
|
||||
|
||||
为了允许让 `false` 直接输出而不是用默认值,可以用 `allow_false` 参数。
|
||||
|
||||
输入
|
||||
|
||||
```liquid
|
||||
{% assign display_price = false %}
|
||||
{{ display_price | default: true, allow_false: true }}
|
||||
```
|
||||
|
||||
输出
|
||||
|
||||
```text
|
||||
false
|
||||
```
|
||||
|
||||
[falsy]: ../tutorials/truthy-and-falsy.html
|
||||
|
||||
@@ -26,6 +26,23 @@ title: divided_by
|
||||
1.6666666666666667
|
||||
```
|
||||
|
||||
{% note info Integer Arithmetic %}Since JavaScript doesn't differentiate integers and floats, LiquidJS is not capable of integer arithmetic and the return type is always `number`, the string representation of which depends on its value.{% endnote %}
|
||||
在 JavaScript 里数字没有浮点和整数的区分,它们的类型都是 `number`:
|
||||
|
||||
```javascript
|
||||
// always true
|
||||
5.0 === 5
|
||||
```
|
||||
|
||||
因此如果需要做整数运算,需要传入额外的 `integerArithmetic` 参数:
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ 5 | divided_by: 3, true }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1
|
||||
```
|
||||
|
||||
[floor]: ./floor.html
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 过滤器
|
||||
description: 每个 Liquid 过滤器的描述和示例
|
||||
---
|
||||
|
||||
LiquidJS 支持 Liquid 语法中具体业务无关的过滤器,基本上 [shopify/liquid 核心][shopify/liquid] 支持的 LiquidJS 都支持。这部分包含了所有 LiquidJS 支持的过滤器的文档和使用示例。
|
||||
@@ -8,10 +9,11 @@ LiquidJS 共支持 40+ 个过滤器,可以分为如下几类:
|
||||
|
||||
类别 | 过滤器
|
||||
--- | ---
|
||||
数学 | plus, minus, modulo, times, floor, ceil, round, divided_by, abs
|
||||
数学 | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
|
||||
字符串 | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, remove, remove_first, truncate, truncatewords
|
||||
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
|
||||
数组 | slice, map, sort, sort_natural, uniq, wheres, first, last, join, reverse
|
||||
数组 | slice, map, sort, sort_natural, uniq, wheres, first, last, join, reverse, concat, compact, size
|
||||
日期 | date
|
||||
其他 | default, json
|
||||
|
||||
[shopify/liquid]: https://github.com/Shopify/liquid
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: raw
|
||||
---
|
||||
|
||||
{% since %}v9.37.0{% endsince %}
|
||||
|
||||
直接返回变量的值。配合 [outputEscape](/api/interfaces/liquid_options_.liquidoptions.html#Optional-outputEscape) 参数使用。
|
||||
|
||||
{% note info 自动转义 %}
|
||||
默认情况下 `outputEscape` 为 `undefined`,这意味着 LiquidJS 输出不会默认转义,因此这时使用 `raw` 没有意义。
|
||||
{% endnote %}
|
||||
|
||||
输入(未设置 `outputEscape`)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
<
|
||||
```
|
||||
|
||||
输入(`outputEscape="escape"`)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
<
|
||||
```
|
||||
|
||||
输入(`outputEscape="json"`)
|
||||
```liquid
|
||||
{{ "<" }}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
"<"
|
||||
```
|
||||
|
||||
输入(`outputEscape="escape"`)
|
||||
```liquid
|
||||
{{ "<" | raw }}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
<
|
||||
```
|
||||
|
||||
@@ -4,7 +4,7 @@ title: upcase
|
||||
|
||||
{% since %}v1.9.1{% endsince %}
|
||||
|
||||
字符串中每个字符都转为大写,对已经是小写的字符没有影响。
|
||||
字符串中每个字符都转为大写,对已经是大写的字符没有影响。
|
||||
|
||||
输入
|
||||
```liquid
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
layout: index
|
||||
description: LiquidJS is a simple, expressive and safe template engine.
|
||||
description: LiquidJS 是一个纯 JavaScript 实现的,简洁的、安全的模板引擎,兼容 Shopify / Github Pages。
|
||||
subtitle: 简单安全的 Liquid 模板引擎
|
||||
---
|
||||
ul#intro-feature-list
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"short_name": "LiquidJS",
|
||||
"name": "LiquidJS",
|
||||
"description": "LiquidJS 是一个简单的、安全的、兼容 Shopify 的、纯 JavaScript 编写的模板引擎。",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon/apple-touch-icon-57x57.png",
|
||||
"type": "image/png",
|
||||
"sizes": "57x57"
|
||||
},
|
||||
{
|
||||
"src": "/icon/favicon-96x96.png",
|
||||
"type": "image/png",
|
||||
"sizes": "96x96"
|
||||
},
|
||||
{
|
||||
"src": "/icon/favicon-196x196.png",
|
||||
"type": "image/png",
|
||||
"sizes": "196x196"
|
||||
},
|
||||
{
|
||||
"src": "/icon/apple-touch-icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"shortcuts" : [
|
||||
{
|
||||
"name": "教程",
|
||||
"url": "/zh-cn/tutorials/intro-to-liquid.html",
|
||||
"description": "一系列描述如何使用 LiquidJS 的文章"
|
||||
},
|
||||
{
|
||||
"name": "标签",
|
||||
"url": "/zh-cn/tags/overview.html",
|
||||
"description": "每个 Liquid 标签的描述和示例"
|
||||
},
|
||||
{
|
||||
"name": "过滤器",
|
||||
"url": "/zh-cn/filters/overview.html",
|
||||
"description": "每个 Liquid 过滤器的描述和示例"
|
||||
},
|
||||
{
|
||||
"name": "演示",
|
||||
"url": "/zh-cn/playground.html",
|
||||
"description": "一个用来尝试和分享 Liquid 模板的在线编辑器"
|
||||
},
|
||||
{
|
||||
"name": "API",
|
||||
"url": "/zh-cn/api/classes/liquid_.liquid.html",
|
||||
"description": "LiquidJS 类和接口的 TypeScript 文档"
|
||||
}
|
||||
],
|
||||
"start_url": "/zh-cn",
|
||||
"display": "standalone",
|
||||
"theme_color": "#0f83ce",
|
||||
"background_color": "#0f83ce"
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
---
|
||||
layout: playground
|
||||
title: 演示
|
||||
description: 一个用来尝试和分享 Liquid 模板的在线编辑器
|
||||
---
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user