Coder Social home page Coder Social logo

harttle / liquidjs Goto Github PK

View Code? Open in Web Editor NEW
1.4K 24.0 223.0 39.06 MB

A simple, expressive, safe and Shopify compatible template engine in pure JavaScript.

Home Page: https://liquidjs.com

License: MIT License

TypeScript 93.08% HTML 0.01% Liquid 1.02% JavaScript 4.82% Shell 0.69% Handlebars 0.39%
liquid template-engine nodejs browser

liquidjs's Introduction

liquidjs

npm version npm downloads Coverage Build Status DUB license semantic-release

A simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript. The purpose of this repo is to provide a standard Liquid implementation for the JavaScript community so that Jekyll sites, Github Pages and Shopify templates can be ported to Node.js without pain.

logo

What's it like?

Basically there're two types of Liquid syntax: tags enclosed by {% %} and outputs enclosed by {{ }}. A Liquid template looks like:

{% if username %}
  {{ username | append: ", welcome to LiquidJS!" | capitalize }}
{% endif %}

A live demo is also available and here's a quick tutorial for Liquid syntax.

Installation

Install from npm in Node.js:

npm install liquidjs

Or use the UMD bundle from jsDelivr:

<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.browser.min.js"></script>

Or render directly from CLI using npx:

npx liquidjs --template 'Hello, {{ name }}!' --context '{"name": "Snake"}'

For more details, refer to the Setup Guide.

Who's Using LiquidJS?

  • Eleventy: Eleventy, a simpler static site generator.
  • Opensense: The smarter way to send email.
  • Directus: an instant REST+GraphQL API and intuitive no-code data collaboration app for any SQL database.
  • Semgrep: Lightweight static analysis for many languages.
  • Rock: An open source CMS, Relationship Management System (RMS) and Church Management System (ChMS) all rolled into one.
  • Mitosis: Write components once, run everywhere. Compiles to React, Vue, Qwik, Solid, Angular, Svelte, and more.
  • Pattern Lab: a frontend workshop environment that helps you build, view, test, and showcase your design system's UI components.
  • Builder.io: the first and only headless CMS with a visual editor that lets you drag and drop with your components, directly within your current site or app. Completely API-driven, for cleaner code and simpler workflows.
  • Microsoft Power Pages: a secure, enterprise-grade, low-code software as a service (SaaS) platform for creating, hosting, and administering modern external-facing business websites.
  • Azure API Management developer portal: an automatically generated, fully customizable website with the documentation of your APIs.

Feel free to create a PR or contact me to add your use case into this list!

Financial Support

If you personally love LiquidJS or it's benefiting your business, please consider financially support us:

Special thanks to the following sponsors!

Opensense Inc.
Opensense
Eleventy
Eleventy
Peter deHaan
Peter deHaan
Touchless
Touchless
Adam Darrah
Dropkiq
Dailycontributors
Dailycontributors
coni2k
Serkan Holat
amit777
amit777
Khaled Salem
Khaled Salem
Sentry
Sentry
Checkout Blocks
Checkout Blocks
Customer IO
Customer IO

Contributors โœจ

Want to contribute? see Contribution Guidelines. Thanks goes to these wonderful people:

Jun Yang
Jun Yang

๐Ÿšง ๐Ÿ’ป
chenos
chenos

๐Ÿ’ป
Zach Leatherman
Zach Leatherman

๐Ÿ›
Tim Hardy
Tim Hardy

๐Ÿ’ป
Paul Robert Lloyd
Paul Robert Lloyd

๐Ÿ’ป ๐Ÿ›
Alec Larson
Alec Larson

๐Ÿ’ป
Patrick Malouin
Patrick Malouin

๐Ÿ’ป ๐Ÿ“–
jaswrks
jaswrks

๐Ÿ’ป
ไธ‰ไธ‰
ไธ‰ไธ‰

๐Ÿ’ป ๐Ÿค”
ssendev
ssendev

๐Ÿ’ป ๐Ÿ“–
wojtask9
wojtask9

๐Ÿ’ป
Andrew Barclay
Andrew Barclay

๐Ÿ’ป
Cory Mawhorter
Cory Mawhorter

๐Ÿ’ป
Mehdi Jaffery
Mehdi Jaffery

๐Ÿ’ป
Robin Bijlani
Robin Bijlani

๐Ÿ’ป ๐Ÿ›
Ryan Kennedy
Ryan Kennedy

๐Ÿ’ป
Sami Kukkonen
Sami Kukkonen

๐Ÿ’ป
Scott Santucci
Scott Santucci

๐Ÿ’ป
Steven
Steven

๐Ÿ’ก ๐Ÿ’ป
azu
azu

๐Ÿ“–
Joonas
Joonas

๐Ÿ’ป
Jamel A.
Jamel A.

