liquify 1.5.0 copy "liquify: ^1.5.0" to clipboard
liquify: ^1.5.0 copied to clipboard

A powerful and extensible Liquid template engine for Dart. Supports full Liquid syntax, custom tags and filters, and high-performance parsing and rendering.

Liquify - Liquid Template Engine for Dart #

Liquify Logo

GitHub release Pub Version GitHub Actions Workflow Status License issues - liquify

Liquify is a comprehensive Dart implementation of the Liquid template language, originally created by Shopify. This high-performance library allows you to parse, render, and extend Liquid templates in your Dart and Flutter applications.

Features #

  • Full support for standard Liquid syntax and semantics
  • Synchronous and asynchronous rendering
  • Custom delimiters (ERB-style, bracket-style, or your own)
  • Environment-scoped filters and tags for security isolation
  • Strict mode for security sandboxing
  • Extensible architecture for custom tags and filters
  • Template inheritance with layouts and blocks
  • File system abstraction for template resolution
  • High-performance parsing with caching
  • Strong typing and null safety

Installation #

Add Liquify to your pubspec.yaml:

dependencies:
  liquify: ^1.5.0

Then run dart pub get or flutter pub get.

Quick Start #

Basic Template Rendering #

import 'package:liquify/liquify.dart';

void main() {
  final template = Template.parse(
    'Hello, {{ name | upcase }}!',
    data: {'name': 'world'},
  );
  
  print(template.render()); // Output: Hello, WORLD!
}

Using the Liquid Class #

The Liquid class provides a convenient API, especially when using custom delimiters:

final liquid = Liquid();
final result = liquid.renderString('Hello, {{ name }}!', {'name': 'World'});
print(result); // Output: Hello, World!

Async Rendering #

Use async rendering for templates with async filters or includes:

final result = await template.renderAsync();

Custom Delimiters #

Liquify supports custom delimiters for integrating with other template systems or avoiding conflicts with frontend frameworks.

ERB-Style Delimiters #

final liquid = Liquid(config: LiquidConfig.erb);
final result = liquid.renderString(
  '<% if show %>Hello, <%= name %>!<% endif %>',
  {'show': true, 'name': 'World'},
);
print(result); // Output: Hello, World!

Custom Delimiters #

final liquid = Liquid.withDelimiters(
  tagStart: '[%',
  tagEnd: '%]',
  varStart: '[[',
  varEnd: ']]',
);

final result = liquid.renderString(
  '[% for item in items %][[ item ]] [% endfor %]',
  {'items': ['a', 'b', 'c']},
);
print(result); // Output: a b c

Whitespace Control #

Whitespace stripping works with custom delimiters using the - marker:

final liquid = Liquid.withDelimiters(tagStart: '[%', tagEnd: '%]', varStart: '[[', varEnd: ']]');

final result = liquid.renderString('Hello    [[- name ]]!', {'name': 'World'});
print(result); // Output: HelloWorld!

Template Inheritance #

Liquify supports template inheritance through the layout tag:

<!-- layouts/base.liquid -->
<!DOCTYPE html>
<html>
<head><title>{% block title %}Default{% endblock %}</title></head>
<body>{% block content %}{% endblock %}</body>
</html>
<!-- page.liquid -->
{% layout "layouts/base.liquid" %}
{% block title %}My Page{% endblock %}
{% block content %}<h1>Welcome!</h1>{% endblock %}

Variables can be passed to layouts:

{% layout "layouts/base.liquid", title: page_title, year: 2024 %}

File System and Template Resolution #

In-Memory Templates with MapRoot #

final fs = MapRoot({
  'main.liquid': 'Hello {% render "partial.liquid" %}',
  'partial.liquid': 'World!',
});

final template = Template.fromFile('main.liquid', fs);
print(template.render()); // Output: Hello World!

Custom Template Resolution #

Implement the Root class for custom template sources:

class DatabaseRoot extends Root {
  @override
  Future<String?> resolveAsync(String path) async {
    return await database.getTemplate(path);
  }
}

Security and Isolation #

Environment-Scoped Filters #

Register filters that are isolated to a specific template:

final template = Template.parse(
  '{{ input | sanitize }}',
  data: {'input': '<script>alert("xss")</script>'},
  environmentSetup: (env) {
    env.registerLocalFilter('sanitize', (value, args, namedArgs) => 
      value.toString().replaceAll(RegExp(r'<[^>]*>'), ''));
  },
);

print(template.render()); // Output: alert("xss")

Strict Mode #

Block access to global filters and tags for untrusted content:

final secureEnv = Environment.withStrictMode();
secureEnv.registerLocalFilter('safe_filter', myFilter);

final template = Template.parse(source, environment: secureEnv);

Environment Cloning #

Create child environments that inherit from a parent:

final baseEnv = Environment();
baseEnv.registerLocalFilter('base', baseFilter);

final childEnv = baseEnv.clone();
childEnv.registerLocalFilter('child', childFilter);
// childEnv has access to both 'base' and 'child' filters

Custom Tags and Filters #

Custom Filter #

FilterRegistry.register('reverse_words', (value, args, namedArgs) {
  return value.toString().split(' ').reversed.join(' ');
});

// Usage: {{ "hello world" | reverse_words }} -> "world hello"

Custom Tag #

class ShoutTag extends AbstractTag with CustomTagParser {
  ShoutTag(super.content, super.filters);

  @override
  dynamic evaluateWithContext(Evaluator evaluator, Buffer buffer) {
    final text = evaluator.evaluate(content.first)?.toString() ?? '';
    buffer.write(text.toUpperCase());
  }

  @override
  Parser parser([LiquidConfig? config]) {
    return (createTagStart(config) &
            string('shout').trim() &
            ref0(expression).trim() &
            createTagEnd(config))
        .map((values) => Tag('shout', [values[2] as ASTNode]));
  }
}

TagRegistry.register('shout', (content, filters) => ShoutTag(content, filters));

// Usage: {% shout "hello" %} -> HELLO

For more examples, see the example directory.

API Documentation #

Full API documentation is available at pub.dev.

Contributing #

Contributions are welcome! Please open an issue first for major changes.

License #

This project is licensed under the MIT License.

Acknowledgements #

  • Shopify for the original Liquid template language
  • LiquidJS for their comprehensive filter implementations
  • liquid_dart for initial Dart implementation inspiration
19
likes
160
points
8.37k
downloads

Publisher

verified publisherglenfordwilliams.com

Weekly Downloads

A powerful and extensible Liquid template engine for Dart. Supports full Liquid syntax, custom tags and filters, and high-performance parsing and rendering.

Repository (GitHub)
View/report issues

Topics

#liquid #templating #teamplate-engine #dart

Documentation

API reference

Funding

Consider supporting this project:

www.buymeacoffee.com

License

MIT (license)

Dependencies

args, file, gato, html, html_unescape, intl, logging, petitparser, timezone

More

Packages that depend on liquify