Compare commits

...
84 Commits
Author SHA1 Message Date
semantic-release-bot 15b78ebd9d chore(release): 9.35.1 [skip ci]
## [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))
2022-02-26 18:12:38 +00:00
Harttle 575f3e2338 docs: add missing tag/filter links on sidebar/table, #482 2022-02-27 02:07:59 +08:00
Harttle 7dfb620d30 fix: some filters throw on nil input, see #481 2022-02-27 02:07:59 +08:00
dependabot[bot]andJun Yang 21b78d9ba0 chore(deps): bump prismjs from 1.25.0 to 1.27.0 in /docs
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.25.0 to 1.27.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.25.0...v1.27.0)

---
updated-dependencies:
- dependency-name: prismjs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-27 02:06:04 +08:00
Harttle 5d077f461a chore: add liquid.min.js into Github release 2022-02-24 01:39:27 +08:00
semantic-release-bot 13343040c6 chore(release): 9.35.0 [skip ci]
# [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))
2022-02-23 17:20:40 +00:00
Harttle ca3240c2c4 fix: url_encode throws on undefined value, fixes #479 2022-02-24 01:14:46 +08:00
Harttle 2b0c5696bc docs: introduce JS script to escape for Hexo, fixes #480 2022-02-24 01:04:50 +08:00
Harttle dab8a29070 feat: expose all tags/filters and TimezoneDate, closes #464 2022-02-23 23:38:21 +08:00
semantic-release-bot f395295721 chore(release): 9.34.1 [skip ci]
## [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))
2022-02-20 07:59:53 +00:00
Harttle 200f6648aa style: fix lint 2022-02-20 15:56:23 +08:00
dependabot[bot]andJun Yang 31aa4ec318 chore(deps): bump ssri from 6.0.1 to 6.0.2
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

---
updated-dependencies:
- dependency-name: ssri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-20 13:04:22 +08:00
dependabot[bot]andJun Yang 231574903f chore(deps): bump y18n from 3.2.1 to 3.2.2
Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

---
updated-dependencies:
- dependency-name: y18n
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-20 13:04:12 +08:00
Harttle dbc0497386 fix: sort filter unexpectedly modifies original array, #475 2022-02-20 13:03:40 +08:00
Harttle ab5e245fba fix: array output now join with "" instead of "," 2022-02-20 13:03:40 +08:00
dependabot[bot]andJun Yang 78ce5662df chore(deps): bump ini from 1.3.5 to 1.3.8
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

---
updated-dependencies:
- dependency-name: ini
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-20 12:53:30 +08:00
dependabot[bot]andJun Yang 8b880f65a2 chore(deps): bump hosted-git-info from 2.8.8 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