๐Ÿ’ป
Brandon Pittman
Brandon Pittman

๐Ÿ’ป
tgrandgent
tgrandgent

๐Ÿ’ป
Martin Schuster
Martin Schuster

๐Ÿ’ป
Ray
Ray

โš ๏ธ ๐Ÿ’ป
Cristofer Gonzales
Cristofer Gonzales

๐Ÿ’ป
Raymond Camden
Raymond Camden

๐Ÿ“–
Steve Stedman
Steve Stedman

๐Ÿ“–
Anthony Ciccarello
Anthony Ciccarello

๐Ÿ“–
Bogdan Chadkin
Bogdan Chadkin

๐Ÿ’ป
Tejas Manohar
Tejas Manohar

๐Ÿ’ป
Peter deHaan
Peter deHaan

๐Ÿ“–
amit777
amit777

๐Ÿ’ป
Steffen Schuldenzucker
Steffen Schuldenzucker

๐Ÿ’ป
Pixcell
Pixcell

๐Ÿ’ป
Jason Etcovitch
Jason Etcovitch

๐Ÿ’ป
ZC
ZC

๐Ÿ“–
Memmie Lenglet
Memmie Lenglet

๐Ÿ’ป
ilhamdev0
ilhamdev0

๐Ÿ“–
ไธ€้ฅฎไธ€ๅ•„็š†ๆ˜ฏไบบ็”Ÿ
ไธ€้ฅฎไธ€ๅ•„็š†ๆ˜ฏไบบ็”Ÿ

๐Ÿ“–
Amit Agarwal
Amit Agarwal

๐Ÿ“–
Laurin Quast
Laurin Quast

๐Ÿ’ป
Matt Vague
Matt Vague

๐Ÿ’ป
Liam Bigelow
Liam Bigelow

๐Ÿ’ป
Jason Kurian
Jason Kurian

๐Ÿ“–
d pham (they/them)
d pham (they/them)

๐Ÿ“–
Aleksandr Hovhannisyan
Aleksandr Hovhannisyan

๐Ÿ’ป
jg-rp
jg-rp

๐Ÿ’ป
Ameya Apte
Ameya Apte

๐Ÿ’ป
tbdrz
tbdrz

๐Ÿ“–
Santi Albo
Santi Albo

๐Ÿ“–
Yahang Wu
Yahang Wu

๐Ÿ“–
hongl
hongl

๐Ÿ“–
zxx-457
zxx-457

๐Ÿ“–
prassie
prassie

๐Ÿ“–
Slav Ivanov
Slav Ivanov

๐Ÿ’ป
Daniel Rosenberg
Daniel Rosenberg

๐Ÿ’ป
bobgubko
bobgubko

๐Ÿ’ป
BaNgan
BaNgan

๐Ÿ“–
Mahyar Pasarzangene
Mahyar Pasarzangene

๐Ÿ“–
Tomรกลก Hรผbelbauer
Tomรกลก Hรผbelbauer

๐Ÿ’ป ๐Ÿ“–
Jason Garber
Jason Garber

๐Ÿ’ป
Nick Reilingh
Nick Reilingh

๐Ÿ“–
Francisco Soto
Francisco Soto

๐Ÿ’ป
David LJ
David LJ

๐Ÿ“–
Rasmus Wriedt Larsen
Rasmus Wriedt Larsen

๐Ÿ“–
Bruno Carvalho
Bruno Carvalho

๐Ÿ’ป
ๅ‚…้น
ๅ‚…้น

๐Ÿ’ป
Joel Hamilton
Joel Hamilton

๐Ÿ’ป
Max Medve
Max Medve

๐Ÿ’ป

liquidjs's People

Contributors

aleclarson avatar aleksandrhovhannisyan avatar allcontributors[bot] avatar amit777 avatar cfjedimaster avatar chenos avatar dependabot[bot] avatar ebobby avatar harttle avatar ilhamdev0 avatar jasonetco avatar jaswrks avatar jg-rp avatar kayuapi avatar mastodon0 avatar n1ru4l avatar oott123 avatar pdehaan avatar pixcell avatar pmalouin avatar prassie avatar santialbo avatar semantic-release-bot avatar sschuldenzucker avatar ssendev avatar stedman avatar thardy avatar tomashubelbauer avatar wojtask9 avatar wyozi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

liquidjs's Issues

Error launching nodejs - v0.12.7

Getting an error when loading my application. I've tried reinstalling the library and it didn't solve this. Would you be able to look into this?

liquidjs/index.js:47
      .then(() => this.parse(html))
             ^
SyntaxError: Unexpected token )
    at exports.runInThisContext (vm.js:73:16)
    at Module._compile (module.js:443:25)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Module.require (module.js:365:17)
    at require (module.js:384:17)
    at Object.<anonymous> (app.js:3:14)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)

Thanks!

