Skip to content

Feature: add chained pipe filters support natively to @comark/binding #427

Description

@miguelrk

Description

While build-time pre-parsers like comark-knap use a separate engine to process knap syntax before comark parses the markdown, @comark/binding handles templating natively at the AST level during runtime.

With loops (#411) and conditionals (#415) already implemented natively in @comark/binding, the remaining key opportunity is bringing knap's chained pipe filters (e.g., {{ text | upper }}) directly into @comark/binding. Note that this does not conflict with the existing default value operator ||.

Motivation

Developers wanting to use knap-style templating currently rely on third-party pre-processors like comark-knap. While comark-knap compiles templates at build time into raw Markdown, running a separate pre-parser bypasses comark's AST lifecycle.

There is an opportunity to bring core data-transformation features natively into @comark/binding at the AST level while keeping structural rendering cleanly delegated to Comark components.

Comparison Matrix

Feature knap / comark-knap (Third-party Pre-parser) Native @comark/binding (First-party Runtime) Status in @comark/binding
Execution Context Pre-parser / String replace AST / Runtime evaluator ✅ Built-in
Loops ({% for %}) Supported Supported natively ✅ Implemented (#411)
Conditionals ({% if %}) Supported Supported natively ✅ Implemented (#415)
Chained Pipe Filters Supported ({{ value | upper }}) Not Supported 🎯 High Priority
Markdown Generators Supported ({{ data | table }}) Won't Fix / Out of Scope Delegated to Components
Inline Variable Assignment Supported ({% set x = 1 %}) Not Supported Low Priority

Feature Evaluation

  • Chained Pipe Filters (|) — High Priority: Add support for readable pipeline transformations (e.g., {{ title | upper | truncate:10 }}) via a native filters configuration option in @comark/binding. Pipe filters operate strictly on data values, transforming primitives/objects before they are rendered into attributes or text nodes.
  • Markdown Generators — Out of Scope: Generating complex structural AST nodes (like TableNode or ListNode) directly from inside inline text expressions conflates value evaluation with AST layout rendering. In Comark's design model, structural rendering belongs in dedicated components (e.g., <Table :data="data"/>), not inline template filters.
  • Inline Variable Assignment ({% set %}) — Low Priority: Unnecessary for AST-level runtime evaluation. Scope variables should be passed directly via context or handled via filters rather than introducing state mutability inside AST expressions.

Proposed solution

To support functional transformations cleanly within the AST lifecycle, @comark/binding will introduce a configurable Pipe Filter Pipeline (|).

Filters will execute purely as value-to-value transformations during expression evaluation, leaving AST structure and layout responsibilities to standard Comark components.

1. Configuration & Registration

Add a filters option to the plugin configuration, allowing developers to define global or contextual transformation functions:

import { comarkBinding } from '@comark/binding'

const bindingPlugin = comarkBinding({
  filters: {
    upper: (val: string) => val?.toUpperCase(),
    truncate: (val: string, length: number) => 
      val?.length > length ? `${val.slice(0, length)}...` : val,
    json: (val: unknown, indent = 2) => JSON.stringify(val, null, indent),
    date: (val: string | Date, formatStr: string) => formatDate(val, formatStr)
  }
})

2. Pipeline Expression Syntax

Update the expression parser to handle standard Unix-style pipe operators (|) with optional arguments:

<!-- Basic transformation -->
# {{ title | upper }}

<!-- Chained filters with arguments -->
<p>{{ article.summary | truncate:120 | upper }}</p>

<!-- Attribute binding support -->
<UserCard :created-at="user.createdAt | date:'YYYY-MM-DD'" :name="user.name | upper"/>

3. Execution Lifecycle

  1. AST Node Identification: The parser identifies interpolation nodes (text or attribute bindings).
  2. Value Evaluation: @comark/binding resolves the base identifier path against the execution context.
  3. Pipeline Sequential Pass: The output of the base expression is passed sequentially as the first parameter into each chained filter function, with explicit arguments passed subsequently.
  4. AST Rendering: The transformed scalar value or object is injected back into the AST node text or prop payload.

4. Handling Structural Layout (Generators Alternative)

Rather than embedding layout logic inside filters, complex structures continue to leverage comark’s native component bindings:

<!-- Preferred architectural pattern over {{ data | table }} -->
<Table :data="data | sort:'name'"/>

Additional context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions