npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

liquidish

v0.4.1

Published

Compile liquid-like templates to *anything*

Downloads

13

Readme

💧 Liquidish

tests Coverage Status npm

This variant of Liquid was created to compile Liquid-like syntax to another templating language. It was created to compile to ISPConfig's tpl syntax, seeing how it does not have proper IDE support and is not as flexible as Liquid.

🔭 Check out luttje/ispconfig-tailwind-theme for an example of how this package can be used.

 

[!WARNING] This is a work in progress. It's not finished yet. The documentation below may be incorrect or incomplete.

🚀 Using Liquidish

Liquidish is designed to work with Vite, but it can be used with any build tool that allows transforming files (e.g: Webpack and Rollup).

We'll assume you have a Vite project set up. If not, you can create one with npm init vite@latest.

  1. Install this package in your project:

    npm i -D liquidish

    Seeing how you will be using this in your bundler, you should likely install it as a dev dependency.

  2. Create a bunch of .liquid files you want to compile, e.g: src/templates.

    See the 📚 Liquidish Syntax for the syntax.

  3. We use the vite-plugin-static-copy to copy the .liquid files to the templates directory. Install it with:

    npm i -D vite-plugin-static-copy
  4. Modify your vite.config.js to include the Liquidish transformer:

    import { resolve } from 'path';
    import { defineConfig } from 'vite';
    import { viteStaticCopy } from 'vite-plugin-static-copy';
    import { ISPConfigTransformationStrategy } from 'liquidish/strategies';
    import { LiquidishTransformer } from 'liquidish';
    
    // Where the `.liquid` files are located
    const srcTemplatesPath = 'src/templates';
    
    // Create a transformer and specify the strategy
    // This example transforms the Liquidish syntax to ISPConfig's `tpl` syntax
    const liquidish = new LiquidishTransformer({
      strategyBuilder: (transformer) => new ISPConfigTransformationStrategy(transformer)
    });
    
    export default defineConfig({
      // ...
      plugins: [
        viteStaticCopy({
          targets: [
            {
              src: `${srcTemplatesPath}/**/*.liquid`,
              dest: 'templates',
    
              transform: (contents, path) => liquidish.transform(contents, path),
    
              rename: function (name, ext, fullPath) {
                const path = fullPath.replace(resolve(__dirname, srcTemplatesPath), '');
    
                // Rename the extension to what you want.
                // In our case ISPConfig expects `.htm` files
                return path.replace(/\.liquid$/, '.htm');
              },
            },
          ],
        }),
      ],
    });
  5. Run Vite:

    npm run dev

🎉 The .liquid files will now be transformed to .htm files in the templates directory.

Next steps

📚 Liquidish Syntax

Liquidish does not support all of Liquid's features. It is a subset of Liquid, with a few extra features.

[!WARNING] Beware that most of the below features can not contain %} in their content. This is because the transformation is done by matching the {% and %} characters.

*TODO: Add a way to escape the %} characters.

Variables

Variables are defined with double curly braces: {{ VARIABLE }}. They're mostly used to output the value of a variable at runtime.

<h1>{{ title }}</h1>

🕒 If variables are known at compile-time (e.g: when using render or for loops), they will be replaced with their value at compile-time. See the render and for sections for more information.

If-statements

If-statements are defined with {% if VARIABLE %} or {% if VARIABLE == 'VALUE' %}. You can expand the if-statement with {% elsif VARIABLE %} or {% elsif VARIABLE == 'VALUE' %} and {% else %}. They end with {% endif %}.

You can also use !=, >, <, >=, <= as operators for if/elsif statements.

{% if VARIABLE %}
    This will be shown if VARIABLE is truthy
{% elsif VARIABLE == 'VALUE' %}
    This will be shown if VARIABLE is 'VALUE'
{% else %}
    This will be shown if none of the above are true
{% endif %}

🕒 If variables are known at compile-time (e.g: when using render or for loops), if/elsif-statements containing them will be evaluated at compile-time. This is implemented in a pretty hacky way, so don't expect much of it. See the render and for sections for more information.

Unless statements

Unless-statements are defined with {% unless VARIABLE %} and are the opposite of if-statements. They end with {% endunless %}.

{% unless VARIABLE %}
    This will be shown if VARIABLE is falsy
{% endunless %}

🕒 If variables are known at compile-time (e.g: when using render or for loops), unless-statements containing them will be evaluated at compile-time. This is implemented in a pretty hacky way, so don't expect much of it. See the render and for sections for more information.

Comments

You can comment out code using {% comment %} and {% endcomment %}.

{% comment %}
    This is a comment
{% endcomment %}