Includes and String requirement on Paths

(Love this libraryโ€”thank you!)

This is going to be a very specific nitpick so just keep that in mind. I wanted to use this lib to parse some existing Jekyll templates and while I donโ€™t expect a completely fluid parse, one thing that did trip me up: Jekyll includes.

Specifically, Jekyll uses {% include footer.html %} (without quotes) and this library expects {% include 'footer.html' %} (quotes). I suppose I just have a question: is this a limitation of the library (something that could be worked around) or a limitation of any non-Ruby implementation?

Thanks!
Zach

forloop.last incorrect if loop has a limit

Iโ€™ve discovered an issue with the forloop object when requesting the last value in an array. This works as expected should the loop have no limit, e.g.:

<!-- if array = [1,2,3,4,5,6] -->
{% for item in array -%}
  {{ item }}{% unless forloop.last %},{% endunless %}
{%- endfor %}

Output: 1,2,3,4,5,6 (note, no trailing comma).

However, if I add a limit, forloop.last is not applied:

<!-- if array = [1,2,3,4,5,6] -->
{% for item in array limit: 3 -%}
  {{ item }}{% unless forloop.last %},{% endunless %}
{%- endfor %}

Output: 1,2,3, (note the trailing comma). Expected output: 1,2,3

Iโ€™ve been using the Ruby implementation of Liquid via Jekyll, and this worked as expected. Building a site using 11ty, for which this project is a dependant, I get the result described above.

Assign when applying filters to the value to story

Consider this block of code

      {% assign transaction_size = 0 %}
      {% for transaction in transactions %}
KIND {{ transaction.kind }}<br />
        {% unless transaction.kind == "capture" or transaction.kind == "void" %}
ADDING FROM {{ transaction_size }}<br />
          {% assign transaction_size = transaction_size | plus: 1 %}
ADDING TO {{ transaction_size }}<br />
        {% endunless %}
      {% endfor %}

transaction_size is still 0, where as the loop should result intransaction_size being 2

Adjusting assign.js

    liquid.registerTag('assign', {
        parse: function(token){
            var match = token.args.match(re);
            assert(match, `illegal token ${token.raw}`);
            this.key = match[1];
            console.log('KEY IS ' + '|' + this.key + '|');
            this.value = match[2];
            console.log('VALUE IS |' + this.value + '|');
        },
        render: function(scope) {
            scope.set(this.key, liquid.evalOutput(this.value, scope));
            return Promise.resolve('');
        }
    });

Result is:

Console:

KEY IS |transaction_size|
VALUE IS | 0|
KEY IS |transaction_size|
VALUE IS | transaction_size | plus: 1|

render:

KIND paid
ADDING FROM 0
ADDING TO 1
KIND refund
ADDING FROM 0
ADDING TO 1
COUNT 0

assign doesn't seem to be applying filters to the right hand side before storing I think?!

About to attempt to debug/fix, but I'm getting pulled all over the place at $dayjob so raising the issue

error node js express start up with missing UL todo list

I ran into an issue trying to get the express sample to start up on my local machine.

module.js:442
    throw err;
    ^

Error: Cannot find module 'any-promise'
  at Function.Module._resolveFilename (module.js:440:15)
    at Function.Module._load (module.js:388:25)
    at Module.require (module.js:468:17)
    at require (internal/module.js:20:19)
   at Object.<anonymous> (../../Downloads/liquidjs-master/src/render.js:2:17)
    at Module._compile (module.js:541:32)
    at Object.Module._extensions..js (module.js:550:10)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)
    at Function.Module._load (module.js:409:3)

Solved by adjusting required liquidjs path and package json file.

Terminal:
Check npm install
Run npm install --save liquidjs

Packaged JSON File:
Added "liquidjs": "^2.0.1"

Replaced in app.js
var Liquid = require(../..) with var Liquid = require('liquidjs')

Solved node js error above however the UL list of todos are missing.

Any help with this would be great! Thanks

Promise support in scope.get

The ruby implementation of liquid supports async/lazy data access so that data can be loaded on-demand.

This is described here in the rust version of of liquid: cobalt-org/liquid-rust#118 (comment)

scope.get should work with promises so that I could do {{ some_async_product.price }} and have the product data load via API call that returns a promise.

This would be a big, breaking change, but I've implemented it in our fork and it was definitely not easy, but at this point it is working well. (I do worry about race conditions with scope.push/pop, though it hasn't been a problem yet.)

strict_variables not throwing exception for undefined object properties

Parsing with strict_variables seems to do what it should if one of my value variables is undefined (e.g. {{name}}), but it doesn't throw an exception if my variable is an object referencing a property (e.g. {{store.name}})

Here's some example code.

const data = {store:{}};
const Liquid = require('liquidjs');
const liquid = Liquid({ strict_variables: true, strict_filters: true });
liquid.parseAndRender('{{store.name}}', data);

