Eleventy: Add pug support for Universal filters

Created on 26 Jun 2018  Â·  9Comments  Â·  Source: 11ty/eleventy

Hello,

I've started using 11ty recently and so far I'm impressed by how everything "just works".

Still, my first road block is adding CSS files to the website, and putting their inclusion path right in the layout. If I read the doc correctly, I need to use the url filter, but I don't think this is available for .pug files.

Can you confirm that's the case, and maybe provide another solution? Maybe just manually adding pathPrefix in my layout?

Thanks a lot

enhancement needs-votes

Most helpful comment

So... I spent some more time on this and I'm hitting a wall here.

As you pointed, what Pug calls filters are not filters in the sense of pipeable operations on variables like Liquid has for example. Filters in pug are used to _compile_/_transpile_ languages. Like converting CoffeeScript to JavaScript, syntax highlighting a piece of code or minifying CSS. One can use them for more trivial conversions (like adding a pathPrefix if needed), but in the end they are used to output __markup__ and not __variables__. It means, that it is not possible to use them in HTML attribute, they have to be used at the HTML tag level...

Another similar mechanism offered by pug are mixins. The syntax is slightly different but they also are required to be used to output HTML markup and can't be used in attributes either.

The last mechanism provided by Pug is simply to declare a JavaScript function inside a pug template and use it immediatly

- function url(input) { return `/${input}.html` }
a(href=url('foo')) Content
<a href="/foo.html">Content</a>

I tried to hack into the Pug.js engine file to inline the url and slug method before each pug content but as I'm inlining them as strings, I can't pass the methods easily (and it would have been a dirty hack anyway).

Pug being fully in JS, I wouldn't be surprised if there is a way to directly pass methods to be available in the global scope, but I could not find any mention of that in the doc. I found that it was possible to pass custom data (as json) using the CLI, so maybe it's possible to pass more but I haven't found how yet.

This proved more complex than I expected this to be, so I'm going to leave it there for now, until I find a way to pass custom methods to pug.

All 9 comments

Correct: Universal filters do not yet support pug.

https://pugjs.org/language/filters.html#custom-filters

I think this would be a small task to add.

Any way I could help with that? Happy to submit a PR if you could point me toward examples for other templating languages.

Here are the exact steps that you’d need to do 😅

One concern that I do have before we move forward there is that the filters documentation actually kinda looks like tags, not filters. This is something that you might have more insight on than me, since you likely have more Pug experience?

Typically tags in other template langs look like {% myTag myArg %} or a paired version {% myTag myArg %}myContent{% endmyTag %}. You’re calling a function and passing in args and content. Whereas filters operate on and augment existing content {% myArg | myFilter %}.

The weird part about reading these docs is that Pug filters seem more like Tags? Or maybe they just kind of operate like both?

Either way, I think that discussion will probably culminate in whether or not we would add Pug filters to the #13 universal shortcode implementation too and probably won’t affect this immediate filter work.

Instructions

Add method to add a single Pug filter to the config (this example goes through how it’s done for Liquid):

Add to the actual template engine in Pug.js (this is pointing to Liquid.js):

Looks like the filters object will be added to getPugOptions here:

Add a test to TemplateRenderPugTest.js, here’s the Liquid example: https://github.com/11ty/eleventy/blob/master/test/TemplateRenderLiquidTest.js#L58

You have a fair point with the filter/tag difference. I don't have that much experience with pug (I actually never used filters, always used mixins in the past).

After reading the code you pointed my understanding is that eleventy already allows user to pass their own custom pug config to the pug engine. And that pug filters are defined through the options.filters key, that will be read by eleventy.

My conclusion is that if I want to implement my own url filter for pug files, I can just do it with the existing code, by using the pug engine options. It won't have the cleaner eleventyConfig.addFilter or eleventyConfig.addPugFilter API, but will still work. Is that correct?

What I'm still a bit unsure about is if I can read the eleventy config (to get access to the prefixPath) from my own custom pug filter, or would I need to have it wrapped as a _real_ eleventy filter to have it?

