lumeland / lume

πŸ”₯ Static site generator for Deno πŸ¦•
https://lume.land
MIT License
1.79k stars 77 forks source link

Allowing filters to use dynamic variables in Pug #320

Closed pluiedev closed 1 year ago

pluiedev commented 1 year ago

Enter your suggestions in details:

Pug has a filter system that is currently hooked up to Lume's filters, that other template engines like Nunjucks also use. However, Pug limits filter inputs to plain text only, which is incredibly inconvenient for Lume users.

For example, the date filter added by the Date plugin can be used in Pug like this:

:date(pattern="HUMAN_DATETIME") 2022-11-22 7:00:30Z

However, this does not work with Pug variables (so something like :date()= variable does not work), and that is by design:

Warning Filters are rendered at compile time. This makes them fast, but it also means that they cannot support dynamic content or options.

By default, compilation in the browser does not have access to JSTransformer-based filters, unless the JSTransformer modules are explicitly packed and made available through a CommonJS platform (such as Browserify or Webpack). In fact, the page you are reading right now uses Browserify to make the filters available in the browser.

Templates pre-compiled on the server do not have this limitation.

As Lume precompiles templates as an SSG, I think we should lift this limitation and bring Pug in line with other template engines like Nunjucks.

oscarotero commented 1 year ago

Okay, I have to investigate the Pug API. I'm already precompiling the templates see the code here, so it should work for dynamic variables.

Any help here is very appreciated :)

oscarotero commented 1 year ago

@pluiedev I've tried to implement this but without success. Do you know how to run Pug in server side to allow to use dynamic variables?

pluiedev commented 1 year ago

@pluiedev I've tried to implement this but without success. Do you know how to run Pug in server side to allow to use dynamic variables?

I don't either, sadly β€” it looks like Pug is kinda poorly documented in that regard. We would have to ask the Pug maintainers I guess? No clue.

oscarotero commented 1 year ago

If you want to try, that's fine. But the last commit to the repo was almost 2 years ago and the maintainers don't reply the issues, so it's seems the project is abandoned.

pluiedev commented 1 year ago

If you want to try, that's fine. But the last commit to the repo was almost 2 years ago and the maintainers don't reply the issues, so it's seems the project is abandoned.

Mhm yeah, which is a shame β€” I really like the terseness that Pug provides and I don't want to give it up just yet. I'll try to look into it myself then :eyes:

pluiedev commented 1 year ago

Okay so I dug into it a bit, turns out the Pug compiler compiles Pug source code down to a template function which returns a manually concatenated HTML string given certain variables. By "manually concatenated" I mean something like this (all debugging machinery removed):

function template(locals) {
  var pug_html = "",
    pug_mixins = {},
    pug_interp;
  var pug_debug_filename, pug_debug_line;
  try {
    var locals_for_with = locals || {};

    (function (date) {
      pug_html = pug_html + "\u003C!DOCTYPE html \u003E";
      pug_html = pug_html + "\u003Chtml\u003E";
      pug_html = pug_html + " ";
      pug_html = pug_html + "\u003Cbody\u003E";
      pug_html = pug_html + " ";
      pug_html = pug_html + "\u003Ch1\u003E";
      pug_html =
        pug_html +
        pug.escape(null == (pug_interp = date) ? "" : pug_interp) +
        "\u003C\u002Fh1\u003E\u003C\u002Fbody\u003E\u003C\u002Fhtml\u003E";
    }.call(
      this,
      "date" in locals_for_with
        ? locals_for_with.date
        : typeof date !== "undefined"
        ? date
        : undefined
    ));
  } catch (err) {
    pug.rethrow(err, pug_debug_filename, pug_debug_line);
  }
  return pug_html;
}

Based on how it works, I think there is a way for us to make dynamic filters β€” since filters are just JS functions, we can introduce them into the template function which can call them on the fly. I'll look for a solution that's built into Pug but at least there is a potential way to do it

oscarotero commented 1 year ago

Sound good!

pluiedev commented 1 year ago

Okay so I did some more digging, and I think this is impossible without substantially modifying Pug, or using filters in another manner.

Basically, Pug evaluates filters before linking and code gen β€” there's no way to externally bypass filter evaluation, and there's no code in code gen (as far as I can tell) that supports filters, since pug-code-gen actually throws an error if it manages to find an unprocessed filter in the AST. Tl;dr if we were to support dynamic filters, we have to substantially alter the Pug compiler, which is admittedly too complicated for me.

However, I think we can just avoid using the builtin filter feature, and directly use interpolated, (un-)buffered code. Instead of:

:date(pattern="HUMAN_DATETIME")= date

We can write

= filters.date(date, "HUMAN_DATETIME")

Which IMO actually works better, but we'd have to think about what to do with filters like md, which works great with just plain text.

oscarotero commented 1 year ago

Nice! Is it possible to have both? For example filters.md(value) and :md?

pluiedev commented 1 year ago

Nice! Is it possible to have both? For example filters.md(value) and :md?

It's possible yeah, you just have to inject filters into the page data before rendering, which makes them available to the template as a global variable. Gonna open a PR that does exactly that very soon πŸ‘