All of my variables are nested like this, so it would be super useful to know if these properties are undefined without having to manually check.

Thanks

for..in with Object works differently than Shopify

In Shopify's liquid implementation, each iteration of a for..in loop supplies the next value of the object.
See example: https://help.shopify.com/themes/liquid/tags/iteration-tags#for

This module differs in that it supplies the next key of the object instead. This is arguably better, since in Shopify's implementation you don't even have access to the key, and in this module you can always just use object[key] to get the value. However, it still differs from how Shopify handles it, which leads to confusion, and templates needing to be adjusted.

Is this difference by design, or just an oversight?

If it's an oversight, then the test here:
https://github.com/harttle/shopify-liquid/blob/master/test/tags/for.js#L27
should be corrected.

Line 30 should read:
.to.eventually.equal('bar-haa-')
instead of
.to.eventually.equal('foo-coo-')

Thanks!

dynamicPartials: false, include tag, and subdirectories

Hi! Hello, me againโ€”same person that filed #51. I would like the following dynamicPartials: false test cases to pass:

dynamicPartials: false

{% include subfolder/included.liquid %} Fails
{% include 'subfolder/included.liquid' %} Single quotes, Fails
{% include "subfolder/included.liquid" %} Double quotes, Fails

dynamicPartials: true (for comparison)

{% include subfolder/included.liquid %} Fails
{% include 'subfolder/included.liquid' %} Single quotes, Passes
{% include "subfolder/included.liquid" %} Double quotes, Passes

This would fix 11ty/eleventy#72 for me! Thank you!

scope.propertyAccessSeq does not work properly

Problem with syntax like bar["foo"].zoo
I wrote the test:

expect(scope.propertyAccessSeq('bar["foo"].zoo'))
  .to.deep.equal(['bar', 'foo', 'zoo']);

and it failed:

  1) scope #propertyAccessSeq() should handle nested access:

      AssertionError: expected [ 'bar', 'foo', '', 'zoo' ] to deeply equal [ 'bar', 'foo', 'zoo' ]
      + expected - actual

       [
         "bar"
         "foo"
      -  ""
         "zoo"
       ]

Filter, parse object arguments bug

There is a bug in the way object filter arguments are parsed when the key is named the same as the value.

Example: foo: someval: someval with context { someval: 'hello' }

That should result in args that are [ 'someval', 'hello' ] but instead it results in [ 'someval', 'someval' ]

Here's a runkit that shows this: https://runkit.com/embed/auys4zba1gxn

I have it fixed in my fork. That also includes tests, but I can't PR because I've made a ton of breaking changes.

https://github.com/stampr/liquidjs/blob/f24c8c49b66890b1c7ca6d332edfa082d9398a42/src/filter.js#L36-L43

strict* settings

Are there any plans to implement strict_variables and/or strict_filters (or strictVariables/strictFilters to fit JS style)? This would be particlarly useful in an Express environment as it would allow the application author to choose whether to show the end user an incorrectly rendered page or a 500 error (depending on how the templating can affect the app functionality, one or the other may be preferable in different apps).

Include variables.

Ahh, found a missing feature. Would be super nice, if we could have the ability to define or pass variables for include statements. Such as

  {% assign sys = system %}
  {% include 'views/components/admin-toolbar' %}

or

{% include 'views/components/admin-toolbar', sys = system %}. These methods currently give Error: tag sys not found . Having these would be super useful, as this works in the official liquid markup.

Parser does not catch variable syntax error

The original liquid ruby gem fails on parsing if this case is given:
{{ user_name }
and generates this error:
Liquid syntax error: Variable '{{ user_name }' was not properly terminated with regexp: /\}\}/
I would expect this parser to catch these syntax errors as well and return a ParseError.
When trying to parse something with this syntax error, the tokenizer returns 'nested endless hash' or i don't know how to name it :)
screen shot 2018-02-28 at 10 56 31 am

I have looked through the config and haven't found anything that will help me :/
@harttle - I'm willing to contribute on this - just give me some guidance ;)

Thanks!

Reading partials from memory

I'd love to be able to have all my templates as strings in a plain object, something like:

templates = {
  'show.html': `
    <div>
      {% include 'menu.html' %}
      {% include 'body.html' %}
    </div>
  `,
  'menu.html': '...',
  'body.html': '...'
}

Is this possible with shopify-liquid? I understand I can simply do parseAndRender(templates['show.html'], { ... }), but is there a way I can get the partials/includes working as well?

Files without extensions should not always get the .liquid extension

I encountered a small problem with the file names.

In my project I have templates for files with various content (C/C++, JSON, markdown, XML).

To help Eclipse select the right editor, I kept the original file extension, and inserted -liquid before the extension.

With this rule, my files are named like initialize-liquid.c, package-liquid.json etc.