Great observations!!

After reading the code you pointed my understanding is that eleventy already allows user to pass their own custom pug config to the pug engine. And that pug filters are defined through the options.filters key, that will be read by eleventy.
My conclusion is that if I want to implement my own url filter for pug files, I can just do it with the existing code, by using the pug engine options. It won't have the cleaner eleventyConfig.addFilter or eleventyConfig.addPugFilter API, but will still work. Is that correct?

Yes, I believe that to be true too!

The main benefit to supplementing the eleventyConfig.addFilter method with pug support is that all of the built-in universal filters would automatically be available in pug, automatically. So, you wouldn’t need to write your own url filter?

If you’re stuck on writing your own url filter for other reasons, there’s not much reason to use pathPrefix at all is there? This is an eleventy config for the built-in url filter. If you’re not using the built-in url filter, then pathPrefix doesn’t buy you much ¯_(ツ)_/¯

I suppose if you’re mixing template engines in a project—but if you’re using the built-in url filter in some places and not others—the universal filter support would be greatly preferred.

Lemme know if you want to PR Pug filter support—if not, I can do it—NBD!

So... I spent some more time on this and I'm hitting a wall here.

As you pointed, what Pug calls filters are not filters in the sense of pipeable operations on variables like Liquid has for example. Filters in pug are used to _compile_/_transpile_ languages. Like converting CoffeeScript to JavaScript, syntax highlighting a piece of code or minifying CSS. One can use them for more trivial conversions (like adding a pathPrefix if needed), but in the end they are used to output __markup__ and not __variables__. It means, that it is not possible to use them in HTML attribute, they have to be used at the HTML tag level...

Another similar mechanism offered by pug are mixins. The syntax is slightly different but they also are required to be used to output HTML markup and can't be used in attributes either.

The last mechanism provided by Pug is simply to declare a JavaScript function inside a pug template and use it immediatly

- function url(input) { return `/${input}.html` }
a(href=url('foo')) Content
<a href="/foo.html">Content</a>

I tried to hack into the Pug.js engine file to inline the url and slug method before each pug content but as I'm inlining them as strings, I can't pass the methods easily (and it would have been a dirty hack anyway).

Pug being fully in JS, I wouldn't be surprised if there is a way to directly pass methods to be available in the global scope, but I could not find any mention of that in the doc. I found that it was possible to pass custom data (as json) using the CLI, so maybe it's possible to pass more but I haven't found how yet.

This proved more complex than I expected this to be, so I'm going to leave it there for now, until I find a way to pass custom methods to pug.

Our messages crossed :)

For the record, I managed to have the universal url filter working as a pug filter. But this is no use as this cannot be used inside of an HTML attribute. In other words:

// This does not work, filters cannot be used in attributes
link(type="text/css", rel="stylesheet", href={url() /style.css})
// This works (<div>/{pathPrefix}/style.css</div>) but is not very useful :)
div
  :url() /style.css

I don't even really need the url filter TBH, I thought I would when I submitted the issue initially, then found another way around. I still wanted to see if I could contribute, but I now think this might not be the easiest issue to start grasping the internals of eleventy :)

@pixelastic oh goodness, wow. That no-attributes limitation is a hefty one. Hmm, let’s sit on this for a bit. I really appreciate you looking at it!

This does kind of highlight the need to maybe expose these built-in universal filters as plugins that Eleventy consumes. That would make them at least require-able if projects needed an escape hatch.

This repository is now using lodash style issue management for enhancements. This means enhancement issues will now be closed instead of leaving them open.

The enhancement backlog can be found here: https://github.com/11ty/eleventy/issues?utf8=%E2%9C%93&q=label%3Aneeds-votes+sort%3Areactions-%2B1-desc+

Was this page helpful?
0 / 5 - 0 ratings

Related issues

michrome picture michrome  Â·  3Comments

jamrelian picture jamrelian  Â·  3Comments

veleek picture veleek  Â·  3Comments

smaimon picture smaimon  Â·  3Comments

kaloja picture kaloja  Â·  3Comments