---
updated-dependencies:
- dependency-name: hosted-git-info
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-20 12:30:08 +08:00
dependabot[bot]andJun Yang d605158f96 chore(deps): bump tar from 4.4.13 to 4.4.19
Bumps [tar](https://github.com/npm/node-tar) from 4.4.13 to 4.4.19.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v4.4.13...v4.4.19)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-20 12:29:48 +08:00
dependabot[bot]andJun Yang 1a44b908ce chore(deps): bump npm-user-validate from 1.0.0 to 1.0.1
Bumps [npm-user-validate](https://github.com/npm/npm-user-validate) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/npm/npm-user-validate/releases)
- [Commits](https://github.com/npm/npm-user-validate/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: npm-user-validate
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-02-20 12:29:42 +08:00
Santi AlboandJun Yang a0817e5dd7 docs: fix uniq filter example in the docs 2022-02-18 19:06:01 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
703675e1e0 chore(deps): bump pathval from 1.1.0 to 1.1.1 (#467)
Bumps [pathval](https://github.com/chaijs/pathval) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/chaijs/pathval/releases)
- [Changelog](https://github.com/chaijs/pathval/blob/master/CHANGELOG.md)
- [Commits](https://github.com/chaijs/pathval/compare/v1.1.0...v1.1.1)

---
updated-dependencies:
- dependency-name: pathval
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-16 17:57:12 +08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
819d060a92 chore(deps): bump ajv from 6.12.0 to 6.12.6 in /docs (#468)
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.0 to 6.12.6.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.0...v6.12.6)

---
updated-dependencies:
- dependency-name: ajv
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-16 17:57:00 +08:00
allcontributors[bot]GitHuballcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
c3d7ecbde1 docs: add santialbo as a contributor for doc (#470)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2022-02-16 17:56:46 +08:00
Jun YangandGitHub 8bdb70878f Merge pull request #469 from santialbo/patch-1
docs: fix example in main docs page
2022-02-16 17:54:10 +08:00
Santi AlboandGitHub 2e1537d50c docs: fix example in main docs page 2022-02-16 10:13:41 +01:00
HarttleandHarttle d4afbc038f docs: add support button on navbar 2022-02-13 15:12:16 +08:00
Damian NowakandGitHub 58d03890b4 docs: whitespace-control: correct typo (#462) 2022-01-29 11:50:37 +08:00
semantic-release-bot e3fb47a90b chore(release): 9.34.0 [skip ci]
# [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))
2022-01-28 17:37:13 +00:00
dependabot[bot]andJun Yang e4ad93ee8c chore(deps): bump node-fetch from 2.6.1 to 2.6.7
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-29 01:32:26 +08:00
HarttleandHarttle 0bdf6792d1 docs: add @tbdrz as contributor 2022-01-29 01:30:48 +08:00
HarttleandHarttle 7e99efc513 feat: ownPropertyOnly option to protect prototype, #454 2022-01-29 01:22:34 +08:00
HarttleandHarttle 527858fc14 docs: better PWA, more responsive, better description 2022-01-29 00:37:43 +08:00
Martin SchusterandGitHub 9da41c8a37 fix: where-filter null handling (#457) 2022-01-28 22:43:38 +08:00
tbdrzandJun Yang 2717dae127 Clear unusefull comments 2022-01-28 22:41:11 +08:00
Harttle 8db6c7b28d docs: tutorial for render tag content 2022-01-23 14:24:58 +08:00
dependabot[bot]andJun Yang 298db97b84 chore(deps): bump shelljs from 0.8.4 to 0.8.5
Bumps [shelljs](https://github.com/shelljs/shelljs) from 0.8.4 to 0.8.5.
- [Release notes](https://github.com/shelljs/shelljs/releases)
- [Changelog](https://github.com/shelljs/shelljs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/shelljs/shelljs/compare/v0.8.4...v0.8.5)

---
updated-dependencies:
- dependency-name: shelljs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-23 00:02:46 +08:00
dependabot[bot]andJun Yang bd8e2b0f96 chore(deps): bump trim-off-newlines from 1.0.1 to 1.0.3
Bumps [trim-off-newlines](https://github.com/stevemao/trim-off-newlines) from 1.0.1 to 1.0.3.
- [Release notes](https://github.com/stevemao/trim-off-newlines/releases)
- [Commits](https://github.com/stevemao/trim-off-newlines/compare/v1.0.1...v1.0.3)

---
updated-dependencies:
- dependency-name: trim-off-newlines
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-23 00:02:32 +08:00
HarttleandHarttle 117eaf57c3 docs: add additional filters/tags as differences, closes #331 2022-01-22 23:24:06 +08:00
HarttleandHarttle 1e69d86b0c chore: build docs on master push 2022-01-22 21:59:02 +08:00
HarttleandHarttle cc6b66216d docs: support dark mode 2022-01-22 21:51:49 +08:00
HarttleandHarttle ca82416dea docs: apply network-first cache strategy 2022-01-22 19:04:12 +08:00
HarttleandHarttle 394d5a1ae2 docs: add tutorials for custom filters and tags 2022-01-22 18:52:37 +08:00
HarttleandHarttle 371da3da74 docs: add jekyllInclude to tutorials 2022-01-20 02:04:54 +08:00
semantic-release-bot 7814285479 chore(release): 9.33.1 [skip ci]
## [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))
2022-01-19 17:19:58 +00:00
HarttleandHarttle 53824a2d00 fix: liquidjs.version on npm package 2022-01-20 01:16:52 +08:00
semantic-release-bot 9966d846b5 chore(release): 9.33.0 [skip ci]
# [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))
2022-01-19 16:56:49 +00:00
HarttleandHarttle 8c27a84059 feat: support offset:continue, see #439 2022-01-20 00:12:18 +08:00
HarttleandHarttle 388d0fbbc4 feat: support Jekyll-like include syntax, see #441 2022-01-19 23:07:25 +08:00
semantic-release-bot d71d0a0c20 chore(release): 9.32.1 [skip ci]
## [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))
2022-01-12 13:27:40 +00:00
allcontributors[bot]andJun Yang 191a0130d2 docs: update .all-contributorsrc [skip ci] 2022-01-12 21:25:24 +08:00
allcontributors[bot]andJun Yang 2c3ebaca0c docs: update README.md [skip ci] 2022-01-12 21:25:24 +08:00
Ameya ApteandJun Yang 7677f848c0 fix: remove limit on operator char length
The matchOperator function has a default value of str.length for the end parameter
2022-01-12 21:24:08 +08:00
semantic-release-bot c7d2e1f1f4 chore(release): 9.32.0 [skip ci]
# [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))
2022-01-02 08:35:53 +00:00
HarttleandHarttle c756191f49 feat: support allow_false for default filter, see #435 2022-01-02 16:31:21 +08:00
HarttleandHarttle e94e386c46 docs: update liquidjs.com and README for demo link 2021-12-31 17:49:27 +08:00
semantic-release-bot b7c782b10a chore(release): 9.31.0 [skip ci]
# [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))
2021-12-19 14:35:13 +00:00
HarttleandHarttle 49d3f64873 docs: echo & liquid tags 2021-12-19 22:31:02 +08:00
HarttleandHarttle be3463c5f9 docs: add jg-rp as contributor 2021-12-19 21:42:46 +08:00
James PriorandJun Yang fde9924ee6 feat: implement liquid and echo tags, see #428 2021-12-19 21:09:26 +08:00
semantic-release-bot 5b2ea63b14 chore(release): 9.30.0 [skip ci]
# [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))
2021-12-18 07:31:17 +00:00
HarttleandHarttle 23279a816a feat: support jekyll-like include, see #433 2021-12-18 15:26:15 +08:00
HarttleandHarttle 2c14a9541d chore: update node to 14 for check pipeline 2021-12-12 20:10:15 +08:00
HarttleandHarttle c474649748 chore: update node to 14 on pipeline 2021-12-12 11:11:03 +08:00
HarttleandHarttle fad17cfbb3 docs: fix apidoc generation for typescript@4 2021-12-11 21:30:19 +08:00
semantic-release-bot 6322acb88c chore(release): 9.29.0 [skip ci]
# [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))
2021-12-11 12:32:58 +00:00
HarttleandHarttle 95fc705b5d chore: upgrade typescript 2021-12-11 20:23:09 +08:00
HarttleandHarttle 6801552fe6 feat: customize globals & strictVariables when calling render, see #432 2021-12-11 20:22:58 +08:00
HarttleandHarttle 6023d9ba10 docs: path traversal vulnerabilities when customizing FS 2021-12-11 12:31:08 +08:00
semantic-release-bot cc4628d804 chore(release): 9.28.6 [skip ci]
## [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))
2021-12-07 15:50:25 +00:00
HarttleandHarttle 69286c6909 test: coverage to 100% 2021-12-07 23:34:53 +08:00
HarttleandHarttle 6c114267a5 fix: size filter does not respect Objects, fixes #385 2021-12-07 23:05:46 +08:00
HarttleandHarttle e2ef236f68 fix: throws when using preserveTimezones on Node.js, fixes #431 2021-12-07 22:43:47 +08:00
semantic-release-bot 80c2280ee6 chore(release): 9.28.5 [skip ci]
## [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))
2021-11-05 19:39:58 +00:00
HarttleandHarttle 44f6b520d5 fix: always allow './' and '../' to be relative, even on windows 2021-11-06 03:36:39 +08:00
semantic-release-bot 3d71d9e35a chore(release): 9.28.4 [skip ci]
## [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))
2021-10-31 08:36:55 +00:00
AleksandrHovhannisyanandHarttle 124f4c4485 fix: allow {%render%} to reassign argument, #404 2021-10-31 16:28:33 +08:00
dependabot[bot]andJun Yang 432d1c7ccb chore(deps): bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-10-28 00:14:13 +08:00
HarttleandHarttle 4b42e63601 docs: update liquidjs for demo/nodejs 2021-10-28 00:03:18 +08:00
semantic-release-bot f809c716eb chore(release): 9.28.3 [skip ci]
## [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))
2021-10-27 16:01:07 +00:00
HarttleandHarttle aebeae9e1b fix: relative root (by default) yields LookupError, fixes #419, #424, also related to #395 2021-10-27 23:55:39 +08:00
Harttle 564972ba74 docs: add version info in playground.html 2021-10-20 23:20:23 +08:00
semantic-release-bot d0c3cc8b47 chore(release): 9.28.2 [skip ci]
## [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))
2021-10-16 14:40:58 +00:00
Harttleandharttle b5b2a98d5e chore: fix types 2021-10-16 22:36:46 +08:00
Harttleandharttle 8894cbfe6e fix: cache ongoing parseFile() calls, fixes #416 2021-10-16 21:07:48 +08:00
144 changed files with 4493 additions and 24872 deletions
+45
View File
@@ -441,6 +441,51 @@
"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"
]
}
],
"contributorsPerLine": 7,
+2 -2
View File
@@ -10,9 +10,9 @@ jobs:
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v2
with:
node-version: 10
node-version: '14'
- name: Build
run: |
npm ci
+27
View File
@@ -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
+4 -9
View File
@@ -10,13 +10,13 @@ jobs:
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v2
with:
node-version: 12
node-version: '14'
- name: Build
run: |
npm ci
npm run build
npm run build:dist
- name: Test
run: |
npm run lint
@@ -29,9 +29,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
- name: Publish Docs
uses: JamesIves/[email protected]
with:
branch: gh-pages
folder: docs/public
run: npx semantic-release
+125
View File
@@ -1,3 +1,128 @@
## [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)
+30 -4
View File
@@ -3,7 +3,6 @@
[![npm downloads](https://img.shields.io/npm/dm/liquidjs.svg?style=flat-square)](https://www.npmjs.org/package/liquidjs)
[![Coverage](https://img.shields.io/coveralls/harttle/liquidjs.svg?style=flat-square)](https://coveralls.io/github/harttle/liquidjs?branch=master)
[![Build Status](https://img.shields.io/github/workflow/status/harttle/liquidjs/Check/master.svg?style=flat-square)](https://github.com/harttle/liquidjs/actions/workflows/check.yml?query=branch%3Amaster)
[![David dependencies](https://img.shields.io/david/harttle/liquidjs.svg?style=flat-square)](https://david-dm.org/harttle/liquidjs)
[![DUB license](https://img.shields.io/dub/l/vibe-d.svg?style=flat-square)](https://github.com/harttle/liquidjs/blob/master/LICENSE)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](https://github.com/harttle/liquidjs)
@@ -16,12 +15,25 @@ A simple, expressive and safe [Shopify][shopify/liquid] / Github Pages compatibl
<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:
@@ -39,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 from open-collective](https://opencollective.com/liquidjs/tiers/backer.svg?avatarHeight=97)
### 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:
@@ -113,6 +132,13 @@ Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to
<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>
</tr>
</table>
+4 -6
View File
@@ -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/"/\&quot;/g' \
-e '1 s/</\&lt;/g' \
-e '1 s/>/\&gt;/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
+15
View File
@@ -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/"/\&quot;/g' \
-e '1 s/</\&lt;/g' \
-e '1 s/>/\&gt;/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
View File
@@ -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/"/\&quot;/g' \
-e '1 s/</\&lt;/g' \
-e '1 s/>/\&gt;/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
+18
View File
@@ -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)
}
})
+3 -3
View File
@@ -9,8 +9,8 @@ if [ ! -f $FILE_LATEST ]; then
curl $URL_LATEST > $FILE_LATEST
fi
if [ ! -f $FILE_LOCAL ]; then
BUNDLES=cjs npm run build:dist
fi
# if [ ! -f $FILE_LOCAL ]; then
BUNDLES=cjs npm run build:dist
# fi
exec node benchmark/diff.js $FILE_LOCAL $FILE_LATEST
+1 -1
View File
@@ -7,6 +7,6 @@
"start": "node index.js"
},
"dependencies": {
"liquidjs": "^9.27.0"
"liquidjs": "^9.28.3"
}
}
-1
View File
@@ -21,5 +21,4 @@ const ctx = {
title: 'Welcome to liquidjs!'
}
// console.log('isTruthy:', isTruthy('a string here'));
engine.renderFile('todolist', ctx).then(console.log)
View File
+8 -2
View File
@@ -2,7 +2,9 @@ title: LiquidJS
subtitle: "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
+19 -12
View File
@@ -213,15 +213,19 @@
}
},
"node_modules/ajv": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/algoliasearch": {
@@ -3889,9 +3893,12 @@
}
},
"node_modules/prismjs": {
"version": "1.25.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz",
"integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg=="
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
"engines": {
"node": ">=6"
}
},
"node_modules/process": {
"version": "0.11.10",
@@ -5293,9 +5300,9 @@
"integrity": "sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8="
},
"ajv": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
@@ -8219,9 +8226,9 @@
"integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE="
},
"prismjs": {
"version": "1.25.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz",
"integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg=="
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="
},
"process": {
"version": "0.11.10",
+1 -1
View File
@@ -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
+9
View File
@@ -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
@@ -79,15 +83,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:
+19
View File
@@ -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
+4 -2
View File
@@ -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, concat
Array | slice, map, sort, sort_natural, uniq, where, first, last, join, reverse, concat, compact, size
Date | date
Misc | default, json
[shopify/liquid]: https://github.com/Shopify/liquid
+2 -2
View File
@@ -14,5 +14,5 @@ Input
Output
```text
ants, bugs, bees```
ants, bugs, bees
```
+28
View File
@@ -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",
+2
View File
@@ -1,3 +1,5 @@
---
layout: playground
title: Playground
description: An online code editor to try out and share Liquid templates
---
+9 -2
View File
@@ -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) {
+20
View File
@@ -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!
```
+62 -20
View File
@@ -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
@@ -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
+64 -2
View File
@@ -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
View File
@@ -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
```
When 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
+28
View File
@@ -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
```
+3 -2
View File
@@ -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
+42 -7
View File
@@ -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,7 +2,7 @@
title: Contribution Guideline
---
## Star on Github 👉 [![harttle/liquidjs](https://img.shields.io/github/stars/harttle/liquidjs?style=flat-square)][liquidjs]
## 👉👉👉 Star LiquidJS [![harttle/liquidjs](https://img.shields.io/github/stars/harttle/liquidjs?style=flat-square)][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.
@@ -26,13 +26,19 @@ npm test
## 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!
[![Become a Patron!](../icon/[email protected])](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
+4
View File
@@ -11,7 +11,11 @@ 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
+6 -1
View File
@@ -1,5 +1,6 @@
---
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 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].
@@ -9,6 +10,10 @@ 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:
+26
View File
@@ -52,6 +52,29 @@ 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 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`:
@@ -105,6 +128,8 @@ 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 parsing and this behavior can not be customized.
{% endnote %}
@@ -123,3 +148,4 @@ Parameter orders are ignored by default, for ea `{% for i in (1..8) reversed lim
[layout]: ../tags/layout.html
[wc]: ./whitespace-control.html
[intro]: ./intro-to-liquid.html
[jekyllInclude]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-jekyllInclude
+100
View File
@@ -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
@@ -61,4 +61,4 @@ function disabledFilter(name) {
}
}
engine.registerFilter('plus', disabledFilter('plus'));
```
```
+5
View File
@@ -88,6 +88,9 @@ var engine = new Liquid({
exists () {
return true
},
contains () {
return true
},
resolve(root, file, ext) {
return file
}
@@ -95,6 +98,8 @@ 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
[fs-node]: https://github.com/harttle/liquidjs/blob/master/src/fs/node.ts
+137
View File
@@ -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
+90
View File
@@ -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
+2 -2
View File
@@ -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
+19
View File
@@ -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
+4 -2
View File
@@ -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, concat
数组 | slice, map, sort, sort_natural, uniq, wheres, first, last, join, reverse, concat, compact, size
日期 | date
其他 | default, json
[shopify/liquid]: https://github.com/Shopify/liquid
+58
View File
@@ -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"
}
+2
View File
@@ -1,3 +1,5 @@
---
layout: playground
title: 演示
description: 一个用来尝试和分享 Liquid 模板的在线编辑器
---
+56 -14
View File
@@ -49,17 +49,17 @@ The collection is empty.
输入
```liquid
{% for i in (1..5) %}
{% if i == 4 %}
{%- if i == 4 -%}
{% break %}
{% else %}
{%- else -%}
{{ i }}
{% endif %}
{%- endif -%}
{% endfor %}
```
输出
```text
1 2 3
123
```
### continue
@@ -69,17 +69,17 @@ The collection is empty.
输入
```liquid
{% for i in (1..5) %}
{% if i == 4 %}
{% continue %}
{% else %}
{%- if i == 4 -%}
{%- continue -%}
{%- else -%}
{{ i }}
{% endif %}
{%- endif -%}
{% endfor %}
```
输出
```text
1 2 3 5
1235
```
### forloop
@@ -137,13 +137,13 @@ index index0 rindex rindex0
```liquid
<!-- if array = [1,2,3,4,5,6] -->
{% for item in array limit:2 %}
{{ item }}
{{- item -}}
{% endfor %}
```
输出
```text
1 2
12
```
### offset
@@ -152,15 +152,57 @@ index index0 rindex rindex0
输入
```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 %}
```
输出
```text
3 4 5 6
3456
```
#### offset:continue
{% since %}v9.33.0{% endsince %}
`offset` 的值可以是 `continue`,用来继续上一次循环。例如:
输入
```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%}
```
输出
```text
12
3456
```
对同样的变量名和集合名(这个例子中是 `"item-array"`),存在唯一的位置记录。也就是说用新的变量名就可以开启一个新的循环:
输入
```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%}
```
输出
```text
12
123456
```
### range
+62
View File
@@ -44,5 +44,67 @@ title: Include
上面的例子中,子模板中 `product` 会保有父模板中的 `featured_product` 变量的值。
## 输出和过滤器
文件名为字符串字面量时,支持 Liquid 输出和过滤器。在拼接文件名时很方便:
```liquid
{% include "prefix/{{name | append: \".html\"}}" %}
```
{% note info 转义 %}
字符串字面量里的 `"` 需要转义为 `\"`,使用静态文件名可以避免这个问题,见下面的 Jekyll-like 文件名。
{% endnote %}
## Jekyll-like 文件名
设置 [dynamicPartials][dynamicPartials] 为 `false` 来启用 Jekyll-like 文件名,这时文件名不需要用引号包含,会被当作字面量处理。 这样的字符串里面仍然支持 Liquid 输出和过滤器,例如:
```liquid
{% include prefix/{{ page.my_variable }}/suffix %}
```
这样文件名里的 `"` 就不用转义了。
```liquid
{% include prefix/{{name | append: ".html"}} %}
```
## Jekyll include
{% since %}v9.33.0{% endsince %}
[jekyllInclude][jekyllInclude] 用来启用 Jekyll-like include 语法。默认为 `false`,当设置为 `true` 时:
- 默认启用静态文件名:`dynamicPartials` 的默认值变为 `false`(而非 `true`)。但你也可以把它设置回 `true`
- 参数的键和值之间由 `=` 分隔(本来是 `:`)。
- 参数放到了 `include` 变量下,而非当前作用域。
例如下面的模板:
```liquid
{% include article.html header="HEADER" content="CONTENT" %}
```
其中 `article.html` 的内容是:
```liquid
<article>
<header>{{include.header}}</header>
{{include.content}}
</article>
```
注意我们通过 `include.header` 引用第一个参数,而不是 `header`。输出如下:
```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
+3 -2
View File
@@ -1,5 +1,6 @@
---
title: 标签
description: 每个 Liquid 标签的描述和示例
---
LiquidJS 支持 Liquid 语法中具体业务无关的标签,包含 [shopify/liquid 核心][shopify/liquid] 里的所有标签。这部分包含了所有 LiquidJS 支持的标签的文档和使用示例。
@@ -10,8 +11,8 @@ LiquidJS 支持十几个过滤器,可以分为如下几类:
--- | --- | ---
迭代 | 遍历一个集合 | for, cycle, tablerow
控制流 | 控制模板渲染的执行分支 | if, unless, elif, else, case, when
变量 | 定义和修改变量 | assign, increment, decrement
变量 | 定义和修改变量 | assign, increment, decrement, capture, echo
文件 | 引入或继承其他模板 | render, include, layout
语言 | 暂时禁用 Liquid 语法 | raw, comment
语言 | 暂时禁用 Liquid 语法 | raw, comment, liquid
[shopify/liquid]: https://github.com/Shopify/liquid
@@ -0,0 +1,32 @@
---
title: 过滤器里访问上下文
---
在 [注册过滤器和标签][register-filters] 里介绍过,可以在函数参数里直接获得过滤器的参数:
```javascript
// Usage: {{ 1 | add: 2, 3 }}
// Output: 6
engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2)
```
但有些过滤器还需要访问当前上下文的变量,比如把 URL 路径转换为完整的 URL 时,需要访问上下文的 `origin` 变量:
```javascript
// Usage: {{ '/index.html' | fullURL }}
// Scope: { origin: "https://liquidjs.com" }
// Output: https://liquidjs.com/index.html
engine.registerFilter('fullURL', function (path) {
const origin = this.context.get(['origin'])
return new URL(path, origin).toString()
})
```
见这个 JSFiddle<http://jsfiddle.net/ctj364up/1/>。
{% note warn 箭头函数 %}
在箭头函数里 `this` 会绑定到当前 JavaScript 上下文,你需要用 `function(){}` 来替代 `()=>{}` 语法,才能正确地访问 `this.context`
{% endnote %}
[register-filters]: /tutorials/register-filters-tags.html
@@ -2,7 +2,7 @@
title: 贡献指南
---
## Star on Github 👉 [![harttle/liquidjs](https://img.shields.io/github/stars/harttle/liquidjs?style=flat-square)][liquidjs]
## Star LiquidJS 👉👉👉 [![harttle/liquidjs](https://img.shields.io/github/stars/harttle/liquidjs?style=flat-square)][liquidjs]
Star 是支持 LiquidJS 最重要的方式,也是最简单的方式:通过提升排名来让更多人了解 LiquidJS,让它得到更好的改进。
@@ -24,13 +24,19 @@ npm test
**向后兼容**:请考虑向后(之前的旧的版本)兼容。LiquidJS 被用于很多层的软件,包括底层库、编译器、站点生成器、 Web 服务器。对多数最终用户来说,驱动或请求整个系统做一次主版本升级是很难办到的。
## 成为赞助者!
## 资金支持
LiquidJS 是开源的、免费的,并且 **没有** 商业支持,也 **没有** 任何广告。如果你喜欢 LiquidJS 或你的公司在使用 LiquidJS,请考虑通过 [Open Collective][oc] 或 [Patreon][pt] 赞助,作为感谢你的名字和头像(或 Logo)会展示在这里和 [Github README][liquidjs]。
LiquidJS 是开源免费的,但支持 [Open Collective][oc] 或 [Patreon][pt] 赞助,作为感谢你的名字和头像(或 Logo)会展示在这里[LiquidJS 首页](https://liquidjs.com/)和 [Github README][liquidjs]。
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/tiers/backer.svg?avatarHeight=72"></object>
### 支持者
如果你喜欢 LiquidJS,希望支持我们,你可以成为 Open Collective Backer
[![Become a Patron!](../../icon/[email protected])](https://www.patreon.com/bePatron?u=32321060)
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/backers.svg?avatarHeight=72"></object>
### 赞助商
如果你的公司/业务在使用 LiquidJS,请考虑赞助我们!让 LiquidJS 变得更好,以及保持开放和免费!
<object type="image/svg+xml" data="https://opencollective.com/liquidjs/sponsors.svg?avatarHeight=72"></object>
[oc]: https://opencollective.com/liquidjs/
[pt]: https://www.patreon.com/harttle
@@ -11,7 +11,11 @@ title: 和 Shopify/liquid 的区别
* 对象的迭代顺序。JavaScript 对象的迭代顺序是插入顺序和数字键递增顺序的组合,但 Ruby Hash 中只是插入顺序(JavaScript 字面量 Object 和 Ruby 字面量 Hash 的插入顺序解释也不同)。
* 排序稳定性。shopify/liquid 和 LiquidJS 都没有定义 [sort][sort] 过滤器的稳定性在,它取决于 Ruby/JavaScript 内置的排序算法,在 Node.js 12+ 和 Google Chrome 70+ LiquidJS 的排序是 [稳定的][stable-sort]。
* shopify/liquid 允许过滤器尾部的未匹配字符,但 LiquidJS 不允许。这就是说如果过滤器参数前忘记写冒号比如 `{%raw%}{{ "a b" | split " "}}{%endraw%}` LiquidJS 会抛出异常。这是为了提升 Liquid 模板的易用性,参考 [#208][#208] 和 [#212][#212]。
* LiquidJS 有额外的标签:[layout][layout] 和相应的 `block`
* LiquidJS 有额外的过滤器:[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
@@ -1,5 +1,6 @@
---
title: Liquid 模板语言简介
title: Liquid 模板语言
description: Liquid 模板语言的介绍,和一些示例代码
---
LiquidJS 是一个简单的、安全的、兼容 Shopify 的、纯 JavaScript 编写的模板引擎。这个项目的目的是为 JavaScript 社区提供一个 Liquid 模板引擎的实现。Liquid 最初用 Ruby 实现并用于 Github Pages, Jekyll 和 Shopify,参考 [和 Shopify/liquid 的区别][diff]。
@@ -9,6 +10,10 @@ LiquidJS 语法相对简单。LiquidJS 中有两种标记:
- **标签**。标签由标签名和参数构成,由 `{%raw%}{%{%endraw%}``%}` 包裹。
- **输出**。输出由一个值和一组可选的过滤器构成,由 `{%raw%}{{{%endraw%}``}}` 包裹。
{% note info 在线示例 %}
在进一步了解细节之前,这里有一个在线示例:<https://liquidjs.com/playground.html>。
{% endnote %}
## 输出
**输出** 用于转换和输出变量到 HTMl。下面的模板将会把 `username` 的值插入到 input 的 `value`
+27 -1
View File
@@ -47,9 +47,32 @@ const engine = new Liquid({
{% liquid foo.html %}
```
{% note warn Common Pitfall %}
{% note warn 常见陷阱 %}
LiquidJS 把这个选项默认值设为 <code>true</code> 以兼容于 shopify/liquid,但如果你在使用 <a href="https://github.com/11ty/eleventy" target="_blank">eleventy</a> 它会设置默认值 <code>false</code> (参考 <a href="https://www.11ty.dev/docs/languages/liquid/#quoted-include-paths" target="_blank">Quoted Include Paths</a>)以兼容于 Jekyll。{% endnote %}
## Jekyll include
{% since %}v9.33.0{% endsince %}
[jekyllInclude][jekyllInclude] 用来启用 Jekyll-like include 语法。默认为 `false`,当设置为 `true` 时:
- 默认启用静态文件名:`dynamicPartials` 的默认值变为 `false`(而非 `true`)。但你也可以把它设置回 `true`
- 参数的键和值之间由 `=` 分隔(本来是 `:`)。
- 参数放到了 `include` 变量下,而非当前作用域。
例如下面的模板中,`name.html` 没有带引号,`header``"HEADER"``=` 分隔,`header` 参数通过 `include.header` 来引用。更多详情请参考 [include][include]。
```liquid
// entry template
{% include article.html header="HEADER" content="CONTENT" %}
// article.html
<article>
<header>{{include.header}}</header>
{{include.content}}
</article>
```
## extname
**extname** 定义了默认的文件后缀,当传入文件名不包含后缀时自动追加。默认值是 `''` 也就是说默认是禁用的。如果设置为 `.liquid`
@@ -102,6 +125,8 @@ LiquidJS 把这个选项默认值设为 <code>true</code> 以兼容于 shopify/l
**strictVariables** 用来启用变量严格模式。如果设置为 `true` 变量不存在时渲染会抛出异常,默认为 `false` 这时不存在的变量会被渲染为空字符串。
**ownPropertyOnly** 用来隐藏原型上的变量,如果你需要把未经处理过的对象传递给模板时,可以设置 `ownPropertyOnly``true`,默认为 `false`
{% note info 不存在的标签 %}
不存在的标签总是会抛出一个解析异常,这一行为无法自定义。
{% endnote %}
@@ -120,3 +145,4 @@ LiquidJS 把这个选项默认值设为 <code>true</code> 以兼容于 shopify/l
[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: 参数解析
---
## 访问原始参数
在 [注册过滤器和标签][register-tags] 中提到,可以通过 `tagToken.args` 来得到标签的原始参数字符串。例如:
```javascript
// Usage: {% random foo bar coo %}
// Output: "foo", "bar" or "coo"
engine.registerTag('random', {
parse(tagToken) {
// tagToken.args === "foo bar coo"
this.items = tagToken.args.split(' ')
},
render(context, emitter) {
// get a random index
const index = Math.floor(this.items.length * Math.random())
// output that item
emitter.write(this.items[index])
}
})
```
见这个 JSFiddle<http://jsfiddle.net/ctj364up/2/>。
## 解析参数的值
除了静态的参数字符串之外,我们更希望把动态的值传递给标签。LiquidJS 中的值可以是字面量(字符串、数字等,也可以是当前上下文的变量。
下面是修改过的模板,也包含三个值用来随机。但它们表示的是值而不是静态的字符串。第一个是字符串字面量,第二个是标识符(表示变量),第三个是属性访问表达式,包含两个标识符。
```liquid
{% random "foo" bar obj.coo %}
```
解析这么多种情况会很麻烦,但 LiquidJS 提供了 [Tokenizer][Tokenizer] 类来处理这种情况。
```javascript
const { Liquid, Tokenizer, evalToken } = require('liquidjs')
engine.registerTag('random', {
parse(tagToken) {
const tokenizer = new Tokenizer(tagToken.args)
this.items = []
while (!tokenizer.end()) {
// here readValue() returns a LiteralToken or PropertyAccessToken
this.items.push(tokenizer.readValue())
}
},
* render(context, emitter) {
const index = Math.floor(this.items.length * Math.random())
const token = this.items[index]
// in LiquidJS, we use yield to wait for async call
const value = yield evalToken(token, context)
emitter.write(value)
}
})
```
用上下文 `{ bar: "bar", obj: { coo: "coo" } }` 来调用这个标签可以得到上第一个例子一样的效果。见这个 JSFiddle<http://jsfiddle.net/ctj364up/3/>.
{% note info 异步和 Promise %}
在 LiquidJS 里异步用生成器实现,这样同样一份标签的实现也可以用于同步的 API 比如 `renderSync()``parseAndRenderSync()``renderFileSync()`。如果要在标签实现里等待 Promise,只需要把 `await somePromise` 换成 `yield somePromise`,并保留 `* render()` 不要改成 `async render()`。更多细节请参考 <a href="/tutorials/sync-and-async.html">Sync and Async</a>。
{% endnote %}
## 把键值对解析为命名参数
当参数很多时或者有可选参数时,使用命名参数语法会很方便。这时参数由无序的键值对构成,LiquidJS 中的 [Hash][Hash] 类就是来处理这种情况的。
```liquid
{% random from:2, to:max %}
```
上面的例子用来产生 [2, max] 范围内的随机数。我们要用 `Hash` 来解析 `from``to` 参数。
```javascript
const { Liquid, Hash } = require('liquidjs')
engine.registerTag('random', {
parse(tagToken) {
// 解析参数结果,存到 `this.args`
this.args = new Hash(tagToken.args)
},
* render(context, emitter) {
// 在当前 `context` 下计算参数的值
const {from, to} = yield this.args.render(context)
const length = to - from + 1
const value = from + Math.floor(length * Math.random())
emitter.write(value)
}
})
```
`{ max: 10 }` 上下文上渲染 `{% random from:2, to:max %}` 将会得到 [2, 10] 范围内的随机数。见这个 JSFiddle<http://jsfiddle.net/ctj364up/4/>。
[register-tags]: /tutorials/register-filters-tags.html
[Tokenizer]: /api/classes/parser_tokenizer_.tokenizer.html
[Hash]: /api/classes/template_tag_hash_.hash.html
@@ -0,0 +1,137 @@
---
title: 渲染标签内容
---
自定义标签可以有内容,也可以嵌套使用。本文描述了如何实现一个由*开始标签*,*结束标签*和之间的*标签内容*的自定义标签。
## 渲染标签内容
我们先实现一个简单的 `wrap` 标签,它会把内容包装在 `<div class="wrapper"></div>` 元素里:
```liquid
{% wrap %}
{{ "hello world!" | capitalize }}
{% endwrap %}
```
期望输出:
```html
<div class='wrapper'>
Hello world!
</div>
```
首先 [注册][register-tags] 一个名为 `wrap` 的标签,把内容解析到 `this.tpls` 数组里。`parse(tagToken, remainTokens)` 中,
- `tagToken` 是当前 *Token* `{% wrap %}`
- `remainTokens` 是当前模板中后续所有 *Token* 的数组。
我们要做的是从 `remainTokens` 里拿出来/`.shift()` 足够的标签直到遇到 `endwrap`(其实可以是任意名字,但按照惯例应该叫 `endwrap`)。如果到模板结尾都没遇到 `endwrap`,需要抛出一个标签未关闭的 `Error`
```javascript
engine.registerTag('wrap', {
parse(tagToken, remainTokens) {
this.tpls = []
let closed = false
while(remainTokens.length) {
let token = remainTokens.shift()
// 得到了结束标签,停止解析
if (token.name === 'endwrap') {
closed = true
break
}
// 把 Token 解析成 Template
// parseToken() 可能会消耗多个 Token
// 例如 {% 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()` 可能是异步的,因此需要 `yield` 来等它完成。更多关于 LiquidJS 异步的信息可以参考 [同步和异步][async]。`render()` 的其他部分比较直观,这是 JSFiddle 版本:<http://jsfiddle.net/por0zcn1/3/>。
## 使用 ParseStream
对于像 [for][for] 和 [if][if] 这样的复杂标签,`parse()` 会变得很复杂。使用 [ParseStream][ParseStream] 工具可以按事件风格来组织 `parse()` 的逻辑。下面是用 `ParseStream` 重写过的 `parse()`,实现了和上面例子中完全一样的功能。
```javascript
parse(tagToken, remainTokens) {
this.tpls = []
this.liquid.parser.parseStream(remainTokens)
.on('template', tpl => this.tpls.push(tpl))
// 注意这里不能用箭头函数,因为我们需要 `this`
.on('tag:endwrap', function () { this.stop() })
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
.start()
}
```
这是 JSFiddle 链接:<http://jsfiddle.net/por0zcn1/4/>。简单起见,下面的例子都借助 `ParseStream` 来实现。
## 操作上下文
上面的 `wrap` 标签看起来没什么用,反正没它也可以很容易地渲染那部分内容。我们现在来实现一个 `repeat` 标签,把内容渲染两次(还可以[加个参数][parameter]让它渲染任意次):
```liquid
{% repeat %}
{{ repeat.i }}. {{ "hello world!" | capitalize }}
{% endrepeat %}`
```
期望输出:
```html
1. Hello world!
2. Hello world!
```
你可能注意到了在 `repeat` 上下文里有个额外的变量 `repeat.i`,这就需要我们操作 *上下文*
{% note info 上下文 %}
<em>上下文</em> 定义了 Liquid 模板中每个变量的值。在 LiquidJS 中,`Context` 由一个 `Scope` 的栈组成。*Scope* 就是一个普通对象,就像传给 `engine.render(tpl, scope)``scope` 一样。
{% endnote %}
每次进入新的 *上下文* 时,我们需要 `push` 一个新的 `Scope`。当结束渲染并退出 *上下文* 时,再把 `Scope`*上下文* `pop` 出来。见下面的实现:
```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()
}
})
```
`parse()` 部分和 `wrap` 标签完全相同,在 `render()` 部分我们通过调用两次 `.renderTemplates(this.tpls)` 来重复渲染内容。这是 JSFiddle 链接:<http://jsfiddle.net/por0zcn1/2/>。
{% note warn 成对使用 Push 和 Pop %}
必须成对地使用 `context.push()``context.pop()`。如果忘记 `pop()` 会导致 `Scope` 泄露给后面的模板内容,也可能损坏 *上下文* 栈。.
{% 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,89 @@
---
title: 同步和异步
---
LiquidJS 支持同步调用也支持异步调用,支持 Promise。为了同异步复用一套标签和过滤器,LiquidJS 标签用生成器来实现。
## 同异步 API
[Liquid][Liquid] 上主要的方法都支持同步和异步,下面这些方法返回 `Promise`
- `render()`
- `renderFile()`
- `parseFile()`
- `parseAndRender()`
- `evalValue()`
它们的同步版本带一个 `Sync` 后缀:
- `renderSync()`
- `renderFileSync()`
- `parseFileSync()`
- `parseAndRenderSync()`
- `evalValueSync()`
## 如何实现兼容同步的标签
### 要求
所有内置标签都兼容同步,可以安全地用于同步或异步 API。为了让你的自定义标签页支持同步,你的标签不能返回 `Promise`,这意味着你的 `render(context, emitter)` 函数:
- 不能直接 `return <Promise>`
- 也不能声明为 `async`
{% note info 不兼容同步的标签 %}
不兼容同步的标签也仍然是合法标签,在异步 API 下也会正常运行。被同步调用时,返回 <code>Promise</code> 的标签会被渲染成 <code>[object Promise]</code>。
{% endnote %}
### 等待 Promise
但 LiquidJS 是支持 `Promise` 的,你仍然可以调用返回 `Promise` 的方法并等它 resolve。只需要把 `await` 换成 `yield` 并保留 `* render()` 不要改成 `async render()`。例如:
```typescript
import { TagToken, Context, Emitter, TopLevelToken } from 'liquidjs'
// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', {
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.str = tagToken.args
},
* render: function(ctx: Context) {
// 同步调用时 `ctx.sync == true``_evalValue()` 会同步地执行
var str = yield this.liquid._evalValue(this.str, ctx)
return str.toUpperCase()
}
})
```
见这个 JSFiddle<http://jsfiddle.net/ctj364up/6/>。
## 只支持异步的标签
对于只用于异步 API 的标签,或者只能实现为异步的标签,使用生成器语法和 async 语法并没有区别。
例如,如果上面的 `this.liquid._evalValue()` 不会检查 `ctx.sync` 而且总是返回一个 `Promise`,那么即使这个标签用 `* render()``yield this.liquid._evalValue()` 实现,最终也会渲染成 `<object Promise>`
这时可以直接使用 async 语法。注意有些 LiquidJS API 会返回 `Promise`,有些会返回生成器。你需要用 [toPromise][toPromise] API 来把生成器转换为 `Promise`,比如:
```typescript
import { TagToken, Context, Emitter, TopLevelToken, toPromise } from 'liquidjs'
// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', {
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.str = tagToken.args; // name
},
render: async function(ctx: Context) {
var str = await toPromise(this.liquid._evalValue(this.str, ctx));
// Or use the alternate API that returns a Promise
// var str = await this.liquid.evalValue(this.str, ctx);
return str.toUpperCase()
}
});
```
见这个 JSFiddle<http://jsfiddle.net/ctj364up/5/>。
[Liquid]: /api/classes/liquid_.liquid.html
[toPromise]: /api/modules/liquid_.html#toPromise
+4
View File
@@ -41,6 +41,10 @@ sidebar:
advanced: Advanced
caching: Caching
registeration: Register Filters/Tags
access_scope_in_filters: Access Scope in Filters
parse_parameters: Parse Parameters
render_tag_content: Render Tag Content
sync_and_async: Sync and Async
whitespace: Whitespace Control
plugins: Plugins
operators: Operators
+4
View File
@@ -41,6 +41,10 @@ sidebar:
advanced: 高级主题
caching: 缓存
registeration: 注册标签/过滤器
access_scope_in_filters: 过滤器里访问上下文
parse_parameters: 参数解析
render_tag_content: 渲染标签内容
sync_and_async: 同步和异步
whitespace: 换行和缩进
plugins: 插件
operators: 运算符
+1 -7
View File
@@ -25,13 +25,7 @@
<div class="wrapper">
<div class="inner">
{{ page.content }}
<ul id="intro-cmd-wrap">
<li class="intro-code-item"><span class="hl">import</span> { Liquid } <span class="hl">from</span> 'liquidjs'</li>
<li class="intro-code-item"><span class="hl">const</span> engine = <span class="hl">new</span> Liquid()</li>
<li class="intro-code-item"><span class="hl">const</span> tpl = engine.parse(<span class="hl-str">'Welcome to &#x7b;&#x7b;v}}!'</span>)</li>
<li class="intro-code-item">engine.render(tpl, {v: "Liquid"}).then(console.log)</li>
<li class="intro-code-item"><span class="hl-comment">// Outputs "Welcome to Liquid!"</span></li>
</ul>
<div id="intro-cmd-wrap" class="highlight typescript"><pre><span class="line"><span class="keyword">import</span> { Liquid } <span class="keyword">from</span> <span class="string">'liquidjs'</span></span><br><span class="line"><span class="keyword">const</span> engine = <span class="keyword">new</span> Liquid()</span><br><span class="line"><span class="keyword">const</span> tpl = engine.parse(<span class="string">'Welcome to {% raw %}{{v}}{% endraw %}!'</span>)</span><br><span class="line">engine.render(tpl, {<span class="attr">v</span>: <span class="string">"Liquid"</span>}).then(<span class="built_in">console</span>.log)</span><br><span class="line"><span class="comment">// Outputs "Welcome to Liquid!"</span></span><br></pre></div>
<div id="intro-get-started-wrap">
<a href="tutorials/setup.html" id="intro-get-started-link">{{ __('index.get_started') }}</a>
</div>
+5
View File
@@ -50,6 +50,11 @@
<td align="center"><a href="https://github.com/bglw"><img src="https://avatars.githubusercontent.com/u/40188355?v=4?s=100" width="100px;" alt=""/></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=""/></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=""/></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=""/></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=""/></a></td>
<td align="center"><a href="https://github.com/ameyaapte1"><img src="https://avatars.githubusercontent.com/u/16054747?v=4?s=100" width="100px;" alt=""/></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=""/></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=""/></a></td>
</tr>
</table>
+2 -1
View File
@@ -3,8 +3,9 @@
<title>{% if page.title %}{{ page.title }} | {% endif %}{{ config.title }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{{ page.description | default(config.description) }}">
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net/">
<link rel="manifest" href="/manifest.json">
<link rel="manifest" href="{% if page.lang != "en" %}/{{page.lang}}{% endif %}/manifest.json">
<!-- Canonical links -->
<link rel="canonical" href="{{ url }}">
<!-- Alternative links -->
+1
View File
@@ -19,6 +19,7 @@
</select>
</div>
<a href="https://github.com/{{ config.github }}" id="github-link-wrap" class="main-nav-link"><i class="icon-github"></i>Github</a>
<a href="https://opencollective.com/liquidjs" class="main-nav-link"><i class="icon-opencollective"></i>Support</a>
<a id="mobile-nav-toggle">
<span class="mobile-nav-toggle-bar"></span>
<span class="mobile-nav-toggle-bar"></span>
+3 -1
View File
@@ -1,12 +1,14 @@
<div id="playground" role="main">
<div class="wrapper">
<h1 class="inner">{{__('playground.title')}}</h1>
<div class="row inner">
<div class="loader" role=status aria-busy=true></div>
<div id="editors" class="row inner hide" aria-hide=true>
<div class="col">
<div id="editorEl">{{ raw('partial/demo.liquid') }}</div>
<div id="dataEl">{}</div>
</div>
<div class="col" id="previewEl">{{__('playground.loading')}}</div>
</div>
<p class="inner version"></p>
</div>
</div>
+17 -6
View File
@@ -21,15 +21,28 @@ input, button, select
border: 0
body
background: color-navy
background: var(--color-navy)
font-size: font-size
font-family: font-sans
color: color-default
color: var(--color-default)
text-rendering: optimizeLegibility
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale
overflow-x: hidden
a
color: var(--color-link)
text-decoration: none
&:hover
color: var(--color-link-hover)
text-decoration: underline
@media print
color: var(--color-default)
text-decoration: underline
&:after
content: " (" attr(href) ")"
font-size: 80%
@media screen and (max-device-width: 480px)
body
-webkit-text-size-adjust: none;
@@ -44,10 +57,8 @@ body
padding: 0 gutter-width
#content-wrap
background: #fff
border-top: 1px solid darken(color-navy, 5%)
border-bottom: 1px solid darken(color-navy, 5%)
margin: -1px 0
background: var(--color-content-bg)
overflow: hidden
.video-container
text-align: center
+1 -1
View File
@@ -3,7 +3,7 @@
padding: 24px 0
color: rgba(255, 255, 255, 0.6)
position: relative
background: color-navy
background: var(--color-navy)
text-align: center
@media mq-normal
padding: 40px 0
+5 -3
View File
@@ -36,7 +36,7 @@ logo-size = 36px
#search-input-wrap
display: none
padding-left: 6px
border-bottom: 1px solid color-gray
border-bottom: 1px solid var(--color-gray)
&.on
display: inline-block
> i, > span, #search-input
@@ -58,8 +58,9 @@ logo-size = 36px
color: #fff
text-decoration: none
line-height: logo-size
white-space: nowrap
opacity: 0.7
transition: 0.2s
transition: opacity 0.2s, color 0.2s
display: inline-block
padding: 0 15px
i
@@ -68,7 +69,8 @@ logo-size = 36px
opacity: 1
&:hover
opacity: 1
color: color-link-hover
color: var(--color-link-hover)
text-decoration: none
#lang-select-wrap
#github-link-wrap
+30 -26
View File
@@ -1,21 +1,25 @@
// https://github.com/chriskempson/tomorrow-theme
highlight-background = #fff
highlight-current-line = #efefef
highlight-selection = #d6d6d6
highlight-foreground = #4d4d4c
highlight-comment = #8e908c
highlight-red = #c82829
highlight-orange = #f5871f
highlight-yellow = #eab700
highlight-green = #718c00
highlight-aqua = #3e999f
highlight-blue = #4271ae
highlight-purple = #8959a8
// Tomorrow Night Eighties
:root {
--highlight-background: #2d2d2d
--highlight-current-line: #393939
--highlight-selection: #515151
--highlight-foreground: #cccccc
--highlight-comment: #999999
--highlight-red: #f2777a
--highlight-orange: #f99157
--highlight-yellow: #ffcc66
--highlight-green: #99cc99
--highlight-aqua: #66cccc
--highlight-blue: #6699cc
--highlight-purple: #cc99cc
}
pre, code
font-family: font-mono
color: highlight-foreground
background: #eee
color: var(--highlight-foreground)
background: var(--highlight-background)
white-space: pre-wrap
code
padding: 0 5px
@@ -29,9 +33,9 @@ pre
padding: 0
.highlight
background: #eee
padding: 10px 15px
color: highlight-foreground
color: var(--highlight-foreground)
background: var(--highlight-background)
overflow: auto
margin: 0
table
@@ -42,12 +46,12 @@ pre
figcaption
clearfix()
margin: -5px 0 5px
color: color-gray
color: var(--color-gray)
a
float: right
pre
margin: 0
padding: 0
border: none
background: none
.line
height: 22px
@@ -55,7 +59,7 @@ pre
pre
.comment
.title
color: highlight-comment
color: var(--highlight-comment)
.variable
.attribute
.tag
@@ -68,27 +72,27 @@ pre
.css .id
.css .class
.css .pseudo
color: highlight-red
color: var(--highlight-red)
.number
.preprocessor
.built_in
.literal
.params
.constant
color: highlight-orange
color: var(--highlight-orange)
.class
.ruby .class .title
.css .rules .attribute
color: highlight-green
color: var(--highlight-green)
.string
.value
.inheritance
.header
.ruby .symbol
.xml .cdata
color: highlight-green
color: var(--highlight-green)
.css .hexcolor
color: highlight-aqua
color: var(--highlight-aqua)
.function
.python .decorator
.python .title
@@ -97,7 +101,7 @@ pre
.perl .sub
.javascript .title
.coffeescript .title
color: highlight-blue
color: var(--highlight-blue)
.keyword
.javascript .function
color: highlight-purple
color: var(--highlight-purple)
+35 -35
View File
@@ -30,7 +30,7 @@
font-size: 18px
#banner-start-command
background: lighten(color-navy, 10%)
background: var(--color-navy-lighter)
padding: 0 15px
color: #fff
display: inline-block
@@ -41,20 +41,20 @@
#banner-start-link
color: #fff
background: color-link
background: var(--color-link)
display: inline-block
padding: 0 15px
text-decoration: none
transition: 0.2s
transition: background 0.2s
&:hover
background: color-link-hover
background: var(--color-link-hover)
#banner-share
display: block
padding-bottom: 60px
#intro-news-list
background-color: darken(color-navy, 10%)
background-color: var(--color-news-bg)
margin: -1px 0
#intro-news-flex
@@ -67,7 +67,7 @@
padding: 24px 30px
text-decoration: none
&:hover
background-color: color-link-hover
background-color: var(--color-link-hover)
@media mq-normal
flex: 0 0 50%
padding: 36px 30px
@@ -108,7 +108,7 @@
padding-left: 70px
.intro-feature-icon
color: color-link
color: var(--color-link)
font-size: 36px
margin-bottom: 6px;
text-align: center
@@ -121,7 +121,7 @@
width: @font-size
.intro-feature-title
color: color-link
color: var(--color-link)
font-size: 24px
.intro-feature-desc
@@ -136,29 +136,21 @@
color: black
&:hover
text-decoration: underline
@media (prefers-color-scheme: dark)
color: white
&:visited, &:hover
color: white
#intro-cmd-wrap
max-width: 700px
background: #eee
padding: 15px 0
padding: 15px 30px
font-size: 16px
pre
line-height: 32px
overflow: auto
margin: 50px auto 0;
@media mq-normal
margin: 70px auto 0;
li
font-size: 16px
font-family: font-mono
line-height: 2
padding: 0 30px
.intro-code-item
white-space: pre
.hl
color: color-link
.hl-str
color: #9e0001ad
.hl-comment
color: #056d05b5
#intro-get-started-wrap
text-align: center
@@ -170,17 +162,17 @@
font-size: 18px
display: inline-block
text-decoration: none
border: 3px solid lighten(color-link, 20%)
border: 3px solid var(--color-link-hover)
padding: 12px 24px
position: relative
transition: 0.2s
color: color-link
transition: background 0.2s, color 0.2s
color: var(--color-link)
&:hover
background: lighten(color-link, 20%)
background: var(--color-link-hover)
color: #fff
#sponsors-wrap, #contributors-wrap
background: lighten(color-navy, 10%)
background: var(--color-navy-lighter)
border-top: 1px solid #161d24
border-bottom: 1px solid #161d24
margin: -1px 0
@@ -210,29 +202,37 @@
display: inline-block
height: 72px
width: 72px
background: color-gray
background: var(--color-gray)
border-radius: 50%
margin-bottom: 10px
.description a
color: #fff
&:hover
background: #fff
color: lighten(color-navy, 10%)
color: var(--color-navy-lighter)
#sponsors-wrap
background: #fff
background: var(--color-content-bg)
border: none
text-align: right
overflow: hidden;
.inner
h3
color: color-default
color: var(--color-default)
p
color: color-gray
color: var(--color-gray)
.open-collective
margin: 0 auto
.description a
color: #333
&:hover
background: color-navy
background: var(--color-navy)
color: #fff
@media (prefers-color-scheme: dark)
p
color: var(--color-default)
.description a
color: #fff
&:hover
background: #fff
color: var(--color-navy-lighter)
+4 -4
View File
@@ -15,13 +15,13 @@ lang-select-height = 40px
width: mobile-nav-width
left: @width * -1
height: 100%
background: color-navy
background: var(--color-navy)
transition: transition-duration
.mobile-nav-on &
transform: translateX(100%)
.mobile-sidebar-list
background: lighten(color-navy, 10%)
background: var(--color-navy-lighter)
#mobile-nav-inner
overflow-y: auto
@@ -85,7 +85,7 @@ lang-select-height = 40px
text-overflow: ellipsis
.mobile-nav-title
color: color-link
color: var(--color-link)
font-weight: bold
padding: 10px 15px
line-height: 1
@@ -101,7 +101,7 @@ lang-select-height = 40px
bottom: 0
left: 0
width: 100%
background: color-navy
background: var(--color-navy)
border-top: 1px solid #444
#mobile-lang-select-label
+20 -27
View File
@@ -53,10 +53,10 @@ note-warn = hsl(0, 100%, 50%)
margin-top: 13px
font-size: 0.9em
text-decoration: none
color: color-default
color: var(--color-default)
display: block
&:hover
color: color-link-hover
color: var(--color-link-hover)
.article-header
padding-bottom: 20px
@@ -67,20 +67,20 @@ note-warn = hsl(0, 100%, 50%)
font-size: 36px
font-weight: 300
text-decoration: none
color: color-default
color: var(--color-default)
transition: 0.2s
a&:hover
color: color-link-hover
color: var(--color-link-hover)
.post &
float: none
.article-date
color: color-gray
color: var(--color-gray)
text-decoration: none
display: inline-block
margin-top: 1em
&:hover
color: color-link-hover
color: var(--color-link-hover)
.article-edit-link
float: right
@@ -91,7 +91,7 @@ note-warn = hsl(0, 100%, 50%)
transition: 0.2s
display: none
&:hover
color: color-link-hover
color: var(--color-link-hover)
@media mq-normal
display: block
@@ -107,7 +107,7 @@ note-warn = hsl(0, 100%, 50%)
.article-content
line-height: line-height
color: color-default
color: var(--color-default)
@media print
font-size: 12pt
p, ol, ul, dl, table, blockquote, iframe, .highlight
@@ -116,7 +116,7 @@ note-warn = hsl(0, 100%, 50%)
font-size: 2em
h2
font-size: 1.5em
border-bottom: 1px solid color-border
border-bottom: 1px solid var(--color-border)
padding-bottom: 10px
margin-bottom: 15px
h3
@@ -127,19 +127,8 @@ note-warn = hsl(0, 100%, 50%)
margin: 1em 0
hr
border-bottom: none;
border-top: 1px dashed color-border;
border-top: 1px dashed var(--color-border);
a
color: color-link
text-decoration: none
&:hover
color: color-link-hover
text-decoration: underline
@media print
color: color-default
text-decoration: underline
&:after
content: " (" attr(href) ")"
font-size: 80%
&[target="_blank"]
position: relative
&::after
@@ -175,7 +164,7 @@ note-warn = hsl(0, 100%, 50%)
blockquote
padding: 0 20px
position: relative
border: 1px solid color-border
border: 1px solid var(--color-border)
border-left: 5px solid #ddd
footer
margin: 1em 0
@@ -186,6 +175,8 @@ note-warn = hsl(0, 100%, 50%)
padding: 0 0.3em
a
color: color-grey
@media (prefers-color-scheme: dark)
border-color: var(--color-gray)
.note
&.tip
border-left-color: note-tip
@@ -207,7 +198,7 @@ note-warn = hsl(0, 100%, 50%)
margin-top: 0
table
max-width: 100%
border: 1px solid color-border
border: 1px solid var(--color-border)
th
font-weight: bold
th, td
@@ -215,12 +206,14 @@ note-warn = hsl(0, 100%, 50%)
tr
&:nth-child(2n)
background: #eee
@media (prefers-color-scheme: dark)
background: var(--color-gray)
.article-footer
margin: 60px 0 0
border-top: 1px solid color-border
border-top: 1px solid var(--color-border)
text-align: center
color: color-gray
color: var(--color-gray)
line-height: 1em
padding-top: 1em
position: relative
@@ -228,13 +221,13 @@ note-warn = hsl(0, 100%, 50%)
display: none
$article-footer-link
color: color-link
color: var(--color-link)
text-decoration: none
font-weight: bold
text-transform: uppercase
position: absolute
&:hover
color: color-link-hover
color: var(--color-link-hover)
@media print
display: none
span
+19 -3
View File
@@ -1,6 +1,8 @@
#playground
background: white
background: var(--color-content-bg)
overflow: hidden
.wrapper
margin-bottom: 40px
h1
font-size: 36px
font-weight: 300
@@ -10,11 +12,10 @@
display: flex
flex-wrap: wrap;
justify-content: space-between
margin-bottom: 30px
font-size: 1em
.col
flex: 1 1 100%
margin-bottom: 30px
margin-bottom: 10px
@media mq-normal
.row
@@ -29,3 +30,18 @@
margin-top: 2vh
#previewEl
height: 70vh
.hide
display: none
.loader
width: 75px
height: 75px
margin: 150px auto 200px
border-top: 5px solid #292929
border-right: 5px solid #efefef
border-bottom: 5px solid #efefef
border-left: 5px solid #efefef
border-radius: 100px
animation: spin 1s infinite linear
@keyframes spin
100%
transform: rotate(360deg)
+5 -5
View File
@@ -11,22 +11,22 @@
margin-top: 40px
padding: 10px 0
font-weight: bold
color: color-link
color: var(--color-link)
display: inline-block
border-top: 1px solid color-border
border-top: 1px solid var(--color-border)
line-height: 1
.sidebar-title:first-child
margin-top: 0
.sidebar-link
display: block
color: color-default
color: var(--color-default)
text-decoration: none
padding: 7px 0
line-height: 1
position: relative
width: 100%
&.current
color: color-link
color: var(--color-link)
&:hover
color: color-link-hover
color: var(--color-link-hover)
+29 -7
View File
@@ -3,12 +3,34 @@ support-for-ie = false
vendor-prefixes = webkit moz ms official
// Colors
color-default = #444
color-gray = #999
color-border = #e3e3e3
color-navy = hsl(210, 25%, 12%)
color-link = #0e83cd
color-link-hover = lighten(color-link, 10%)
:root {
--color-default: #444
--color-default-invert: #d1d2d4
--color-gray: #999
--color-border: #e3e3e3
--color-navy: hsl(210, 25%, 12%)
--color-content-bg: #fff
--color-news-bg: darken(hsl(210, 25%, 12%), 5%)
--color-navy-lighter: lighten(hsl(210, 25%, 12%), 10%)
--color-link: #0e83cd
--color-link-hover: lighten(#0e83cd, 10%)
}
@media (prefers-color-scheme: dark) {
:root {
--color-default: #d1d2d4
--color-default-invert: #444
--color-gray: invert(#999)
--color-border: #6a6a6a
--color-backgrond: #fff
--color-navy: hsl(210, 25%, 12%)
--color-news-bg: darken(hsl(210, 25%, 12%), 5%)
--color-navy-lighter: lighten(hsl(210, 25%, 12%), 7%)
--color-content-bg: #1c1c1c
--color-link: #0e83cd
--color-link-hover: lighten(#0e83cd, 10%)
}
}
// Typography
font-sans = "Helvetica Neue", Helvetica, Arial, sans-serif
@@ -18,7 +40,7 @@ font-size = 15px
line-height = 1.6em
// Layout
max-width = 1200px
max-width = 1800px
gutter-width = 20px
sidebar-width = 220px
mobile-nav-width = 260px
+19 -1
View File
@@ -59,6 +59,7 @@
// playground
/* global liquidjs, ace */
if (!location.pathname.match(/playground.html$/)) return;
updateVersion(liquidjs.version);
const engine = new liquidjs.Liquid();
const editor = createEditor('editorEl', 'liquid');
const dataEditor = createEditor('dataEl', 'json');
@@ -75,9 +76,20 @@
editor.on('change', update);
dataEditor.on('change', update);
update();
ready();
function ready() {
const loader = document.querySelector('.loader');
loader.classList.add('hide');
loader.setAttribute('aria-busy', false);
const editors = document.querySelector('#editors');
editors.classList.remove('hide');
editors.setAttribute('aria-hide', false);
}
function createEditor(id, lang) {
var editor = ace.edit(id);
const editor = ace.edit(id);
editor.setTheme('ace/theme/tomorrow_night');
editor.getSession().setMode('ace/mode/' + lang);
editor.getSession().setOptions({
@@ -121,6 +133,12 @@
function utoa(str) {
return btoa(unescape(encodeURIComponent(str)));
}
function updateVersion(version) {
document.querySelector('.version').innerHTML = `
liquidjs@<a target="_blank" href="https://www.npmjs.com/package/liquidjs/v/${version}">${version}</a>
`
}
}());
if ('serviceWorker' in navigator) {
+1375 -24169
View File
File diff suppressed because it is too large Load Diff
+34 -13
View File
@@ -1,6 +1,6 @@
{
"name": "liquidjs",
"version": "9.28.1",
"version": "9.35.1",
"description": "A simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript.",
"main": "dist/liquid.node.cjs.js",
"module": "dist/liquid.node.esm.js",
@@ -12,14 +12,15 @@
"types": "dist/liquid.d.ts",
"scripts": {
"lint": "eslint \"**/*.ts\" .",
"check": "npm test && npm run lint",
"check": "npm run build && npm test && npm run lint && npm run perf:diff",
"test": "nyc mocha \"test/**/*.ts\"",
"test:e2e": "mocha \"test/e2e/**/*.ts\"",
"perf": "cd benchmark && npm ci && npm start",
"perf:diff": "bin/perf-diff.sh",
"perf:engines": "cd benchmark && npm run engines",
"postversion": "npm run build:dist",
"build": "npm run build:dist && npm run build:docs",
"build:dist": "rollup -c rollup.config.ts && ls -lh dist",
"build:dist": "rollup -c rollup.config.ts",
"build:docs": "bin/build-docs.sh"
},
"bin": {
@@ -73,15 +74,15 @@
"@types/sinon": "^7.0.6",
"@types/sinon-chai": "^3.2.2",
"@types/supertest": "^2.0.7",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"all-contributors-cli": "^6.20.0",
"benchmark": "^2.1.4",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"coveralls": "^3.0.2",
"cross-env": "^5.2.0",
"eslint": "^6.8.0",
"eslint": "^7.32.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.15.0",
"eslint-plugin-mocha": "^5.3.0",
@@ -94,19 +95,20 @@
"mocha": "^9.0.1",
"nyc": "^15.1.0",
"regenerator-runtime": "^0.12.1",
"rollup": "^1.1.2",
"rollup": "^2.61.0",
"rollup-plugin-replace": "^2.1.0",
"rollup-plugin-typescript2": "^0.21.1",
"rollup-plugin-typescript2": "^0.31.1",
"rollup-plugin-uglify": "^5.0.2",
"rollup-plugin-version-injector": "^1.3.3",
"semantic-release": "^17.2.3",
"sinon": "^7.5.0",
"sinon-chai": "^3.3.0",
"supertest": "^3.4.2",
"ts-node": "^8.0.2",
"tslib": "^1.9.3",
"typedoc": "^0.15.0",
"tslib": "^2.3.1",
"typedoc": "^0.19.2",
"typedoc-plugin-markdown": "^2.2.17",
"typescript": "^3.3.3"
"typescript": "^4.5.3"
},
"release": {
"branch": "master",
@@ -126,7 +128,25 @@
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
"@semantic-release/github"
[
"@semantic-release/github",
{
"assets": [
{
"path": "dist/*.umd.js",
"label": "liquid.js"
},
{
"path": "dist/*.min.js",
"label": "liquid.min.js"
},
{
"path": "dist/*.min.js.map",
"label": "liquid.min.js.map"
}
]
}
]
]
},
"nyc": {
@@ -139,5 +159,6 @@
"pre-commit": "npm run check",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
},
"dependencies": {}
}
+41 -59
View File
@@ -2,6 +2,7 @@ import { uglify } from 'rollup-plugin-uglify'
import pkg from './package.json'
import typescript from 'rollup-plugin-typescript2'
import replace from 'rollup-plugin-replace'
import versionInjector from 'rollup-plugin-version-injector'
const version = process.env.VERSION || pkg.version
const sourcemap = true
@@ -13,13 +14,35 @@ const banner = `/*
const treeshake = {
propertyReadSideEffects: false
}
const tsconfig = {
tsconfigOverride: {
include: [ 'src' ],
exclude: [ 'test', 'benchmark' ],
compilerOptions: {
target: 'es5',
module: 'ES2015'
}
}
}
const versionInjection = versionInjector({
injectInComments: false,
injectInTags: {
fileRegexp: /\.(ts|js|html|css)$/,
tagId: 'VI',
dateFormat: 'mmmm d, yyyy HH:MM:ss'
},
packageJson: './package.json',
logLevel: 'info',
logger: console,
exclude: []
})
const input = './src/liquid.ts'
const replaceFS = {
const browserFS = {
include: './src/liquid-options.ts',
delimiters: ['', ''],
'./fs/node': './fs/browser'
}
const replaceStream = {
const browserStream = {
include: './src/render/render.ts',
delimiters: ['', ''],
'../emitters/streamed-emitter': '../emitters/streamed-emitter-browser'
@@ -32,16 +55,7 @@ const nodeCjs = {
banner
}],
external: ['path', 'fs'],
plugins: [typescript({
tsconfigOverride: {
include: [ 'src' ],
exclude: [ 'test', 'benchmark' ],
compilerOptions: {
target: 'ES2017',
module: 'ES2015'
}
}
})],
plugins: [versionInjection, typescript(tsconfig)],
treeshake,
input
}
@@ -53,16 +67,7 @@ const nodeEsm = {
banner
}],
external: ['path', 'fs'],
plugins: [typescript({
tsconfigOverride: {
include: [ 'src' ],
exclude: [ 'test', 'benchmark' ],
compilerOptions: {
target: 'ES2017',
module: 'ES2015'
}
}
})],
plugins: [versionInjection, typescript(tsconfig)],
treeshake,
input
}
@@ -75,18 +80,10 @@ const browserEsm = {
}],
external: ['path', 'fs'],
plugins: [
replace(replaceFS),
replace(replaceStream),
typescript({
tsconfigOverride: {
include: [ 'src' ],
exclude: [ 'test', 'benchmark' ],
compilerOptions: {
target: 'ES2017',
module: 'ES2015'
}
}
})
versionInjection,
replace(browserFS),
replace(browserStream),
typescript(tsconfig)
],
treeshake,
input
@@ -101,18 +98,10 @@ const browserUmd = {
banner
}],
plugins: [
replace(replaceFS),
replace(replaceStream),
typescript({
tsconfigOverride: {
include: [ 'src' ],
exclude: [ 'test', 'benchmark' ],
compilerOptions: {
target: 'es5',
module: 'ES2015'
}
}
})
versionInjection,
replace(browserFS),
replace(browserStream),
typescript(tsconfig)
],
treeshake,
input
@@ -123,21 +112,14 @@ const browserMin = {
file: 'dist/liquid.browser.min.js',
name: 'liquidjs',
format: 'umd',
sourcemap
sourcemap,
banner
}],
plugins: [
replace(replaceFS),
replace(replaceStream),
typescript({
tsconfigOverride: {
include: [ 'src' ],
exclude: [ 'test', 'benchmark' ],
compilerOptions: {
target: 'es5',
module: 'ES2015'
}
}
}),
versionInjection,
replace(browserFS),
replace(browserStream),
typescript(tsconfig),
uglify()
],
treeshake,
+36 -15
View File
@@ -1,53 +1,74 @@
import { isArray, isNil, last as arrayLast } from '../../util/underscore'
import { argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast, hasOwnProperty } from '../../util/underscore'
import { toArray } from '../../util/collection'
import { isTruthy } from '../../render/boolean'
import { FilterImpl } from '../../template/filter/filter-impl'
import { Scope } from '../../context/scope'
import { isComparable } from '../../drop/comparable'
export const join = (v: any[], arg: string) => v.join(arg === undefined ? ' ' : arg)
export const last = (v: any) => isArray(v) ? arrayLast(v) : ''
export const first = (v: any) => isArray(v) ? v[0] : ''
export const reverse = (v: any[]) => [...v].reverse()
export const join = argumentsToValue((v: any[], arg: string) => toArray(v).join(arg === undefined ? ' ' : arg))
export const last = argumentsToValue((v: any) => isArray(v) ? arrayLast(v) : '')
export const first = argumentsToValue((v: any) => isArray(v) ? v[0] : '')
export const reverse = argumentsToValue((v: any[]) => [...toArray(v)].reverse())
export function sort<T> (this: FilterImpl, arr: T[], property?: string) {
const getValue = (obj: Scope) => property ? this.context.getFromScope(obj, property.split('.')) : obj
return toArray(arr).sort((lhs, rhs) => {
arr = toValue(arr)
const getValue = (obj: Scope) => property ? this.context.getFromScope(obj, stringify(property).split('.')) : obj
return [...toArray(arr)].sort((lhs, rhs) => {
lhs = getValue(lhs)
rhs = getValue(rhs)
return lhs < rhs ? -1 : (lhs > rhs ? 1 : 0)
})
}
export function sortNatural<T> (input: T[], property?: string) {
input = toValue(input)
const propertyString = stringify(property)
const compare = property === undefined
? caseInsensitiveCompare
: (lhs: T, rhs: T) => caseInsensitiveCompare(lhs[propertyString], rhs[propertyString])
return [...toArray(input)].sort(compare)
}
export const size = (v: string | any[]) => (v && v.length) || 0
export function map<T1, T2> (this: FilterImpl, arr: Scope[], property: string) {
return toArray(arr).map(obj => this.context.getFromScope(obj, property.split('.')))
export function map (this: FilterImpl, arr: Scope[], property: string) {
arr = toValue(arr)
return toArray(arr).map(obj => this.context.getFromScope(obj, stringify(property).split('.')))
}
export function compact<T> (this: FilterImpl, arr: T[]) {
return toArray(arr).filter(x => !isNil(x))
arr = toValue(arr)
return toArray(arr).filter(x => !isNil(toValue(x)))
}
export function concat<T1, T2> (v: T1[], arg: T2[] | T2): (T1 | T2)[] {
export function concat<T1, T2> (v: T1[], arg: T2[]): (T1 | T2)[] {
v = toValue(v)
return toArray(v).concat(arg)
}
export function slice<T> (v: T[], begin: number, length = 1): T[] {
export function slice<T> (v: T[] | string, begin: number, length = 1): T[] | string {
v = toValue(v)
if (isNil(v)) return []
if (!isArray(v)) v = stringify(v)
begin = begin < 0 ? v.length + begin : begin
return v.slice(begin, begin + length)
}
export function where<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): T[] {
arr = toValue(arr)
return toArray(arr).filter(obj => {
const value = this.context.getFromScope(obj, String(property).split('.'))
return expected === undefined ? isTruthy(value, this.context) : value === expected
const value = this.context.getFromScope(obj, stringify(property).split('.'))
if (expected === undefined) return isTruthy(value, this.context)
if (isComparable(expected)) return expected.equals(value)
return value === expected
})
}
export function uniq<T> (arr: T[]): T[] {
arr = toValue(arr)
const u = {}
return (arr || []).filter(val => {
if (u.hasOwnProperty(String(val))) return false
if (hasOwnProperty.call(u, String(val))) return false
u[String(val)] = true
return true
})
+6 -3
View File
@@ -1,11 +1,14 @@
import strftime from '../../util/strftime'
import { isString, isNumber } from '../../util/underscore'
import { LiquidDate } from '../../util/liquid-date'
import { toValue, stringify, isString, isNumber } from '../../util/underscore'
import { FilterImpl } from '../../template/filter/filter-impl'
import { TimezoneDate } from '../../util/timezone-date'
export function date (this: FilterImpl, v: string | Date, arg: string) {
const opts = this.context.opts
let date: Date
let date: LiquidDate
v = toValue(v)
arg = stringify(arg)
if (v === 'now' || v === 'today') {
date = new Date()
} else if (isNumber(v)) {
@@ -29,5 +32,5 @@ export function date (this: FilterImpl, v: string | Date, arg: string) {
}
function isValidDate (date: any): date is Date {
return date instanceof Date && !isNaN(date.getTime())
return (date instanceof Date || date instanceof TimezoneDate) && !isNaN(date.getTime())
}
+4 -4
View File
@@ -20,17 +20,17 @@ export function escape (str: string) {
}
function unescape (str: string) {
return String(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m])
return stringify(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m])
}
export function escapeOnce (str: string) {
return escape(unescape(str))
return escape(unescape(stringify(str)))
}
export function newlineToBr (v: string) {
return v.replace(/\n/g, '<br />\n')
return stringify(v).replace(/\n/g, '<br />\n')
}
export function stripHtml (v: string) {
return v.replace(/<script.*?<\/script>|<!--.*?-->|<style.*?<\/style>|<.*?>/g, '')
return stringify(v).replace(/<script.*?<\/script>|<!--.*?-->|<style.*?<\/style>|<.*?>/g, '')
}
+1 -1
View File
@@ -3,5 +3,5 @@ export * from './math'
export * from './url'
export * from './array'
export * from './date'
export * from './object'
export * from './misc'
export * from './string'
+14 -20
View File
@@ -1,30 +1,24 @@
import { caseInsensitiveCompare } from '../../util/underscore'
import { toValue, argumentsToValue } from '../../util/underscore'
export const abs = Math.abs
export const atLeast = Math.max
export const atMost = Math.min
export const ceil = Math.ceil
export const dividedBy = (v: number, arg: number) => v / arg
export const floor = Math.floor
export const minus = (v: number, arg: number) => v - arg
export const modulo = (v: number, arg: number) => v % arg
export const times = (v: number, arg: number) => v * arg
export const abs = argumentsToValue(Math.abs)
export const atLeast = argumentsToValue(Math.max)
export const atMost = argumentsToValue(Math.min)
export const ceil = argumentsToValue(Math.ceil)
export const dividedBy = argumentsToValue((v: number, arg: number) => v / arg)
export const floor = argumentsToValue(Math.floor)
export const minus = argumentsToValue((v: number, arg: number) => v - arg)
export const modulo = argumentsToValue((v: number, arg: number) => v % arg)
export const times = argumentsToValue((v: number, arg: number) => v * arg)
export function round (v: number, arg = 0) {
v = toValue(v)
arg = toValue(arg)
const amp = Math.pow(10, arg)
return Math.round(v * amp) / amp
}
export function plus (v: number, arg: number) {
v = toValue(v)
arg = toValue(arg)
return Number(v) + Number(arg)
}
export function sortNatural (input: any[], property?: string) {
if (!input || !input.sort) return []
if (property !== undefined) {
return [...input].sort(
(lhs, rhs) => caseInsensitiveCompare(lhs[property], rhs[property])
)
}
return [...input].sort(caseInsensitiveCompare)
}
+14
View File
@@ -0,0 +1,14 @@
import { isFalsy } from '../../render/boolean'
import { isArray, isString, toValue } from '../../util/underscore'
import { FilterImpl } from '../../template/filter/filter-impl'
export function Default<T1 extends boolean, T2> (this: FilterImpl, value: T1, defaultValue: T2, ...args: Array<[string, any]>): T1 | T2 {
value = toValue(value)
if (isArray(value) || isString(value)) return value.length ? value : defaultValue
if (value === false && (new Map(args)).get('allow_false')) return false as T1
return isFalsy(value, this.context) ? defaultValue : value
}
export function json (value: any) {
return JSON.stringify(value)
}
-11
View File
@@ -1,11 +0,0 @@
import { isFalsy } from '../../render/boolean'
import { isArray, isString, toValue } from '../../util/underscore'
import { FilterImpl } from '../../template/filter/filter-impl'
export function Default<T1, T2> (this: FilterImpl, v: string | T1, arg: T2): string | T1 | T2 {
if (isArray(v) || isString(v)) return v.length ? v : arg
return isFalsy(toValue(v), this.context) ? arg : v
}
export function json (v: any) {
return JSON.stringify(v)
}
+7 -3
View File
@@ -41,7 +41,11 @@ export function rstrip (str: string) {
}
export function split (v: string, arg: string) {
return stringify(v).split(String(arg))
const arr = stringify(v).split(String(arg))
// align to ruby split, which is the behavior of shopify/liquid
// see: https://ruby-doc.org/core-2.4.0/String.html#method-i-split
while (arr.length && arr[arr.length - 1] === '') arr.pop()
return arr
}
export function strip (v: string) {
@@ -68,11 +72,11 @@ export function replaceFirst (v: string, arg1: string, arg2: string) {
export function truncate (v: string, l = 50, o = '...') {
v = stringify(v)
if (v.length <= l) return v
return v.substr(0, l - o.length) + o
return v.substring(0, l - o.length) + o
}
export function truncatewords (v: string, l = 15, o = '...') {
const arr = v.split(/\s+/)
const arr = stringify(v).split(/\s+/)
let ret = arr.slice(0, l).join(' ')
if (arr.length >= l) ret += o
return ret
+4 -2
View File
@@ -1,2 +1,4 @@
export const urlDecode = (x: string) => x.split('+').map(decodeURIComponent).join(' ')
export const urlEncode = (x: string) => x.split(' ').map(encodeURIComponent).join('+')
import { stringify } from '../../util/underscore'
export const urlDecode = (x: string) => stringify(x).split('+').map(decodeURIComponent).join(' ')
export const urlEncode = (x: string) => stringify(x).split(' ').map(encodeURIComponent).join('+')
+1 -1
View File
@@ -9,7 +9,7 @@ export default {
tokenizer.advance()
this.value = tokenizer.remaining()
},
render: function * (ctx: Context) {
render: function * (ctx: Context): Generator<unknown, void, unknown> {
ctx.bottom()[this.key] = yield this.liquid._evalValue(this.value, ctx)
}
} as TagImplOptions
+1 -1
View File
@@ -17,7 +17,7 @@ export default {
})
stream.start()
},
render: function * (ctx: Context) {
render: function * (ctx: Context): Generator<unknown, void, string> {
const r = this.liquid.renderer
const html = yield r.renderTemplates(this.templates, ctx)
ctx.bottom()[this.variable] = html
+13
View File
@@ -0,0 +1,13 @@
import { Value } from '../../template/value'
import { Emitter } from '../../emitters/emitter'
import { TagImplOptions, TagToken, Context } from '../../types'
export default {
parse: function (token: TagToken) {
this.value = new Value(token.args, this.liquid)
},
render: function * (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
const val = yield this.value.value(ctx, false)
emitter.write(val)
}
} as TagImplOptions
+7 -2
View File
@@ -1,7 +1,7 @@
import { assert, Tokenizer, evalToken, Emitter, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../../types'
import { toEnumerable } from '../../util/collection'
import { ForloopDrop } from '../../drop/forloop-drop'
import { Hash } from '../../template/tag/hash'
import { Hash, HashValue } from '../../template/tag/hash'
const MODIFIERS = ['offset', 'limit', 'reversed']
@@ -38,7 +38,7 @@ export default {
stream.start()
},
render: function * (ctx: Context, emitter: Emitter) {
render: function * (ctx: Context, emitter: Emitter): Generator<unknown, void | string, HashValue | Template[]> {
const r = this.liquid.renderer
let collection = toEnumerable(yield evalToken(this.collection, ctx))
@@ -47,7 +47,11 @@ export default {
return
}
const continueKey = 'continue-' + this.variable + '-' + this.collection.getText()
ctx.push({ continue: ctx.getRegister(continueKey) })
const hash = yield this.hash.render(ctx)
ctx.pop()
const modifiers = this.liquid.options.orderedFilterParameters
? Object.keys(hash).filter(x => MODIFIERS.includes(x))
: MODIFIERS.filter(x => hash[x] !== undefined)
@@ -58,6 +62,7 @@ export default {
return reversed(collection)
}, collection)
ctx.setRegister(continueKey, (hash['offset'] || 0) + collection.length)
const scope = { forloop: new ForloopDrop(collection.length, this.collection.getText(), this.variable) }
ctx.push(scope)
for (const item of collection) {
+1 -1
View File
@@ -22,7 +22,7 @@ export default {
.start()
},
render: function * (ctx: Context, emitter: Emitter) {
render: function * (ctx: Context, emitter: Emitter): Generator<unknown, void, string> {
const r = this.liquid.renderer
for (const { predicate, templates } of this.branches) {
+2 -2
View File
@@ -20,7 +20,7 @@ export default {
} else tokenizer.p = begin
} else tokenizer.p = begin
this.hash = new Hash(tokenizer.remaining())
this.hash = new Hash(tokenizer.remaining(), this.liquid.options.jekyllInclude)
},
render: function * (ctx: Context, emitter: Emitter) {
const { liquid, hash, withVar } = this
@@ -34,7 +34,7 @@ export default {
const scope = yield hash.render(ctx)
if (withVar) scope[filepath] = evalToken(withVar, ctx)
const templates = yield liquid._parsePartialFile(filepath, ctx.sync, this['currentFile'])
ctx.push(scope)
ctx.push(ctx.opts.jekyllInclude ? { include: scope } : scope)
yield renderer.renderTemplates(templates, ctx, emitter)
ctx.pop()
ctx.restoreRegister(saved)
+3 -1
View File
@@ -16,10 +16,12 @@ import tablerow from './tablerow'
import unless from './unless'
import Break from './break'
import Continue from './continue'
import echo from './echo'
import liquid from './liquid'
import { TagImplOptions } from '../../template/tag/tag-impl-options'
const tags: { [key: string]: TagImplOptions } = {
assign, 'for': For, capture, 'case': Case, comment, include, render, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue
assign, 'for': For, capture, 'case': Case, comment, include, render, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue, echo, liquid
}
export default tags
+14
View File
@@ -0,0 +1,14 @@
import { Emitter } from '../../emitters/emitter'
import { TagImplOptions, TagToken, Context } from '../../types'
import { Tokenizer } from '../../parser/tokenizer'
export default {
parse: function (token: TagToken) {
const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
const tokens = tokenizer.readLiquidTagTokens(this.liquid.options)
this.tpls = this.liquid.parser.parseTokens(tokens)
},
render: function * (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
yield this.liquid.renderer.renderTemplates(this.tpls, ctx, emitter)
}
} as TagImplOptions
+16 -10
View File
@@ -1,3 +1,4 @@
import { __assign } from 'tslib'
import { assert } from '../../util/assert'
import { ForloopDrop } from '../../drop/forloop-drop'
import { toEnumerable } from '../../util/collection'
@@ -50,13 +51,13 @@ export default {
const filepath = yield this.renderFilePath(this['file'], ctx, liquid)
assert(filepath, () => `illegal filename "${filepath}"`)
const childCtx = new Context({}, ctx.opts, ctx.sync)
const scope = yield hash.render(ctx)
const childCtx = new Context({}, ctx.opts, { sync: ctx.sync, globals: ctx.globals, strictVariables: ctx.strictVariables })
const scope = childCtx.bottom()
__assign(scope, yield hash.render(ctx))
if (this['with']) {
const { value, alias } = this['with']
scope[alias || filepath] = evalToken(value, ctx)
}
childCtx.push(scope)
if (this['for']) {
const { value, alias } = this['for']
@@ -67,7 +68,7 @@ export default {
scope[alias] = item
const templates = yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile'])
yield liquid.renderer.renderTemplates(templates, childCtx, emitter)
scope.forloop.next()
scope['forloop'].next()
}
} else {
const templates = yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile'])
@@ -91,15 +92,20 @@ export function parseFilePath (tokenizer: Tokenizer, liquid: Liquid): ParsedFile
if (file.getText() === 'none') return null
if (TypeGuards.isQuotedToken(file)) {
// for filenames like "files/{{file}}", eval as liquid template
const tpls = liquid.parse(evalQuotedToken(file))
// for filenames like "files/file.liquid", extract the string directly
if (tpls.length === 1 && TypeGuards.isHTMLToken(tpls[0].token)) return tpls[0].token.getContent()
return tpls
const templates = liquid.parse(evalQuotedToken(file))
return optimize(templates)
}
return file
}
const filepath = tokenizer.readFileName().getText()
return filepath === 'none' ? null : filepath
const tokens = [...tokenizer.readFileNameTemplate(liquid.options)]
const templates = optimize(liquid.parser.parseTokens(tokens))
return templates === 'none' ? null : templates
}
function optimize (templates: Template[]): string | Template[] {
// for filenames like "files/file.liquid", extract the string directly
if (templates.length === 1 && TypeGuards.isHTMLToken(templates[0].token)) return templates[0].token.getContent()
return templates
}
export function renderFilePath (file: ParsedFileName, ctx: Context, liquid: Liquid) {
+1
View File
@@ -1,4 +1,5 @@
export interface Cache<T> {
write (key: string, value: T): void | Promise<void>;
read (key: string): T | undefined | Promise<T | undefined>;
remove (key: string): void | Promise<void>;
}
+38 -12
View File
@@ -1,22 +1,42 @@
import { Drop } from '../drop/drop'
import { __assign } from 'tslib'
import { NormalizedFullOptions, defaultOptions } from '../liquid-options'
import { NormalizedFullOptions, defaultOptions, RenderOptions } from '../liquid-options'
import { Scope } from './scope'
import { isArray, isNil, isString, isFunction, toLiquid } from '../util/underscore'
import { InternalUndefinedVariableError } from '../util/error'
export class Context {
/**
* insert a Context-level empty scope,
* for tags like `{% capture %}` `{% assign %}` to operate
*/
private scopes: Scope[] = [{}]
private registers = {}
/**
* user passed in scope
* `{% increment %}`, `{% decrement %}` changes this scope,
* whereas `{% capture %}`, `{% assign %}` only hide this scope
*/
public environments: Scope
/**
* global scope used as fallback for missing variables
*/
public globals: Scope
public sync: boolean
/**
* The normalized liquid options object
*/
public opts: NormalizedFullOptions
public constructor (env: object = {}, opts: NormalizedFullOptions = defaultOptions, sync = false) {
this.sync = sync
/**
* Throw when accessing undefined variable?
*/
public strictVariables: boolean;
public constructor (env: object = {}, opts: NormalizedFullOptions = defaultOptions, renderOptions: RenderOptions = {}) {
this.sync = !!renderOptions.sync
this.opts = opts
this.globals = opts.globals
this.globals = renderOptions.globals ?? opts.globals
this.environments = env
this.strictVariables = renderOptions.strictVariables ?? this.opts.strictVariables
}
public getRegister (key: string) {
return (this.registers[key] = this.registers[key] || {})
@@ -39,11 +59,11 @@ export class Context {
return this.getFromScope(scope, paths)
}
public getFromScope (scope: object, paths: string[] | string) {
if (typeof paths === 'string') paths = paths.split('.')
return paths.reduce((scope, path) => {
scope = readProperty(scope, path)
if (isNil(scope) && this.opts.strictVariables) {
throw new InternalUndefinedVariableError(path)
if (isString(paths)) paths = paths.split('.')
return paths.reduce((scope, path, i) => {
scope = readProperty(scope, path, this.opts.ownPropertyOnly)
if (isNil(scope) && this.strictVariables) {
throw new InternalUndefinedVariableError((paths as string[]).slice(0, i + 1).join!('.'))
}
return scope
}, scope)
@@ -67,10 +87,11 @@ export class Context {
}
}
export function readProperty (obj: Scope, key: string) {
export function readProperty (obj: Scope, key: string, ownPropertyOnly: boolean) {
if (isNil(obj)) return obj
obj = toLiquid(obj)
if (isFunction(obj[key])) return obj[key]()
const jsProperty = readJSProperty(obj, key, ownPropertyOnly)
if (isFunction(jsProperty)) return jsProperty.call(obj)
if (obj instanceof Drop) {
if (obj.hasOwnProperty(key)) return obj[key]
return obj.liquidMethodMissing(key)
@@ -78,6 +99,10 @@ export function readProperty (obj: Scope, key: string) {
if (key === 'size') return readSize(obj)
if (key === 'first') return readFirst(obj)
if (key === 'last') return readLast(obj)
return jsProperty
}
export function readJSProperty (obj: Scope, key: string, ownPropertyOnly: boolean) {
if (ownPropertyOnly && !Object.hasOwnProperty.call(obj, key)) return undefined
return obj[key]
}
@@ -92,6 +117,7 @@ function readLast (obj: Scope) {
}
function readSize (obj: Scope) {
if (obj.hasOwnProperty('size') || obj['size'] !== undefined) return obj['size']
if (isArray(obj) || isString(obj)) return obj.length
return obj['size']
if (typeof obj === 'object') return Object.keys(obj).length
}
+1
View File
@@ -3,6 +3,7 @@ import { Emitter } from './emitter'
export class StreamedEmitter implements Emitter {
public buffer = '';
// eslint-disable-next-line @typescript-eslint/no-var-requires
public stream: NodeJS.ReadWriteStream = new (require('stream').PassThrough)()
public write (html: any) {
this.stream.write(stringify(html))
+4 -2
View File
@@ -9,9 +9,11 @@ export interface FS {
readFileSync: (filepath: string) => string;
/** resolve a file against directory, for given `ext` option */
resolve: (dir: string, file: string, ext: string) => string;
/** defaults to "/", will be used for "within roots" check */
/** check if file is contained in `root`, always return `true` by default. Warning: not setting this could expose path traversal vulnerabilities. */
contains?: (root: string, file: string) => boolean;
/** defaults to "/" */
sep?: string;
/** dirname for a filepath, used when resolving relative path */
/** required for relative path resolving */
dirname?: (file: string) => string;
/** fallback file for lookup failure */
fallback?: (file: string) => string | undefined;

Some files were not shown because too many files have changed in this diff Show More