However, when I tried to use a file named LICENSE-liquid, I got some errors claiming that the file is not found. I had no time to investigate, I renamed the file to LICENSE.liquid and avoided the problem.

My understanding was that the parser did not actually check if the file is present, but was directly upset by the missing extension, tried to add the default .liquid, and failed.

Could you check if this is true? If so, is this really the desired behaviour? I don't see any reasons why files without extnsions should not be accepted; if they exist they are as good as any other files.

Can't recursively concat

Is this the expected behaviour for liquid, to not be able to assign recursively?

{% assign yes = "yes" | split: "," %}
    <h5>Does concat work?</h5>
    {% assign yes = yes | concat: yes %}
    {{ yes }}

Prints out ["yes"], as opposed to ["yes", "yes"]

getTemplate

So... I've finally gotten around to actually using the new async template engine, and overriding the getTemplate function on the engine. How would you recommend overriding the default behavior of changing the path in {% include 'dog' %} from 'dog' to 'c:\dev\myproject\dog.liquid'?

In my case, I'd just like for path to come into getTemplate simply as 'dog' because that's going to be the name in the database. Should we setup an option or something that can turn that behavior off?

render error for Truthy value

{% for link in docs %}
        <a href="/{{link.slug}}/" class="_list-item _icon-{{link.slug | split: '~' | first }} _list-dir" title="">
          <img src="/images/docs/{{link.slug | split: '~' | first }}.png" alt=""><span class="name">{{link.name}}{%if link.version and  link.version != '' %}~{{link.version}}{%endif%}
        </span></a>
{% endfor %}

docs data:

[
{"name":"Angular","slug":"angular","type":"angular","links":{"home":"https://angular.io/","code":"https://github.com/angular/angular"},
"version":"","release":"5.0.1","mtime":1510333264,"db_size":8769411},
{"name":"Angular","slug":"angular~4","type":"angular","links":{"home":"https://angular.io/","code":"https://github.com/angular/angular"},
"version":"4","release":"4.4.6","mtime":1510333140,"db_size":8247972}
]

{%if link.version and link.version != '' %}~{{link.version}}{%endif%}

catch error

"version":""
render the ~

may and operator case this, i am not sure.

but the follow is ok.

{%if link.version %}{%if link.version != '' %}~{{link.version}}{%endif%}{%endif%}

The `plus:` filter fails for string input

I encountered a problem with constructs like this:

{{ value | plus: 1 }}

when the value is a string.

The error was:

(node:82019) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 71): RenderError: cb(...).toFixed is not a function

For comparison, the equivalent minus filter works properly with the same value:

{{ value | minus: 1 }}

Checking the implementations, they are very similar:

'minus': bindFixed((v, arg) => v - arg),
'plus': bindFixed((v, arg) => v + arg),

I think that the problem is the JavaScript + operator, that behaves differently if the first operand is a string.

I'm not sure what the semantic of the original Shopify plus tag is, but I guess it is a numeric operation.

In this case I suggest you add an explicit conversion to number, to prevent the error.

Perhaps you should also consider cases when the second operand is a string, and must be converted to number.

NB: The same explicit conversion to number might be required to other tags too, first and second operand.

cols parameter in tablerow is mandatory?

Hi,
Is there any reason why the col parameter is mandatory on tablerow tag?

Didn't work:

<table>
{% tablerow product in collection.products %}
  {{ product.title }}
{% endtablerow %}
</table>

Works:

<table>
{% tablerow product in collection.products cols:1 %}
  {{ product.title }}
{% endtablerow %}
</table>

Base on official documentation section of tablerow, it seems that we can skip it.

Related test:
https://github.com/harttle/liquidjs/blob/master/test/tags/tablerow.js

Rendering includes with their own models

I have another requirement that I'm not sure how to implement using shopify-liquid. My cms uses the concept of template objects. It's basically a javascript object that has a template property that is the liquid template, and every other property is the model. I realize I'll probably need to fork and make some changes to get this to happen, but I wanted to run it by you first to get your feedback. For starters, I don't know how to pass an object as the model into an include. I'd like to be able to:

  • include my template objects in other templates
  • have the included template objects use their own models in rendering themselves
  • allow the parent to override any model properties that share the same name
  • chain the above all the way to the top of a nested include chain (allow parents to override any model properties in their nested includes if they have the same name)

Here's an example:

var templateObjectOne = { 
    name: "TemplateHeader", 
    title: "Fancy Title", 
    favoriteNumber: 11, 
    template: "<header>{{title}}-{{favoriteNumber}}</header>"
};
// Need to somehow default the "include 'TemplateHeader'" to use the model in templateObjectOne.
//  Perhaps just completely alter includes to expect templateObjects in my fork
var templateObjectTwo = { 
    name: "TemplateWithInclude", 
    favoriteNumber: 7,
    template: "{% include 'TemplateHeader' %}<div>some content</div>"
};
// Ideally, I would change to simply - engine.render(templateObjectTwo);
var tpl = engine.parse(templateObjectTwo.template); 
var output = engine.render(tpl, templateObjectTwo); 
// should equal "<header>Fancy Title-7</header><div>some content</div>"