By default this will be removed from the output. If you want to keep the comments, you can set showComments to true on the transformer:

const liquidish = new LiquidishTransformer({
  //...
  showComments: true,
});

Render

When using {% render './path/to/sub-template.liquid' %} the sub-template will be compiled to the final output. This is useful for reusing components.

You must provide the path starting with ./ or ../ so it can be adjusted to the correct path when compiled to its final location.

{% render './components/button.liquid' %}

The .liquid extension is optional and will be added automatically if the specified path without it does not exist.

To pass parameters to the sub-template, you can use following syntax:

{% render './components/button', parameter: 'My cool button text', another_parameter: 'another_value' %}

[!NOTE] The provided parameters will be known at compile-time and will be replaced with their value in the sub-template:

<!-- ./components/button.liquid -->
<button class="px-4 py-2">{{ parameter }}</button>

Will be compiled to:

<button class="px-4 py-2">My cool button text</button>

In order to pass complex JSON objects/arrays to a component you can use:

{% render 'components/heading', {
    "slot": "{{ logout_txt }} {{ cpuser }}",
    "attributes": [
        ["id", "logout-button"],
        ["data-load-content", "login/logout.php"]
    ]
} %}

The JSON must be a valid JSON object. This means that you can only use double quotes for strings and not single quotes.

For

{% for ... in ... %} compiles to output at compile-time using known variables. It does not support iterating over unknown variables at runtime.

Provide it with a variable that is known at compile-time, and it will loop over it:

{% for item in items %}
    {{ item }}
{% endfor %}

This can be useful when you want to loop over a bunch of items that are known at compile-time, e.g: for attributes in a button component:

<!-- ./components/button.liquid -->
<button class="px-4 py-2"
        {% for attribute in attributes %}
        {{ attribute[0] }}="{{ attribute[1] }}"
        {% endfor %}>
        {{ slot }}
</button>

The attributes would be provided like this:

{% render './components/button', {
    "slot": "Click me",
    "attributes": [
        ["id", "click-me"],
        ["data-load-content", "click.php"]
    ]
} %}

This will be compiled to:

<button class="px-4 py-2" id="click-me" data-load-content="click.php">Click me</button>

Metadata

You can provide metadata to the transformer by using the meta tag. This can be used to ignore component files, who only work when called with render and provided their parameters.

Additionally you can provide default parameters for the component, which will be used when the component is called without parameters.

{% meta {
  "isChildOnly": true,
  "defaults": {
    "parameter": "value"
  }
} %}

The data provided must be a valid JSON object. The meta tag must be the first element in the file.

The isChildOnly key can be used for sub-templates. When the transformer runs into a file with isChildOnly set to true, it will not compile it to a separate file. Instead, it can only be included in the parent file using the render tag.

Whitespace control

Just like Liquid, Liquidish outputs empty lines whenever you use a tag:

{% if value_that_is_true %}
    {{ my_variable }}
{% endif %} !

This will output:


    Contents of my variable
 !

To trim whitespaces you can use the {%- and -%} for logic tags and {{- and -}} for variable tags:

{%- if value_that_is_true %}
    {{- my_variable }}
{%- endif -%} !

This will output:

Contents of my variable!

Other syntax

Transformations can be added to Liquidish by using transformation strategies like those provided in the 🗺 Transformation Strategies section.

You can also create a 🧩 Custom Transformation Strategy.

🗺 Transformation Strategies

Liquidish accepts a strategy that defines how the Liquidish syntax is transformed to the target language.

🖥 ISPConfigTransformationStrategy

This strategy transforms Liquidish to ISPConfig's tpl syntax.

To use it, you instantiate a new LiquidishTransformer like this:

import { ISPConfigTransformationStrategy } from 'liquidish/strategies/ispconfig-transformation-strategy';
import { LiquidishTransformer } from 'liquidish';

const liquidish = new LiquidishTransformer({
    strategyBuilder: (transformer) => new ISPConfigTransformationStrategy(transformer)
});

It compiles Liquidish syntax to ISPConfig's tpl syntax in the following ways:

| Liquidish Syntax | ISPConfig .tpl Syntax | strategyMethodName | Notes | |---|---|---|---| | {{ VARIABLE }} | {tmpl_var name="VARIABLE"} | variable | If the variable is known at compile-time, it will be replaced with its value. For example when using render or for loops | | {% comment %} ... {% endcomment %} | <!-- ... --> | comment | Outputs nothing if showComments is set to false on the transformer | | {% render './template' %} | Replaced with the contents of the ./template.liquid file | render | The .liquid extension does not have to be provided | | {% render './template', parameter1: 'value', parameter2: '{{ cool }}' %} | Replaced with the contents of the ./template.liquid file | render | The provided parameters are used to replace the variables in the sub-template at compile-time | | {% render './template', { "parameter1": "{{ logout_txt }}", "parameter2": ["arrays", "are", "supported"] } %} | Replaced with the contents of the ./template.liquid file | render | You can provide JSON of which the keys are passed as parameters to the sub-template | | {% for item in items %}{{ item }}{% endfor %} | Replaced with item, repeated for each item in the collection at compile time | for | | | {% if VARIABLE %} | {tmpl_if VARIABLE} | if | | | {% if VARIABLE == 'VALUE' %} | {tmpl_if name="VARIABLE" op="==" value="VALUE"} | if | The == operator can be replaced with !=, >, <, >=, <= | | {% elsif VARIABLE %} | {tmpl_elseif VARIABLE} | elsif | | | {% elsif VARIABLE == 'VALUE' %} | {tmpl_elseif name="VARIABLE" op="==" value="VALUE"} | elsif | | | {% else %} | {tmpl_else} | else | | | {% endif %} | {/tmpl_if} | endif | | | {% unless VARIABLE %} | {tmpl_unless VARIABLE} | unless | | | {% endunless %} | {/tmpl_unless} | endunless | |

In addition to the standard Liquidish syntax, it also includes:

Loops

Loops compile to ISPConfig's loops. For example, this Liquidish code:

{% loop items %}
    {{ item }}
{% endloop %}

Becomes this ISPConfig code:

{tmpl_loop name="items"}
    {{ item }}
{/tmpl_loop}

Unlike with for loops, the in keyword is not available to specify the iterable.

Dyninclude

Dyninclude are defined with {% dyninclude 'template-file' %} and compile to ISPConfig's dyninclude tag:

{tmpl_dyninclude name="template-file"}

Hooks

Hooks are defined with {% hook 'hookName' %} and compile to ISPConfig's hook tag:

{tmpl_hook name="hookName"}

🌍 PHPTransformationStrategy

The PHP transformation strategy is a simple example of how to transform Liquidish to PHP. It is included as an example.

It is used like this:

import { PHPTransformationStrategy } from 'liquidish/strategies/php-transformation-strategy';
import { LiquidishTransformer } from 'liquidish';

const liquidish = new LiquidishTransformer({
    strategyBuilder: (transformer) => new PHPTransformationStrategy(transformer)
});

It compiles Liquidish syntax to PHP in the following ways:

| Liquidish Syntax | PHP Syntax | strategyMethodName | Notes | |---|---|---|---| | {{ VARIABLE }} | <?php echo $VARIABLE; ?> | variable | If the variable is known at compile-time, it will be replaced with its value. For example when using render or for loops | | {% comment %} ... {% endcomment %} | <!-- ... --> | comment | Outputs PHP comments (<?php /* ... */ ?>) if showComments is set to false on the transformer | | {% render './template' %} | Replaced with the contents of the ./template.liquid file | render | The .liquid extension does not have to be provided | | {% render './template', parameter1: 'value', parameter2: '{{ cool }}' %} | Replaced with the contents of the ./template.liquid file | render | The provided parameters are used to replace the variables in the sub-template at compile-time | | {% render './template', { "parameter1": "{{ logout_txt }}", "parameter2": ["arrays", "are", "supported"] } %} | Replaced with the contents of the ./template.liquid file | render | You can provide JSON of which the keys are passed as parameters to the sub-template | | {% for item in items %}{{ item }}{% endfor %} | Replaced with item, repeated for each item in the collection at compile time | for | | | {% if VARIABLE %} | <?php if ($VARIABLE) : ?> | if | | | {% if VARIABLE == 'VALUE' %} | <?php if ($VARIABLE == 'VALUE') : ?> | if | The == operator can be replaced with !=, >, <, >=, <= | | {% elsif VARIABLE %} | <?php elseif ($VARIABLE) : ?> | elsif | | | {% elsif VARIABLE == 'VALUE' %} | <?php elseif ($VARIABLE == 'VALUE') : ?> | elsif | | | {% else %} | <?php else : ?> | else | | | {% endif %} | <?php endif; ?> | endif | | | {% unless VARIABLE %} | <?php if (!$VARIABLE) : ?> | unless | | | {% endunless %} | <?php endif; ?> | endunless | |

And it includes an additional include tag:

{% include './header.php' %}

This will be transformed to PHP's include statement.

<?php include './header.php'; ?>

🧩 Custom Transformation Strategy

You can create your own transformation strategy by extending the TransformationStrategy class.

See the tests and the existing strategies (php, ISPConfig) for examples.