I'd love to get your thoughts on this, especially on any quick wins or preferred extension points with what you have already. I'd prefer to make as few changes as possible to your code.

Thanks,
Tim

Generated strings in `include` statements aren't working

Using:

โ””โ”€โ”ฌ @11ty/[email protected]
  โ””โ”€โ”€ [email protected]

I'm using the following code to vary a background pattern based on the length of a page's title.

{% assign image = title | size | modulo: 5 | plus: 1 %}
{% include bg-{{image}} %}

That works fine in Liquid (Ruby) - I was compiling the site with Jekyll previously. But I've recently moved to a site compiler (11ty) that uses liquidjs and that no longer works. The 11ty debugger throws the following error:

RenderError: ENOENT: Failed to lookup bg-.liquid in: _includes, line:7

If I print the image variable {{image}} right above the include statement, I get a number as expected. Inside the include statement, however, image ends up undefined, and I'm unable to compile the site.

Thanks for your help!

Custom block tag scope

I'm trying to create a custom block tag like so:

main.liquid

{% carousel %}Carousel #1{% endcarousel %}

carousel.liquid

<div class="carousel">{% content %}</div>

Rendering main.liquid, should replace {% content %} with whatever is inside the carousel tags. In the example above it'd create:

result.html

<div class="carousel">Carousel #1</div>

I've coded tag registration like so:

engine.registerTag('content', { render: async () => '' });
engine.registerTag('carousel', {
  parse: (token, remainingTokens) => {

    let stream = engine.parser.parseStream(remainingTokens);
    this.tokens = [];

    stream
      .on('template', token => this.tokens.push(token))
      .on(`tag:endcarousel`, () => stream.stop())
      .on('end', () => { throw new Error(`tag ${token.raw} not closed`); });

    stream.start();
  },
  render: async (scope, hash) => {

    let innerTokens = this.tokens;
    let outerTokens = await engine.getTemplate('carousel');
    let result = [];

    for (let i = 0; i < outerTokens.length; i++) {
      let token = outerTokens[i];
      if (token.name === 'content') {
        Array.prototype.push.apply(result, innerTokens);
      } else {
        result.push(token);
      }
    }

    return await engine.renderer.renderTemplates(result, scope);
  }
});

The above code works until there are multiple {% carousel %} blocks on a template. In which case it'll just repeat the contents of the last defined carousel.

This tells me I have a block/scoping issue, and am perhaps not registering blocks/tokens correctly. Can somebody point me in the right direction? I've looked at how the layout and include tags are registered, but not sure I grasp what I need to do.

paginate tag does not supported

Seem you haven't support {% paginate %} tag yet. Is there any solution to render them?
Currently my code look like this, did I used it on wrong way?
{% paginate list by limit %}
{% for %}
{% endfor %}
{% endpaginate %}

trim_right too greedy

It looks like the trim_right option also removes white spaces from the next line.

For example:

    // {{ register.name }} bitfields.
{% for field in register.fields %}
    state->u.{{ deviceFamily | downcase }}.fld.{{ register.name | downcase | c_reserved }}.{{ field.name | downcase | c_reserved }} = cm_object_get_child_by_name(state->u.{{ deviceFamily | downcase }}.reg.{{ register.name | downcase | c_reserved }}, "{{ field.name }}"); // {{ field.bitOffset }}, {{ field.bitWidth }}.
{% endfor %} 

will generate:

// SRbitfields.
state->u.f4.fld.sr.awd= cm_object_get_child_by_name(state->u.f4.reg.sr, "AWD"); // 0, 1.
state->u.f4.fld.sr.eoc= cm_object_get_child_by_name(state->u.f4.reg.sr, "EOC"); // 1, 1.
state->u.f4.fld.sr.jeoc= cm_object_get_child_by_name(state->u.f4.reg.sr, "JEOC"); // 2, 1.
state->u.f4.fld.sr.jstrt= cm_object_get_child_by_name(state->u.f4.reg.sr, "JSTRT"); // 3, 1.
state->u.f4.fld.sr.strt= cm_object_get_child_by_name(state->u.f4.reg.sr, "STRT"); // 4, 1.
state->u.f4.fld.sr.ovr= cm_object_get_child_by_name(state->u.f4.reg.sr, "OVR"); // 5, 1.
// CR1bitfields.
state->u.f4.fld.cr1.awdch= cm_object_get_child_by_name(state->u.f4.reg.cr1, "AWDCH"); // 0, 5.

There are several problems here:

  • the initial spaces before state->u... are gone
  • the inter-word spaces (for example SR bitfields, CR1bitfields) are gone
  • I had to add a dot at the end of the line, otherwise strange things happened.

The second and third problems seems to indicate that the trim_right default should not apply to {{ ... }}, but only to tags.

In other implementations the white space control logic seems more elaborate (for example http://jinja.pocoo.org/docs/dev/templates/#whitespace-control).

`include` tag doesn't respect the paths specified in `view` while using with express

Hello,

I'm trying to setup a local development/preview environment for theme editing. I was able to get it running with every template file under templates folder. But for some reason, when using include tags. The paths specified in app.set('views', ['./', './templates']) is not respected by include tag.

Here's an example:

var Liquid = require('shopify-liquid');
var express = require('express');
var app = express();
var engine = Liquid({
  extname: '.liquid',
  cache: false});

// register liquid engine
app.engine('liquid', engine.express({
  strict_variables: false,         // Default: fasle
  strict_filters: false            // Default: false
}));
app.set('views', ['./', './templates', './snippets', './layout', './locales']);            // specify the views directory
app.set('view engine', 'liquid');       // set to default

in my template:

 <div class="grid-uniform">

      {% for product in collections.frontpage.products %}

        {% include 'product-grid-item' %}

      {% else %}
</div>

the exception I got is:
Error: ENOENT: no such file or directory, open '/product-grid-item' at Error (native)
seems like its looking for templates under root path?

How can I fix this or get this to work? Thanks!

Async Tags: Retrieve templates from database

How can I change layouts and includes to pull the templates from a database or other source than the filesystem? For example, in liquid-node, I overrode the following in order to cause includes to pull their templates from my own service by name in mongodb...

CustomFileSystem.prototype.readTemplateFile = function(path) {
        return templateEngine.getTemplate(path)
            .then((templateObject) => {
                if (templateObject && templateObject.template) {
                    return templateObject.template; // return the template string itself
                }
                else {
                    throw new Error(`template '${path}' not found`);
                }
            });

    };

templateEngine.engine.fileSystem = new CustomFileSystem();

I'm looking to replace my current implementation of liquid-node in my content management system with shopify-liquid. Liquid-node doesn't have the layout, include, and block functionality that you do, and I'm just not into coffeescript, so helping out with that project was difficult for me. I'm sure this is the first of many requests as I dig into your implementation and see if it will fit my needs :) I'm looking forward to seeing what your template engine can do. If this does work out, I will probably have some pull requests along the way.

Iterating a hash

Hello

I have been trying to iterate a hash/object in Liquid but the output is not as expected.

See Liquid reference: https://github.com/Shopify/liquid/wiki/Liquid-for-Designers#allowed-collection-types

The reference says:

When iterating a hash, item[0] contains the key, and item[1] contains the value:

{% for item in hash %}
  {{ item[0] }}: {{ item[1] }}
{% endfor %}

However, item[0] and item[1] contain the first and second characters of the item key instead of having the key and the value of current item.

Am I iterating the hash correctly? Is this a not-implemented feature?

PS: Thank you for the work on liquidjs. Its awesome. ๐Ÿ˜ƒ

assignments seem to be handled as references

I had a strange behaviour while doing simple additions.

lets assume the following liquid:

{% assign price_total = 0 %}
{% for item in items %}
    {% assign price_quantity = item.price | times: item.quantity %}
    {% assign price_total = price_total | plus: price_quantity %}
{% endfor %}

if items contains only one item with a price of 5.99 and a quantity of 1, total_price will return 11.98. i was really surprised in the first place. the iteration only takes place one time. my quess was, that the plus-filter already mutates price_total and the assignment mutates it again. this might be, because price_total is internally handled as an object and in JS objects are always references. so for mutating price_total a clone must be created and later reassigned.

the following works and proofs my suspicion:

{% assign price_total = 0 %}
{% for item in items %}
    {% assign price_quantity = item.price | times: item.quantity %}
    {% assign price_total_copy = price_total %}
    {% assign price_total = price_total_copy | plus: price_quantity %}
{% endfor %}

when i make a copy of price_total first, then mutate price_total_copy and reassign it to price_total, it works like a charm.

the first behaviour is the one that was originally intended and also used in the Shopify implementation.

assign variable scope doesn't include loops

Hi,
Thank you for your time on the library.

A bug:

{% assign total = 10 %}
{% for line in (0..2) %}
    {% assign total = 20 %}
{% endfor %}
Total: {{total}} -- should be 20<br>

shows 10 as the result, this is a bug.

The live dot liquid site correctly shows 20.

Thanks,
Larry

Parse liquid template in browser

Can the library support also running on browser?
From my observe, only the include tag, which need fs, or db access is not compatible with browser.

custom token definitions?

I want to use part of front end template and use part of back end template.
so catch the token definition rended!
open {{
close }}

I want to chang to
open: '<%=',
close: '%>'


or use raw block to render?

Plan to implement {{block.super}}

Hello, thanks for your hard works! currently I'm using nunjucks in our frontend project(mock server with express), but I have a plan to migrate to liquidjs instead, because our Backend is also using liquid, some of their syntax differencies bother me a lot because each time I have to manually adjust nunjucks templates to standard liquid syntax before sending them to backend developers, I have searched a lot and finally found liquidjs, really amazing, but I really miss the {{block.super}} syntax, since we have already supported "block", I would like to know do we have some plan to implement it as well? It would be a huge benefit and I think some others might also have the same question as me :)

strip_html filter does not remove elements that include attributes

The strip_html filter is not comparable to the same filter in the Ruby version. While Ruby Liquid removes all HTML elements, this implementation currently ignores any opening tag containing attributes.

Comparing the regex, we can see why. Ruby Liquid:

def strip_html(input)
    empty = ''.freeze
    input.to_s.gsub(/<script.*?<\/script>/m, empty).gsub(/<!--.*?-->/m, empty).gsub(/<style.*?<\/style>/m, empty).gsub(/<.*?>/m, empty)
end

As well as covering script, style and comment tags, it also removes any string of characters starting with < and ending with >.

Liquid JS on the other hand uses a different approach:

'strip_html': v => stringify(v).replace(/<\/?\s*\w+\s*\/?>/g, ''),

My JavaScript skills are pretty rough, but I monkey patched this filter to use the following regex instead:

str.replace(/<script.*?<\/script>|<!--.*?-->|<style.*?<\/style>|<.*?>/g, '')

This more closely matches that used in the Ruby implementation. Would a PR with this change be accepted at all?

Thanks,

Paul

Remove any-promise dependency

Hi, I'd like to ask for a feature request: remove any-promise dependency. In their Readme it specifies:

NOTE: You probably want native promises now

If you try to rollup liquidjs that bit will throw an error:

window_not_defined

This is because any-promise is using window.Promise straight away without checking that window exists first:

https://github.com/kevinbeaty/any-promise/blob/master/register-shim.js#L10

So, even if you wrap liquidjs with UMD, this dependency is not properly wrapped and it will blow up.

Express res.render treats variable as filter...

So, I have a template file, that has the following piece of Liquid:

{{% if system.user.Rightslevel >= 2 %}}
            Will include
            {{% include 'admin-toolbar' %}}
{{% endif %}}

And here's how I'm using shopify-liquid with express...

const Liquid = require('shopify-liquid');
const express = require('express');

const engine = new Liquid();
const app = express();

app.engine('liquid', engine.express());
app.set('views', './views'); 
app.set('view engine', 'liquid');

app.get('/', (req, res) => {
    const dataObject = {};
    if (req.cookies.authenticatedUser) {
        dataObject.user = JSON.parse(req.cookies.authenticatedUser);
    }
    res.render('index', { system: dataObject });
});

And here's what that gives me..

Error: filter "system" not found
   at Object._filterInstance.parse (E:\Workspace\Webdev\Foliage Native\node_modules\shopify-liquid\src\filter.js:22:23)
   at Object.construct (E:\Workspace\Webdev\Foliage Native\node_modules\shopify-liquid\src\filter.js:40:18)
   at filters.filters.map.str (E:\Workspace\Webdev\Foliage Native\node_modules\shopify-liquid\src\parser.js:87:48)
   at Array.map (native)
   at parseOutput (E:\Workspace\Webdev\Foliage Native\node_modules\shopify-liquid\src\parser.js:87:30)
   at parseToken (E:\Workspace\Webdev\Foliage Native\node_modules\shopify-liquid\src\parser.js:58:28)
   at Object.parse (E:\Workspace\Webdev\Foliage Native\node_modules\shopify-liquid\src\parser.js:47:28)
   at Object._engine.parse (E:\Workspace\Webdev\Foliage Native\node_modules\shopify-liquid\index.js:34:28)
   at Object._engine.handleCache (E:\Workspace\Webdev\Foliage Native\node_modules\shopify-liquid\index.js:71:18)
   at Object._engine.renderFile (E:\Workspace\Webdev\Foliage Native\node_modules\shopify-liquid\index.js:46:28)

Seems to me like it isn't treating the variable as such... anything I might have missed?

Offer a renderSync method?

Most templating engines offer synchronous render methods (ejs, mustache, handlebars, haml, pug, nunjucks, etc).

Does one exist for liquidjs? If not, could one be added?

(love this library btwโ€”thank you)

Date filter

Hey mate I was wondering if it is possible to use strings such as 2016-01-05T13:15:23 with the date filter or does it only support JS date objects?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.