I'll start this as a question, as that's where it started on Twitter. _(If there's a bug hidden here as well, a new issue could be created.)_
https://twitter.com/jouni_kantola/status/1238619820494663680
How do I in an _idiomatic way_ inline CSS from PostCSS, when using Liquid and async PostCSS plugins?
Template shortcode worked:
https://gist.github.com/jouni-kantola/481d87634f9455ead0397d3f01663036
I tried, but failed, using filters. All I got was an empty object literal. Changing the code in the gist to use filters resulted in:
<style>{}</style>
Maybe I'm missing something (I had a very basic CSS test file), but this seems to work for me in https://github.com/pdehaan/11ty-postcss sandbox:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<style>*{font-family:Comic Sans MS}body{background-color:red}table{border:2px solid salmon}</style>
<title>Home</title>
</head>
In my .eleventy.js config file, I converted to a paired shortcode:
eleventyConfig.addPairedShortcode("postcss", require("./utils/transform-css"));
So instead of using {% capture %}{% include %}{% endcapture %}, I can just include the CSS directly in my shortcode:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<style>
{%- postcss -%}
{% include entry.css %}
{%- endpostcss -%}
</style>
<title>{{ title }}</title>
</head>
But, like I said, I had a very basic CSS file, so maybe I'm missing some workflow issues (although it does prove that at least it's piping the content through cssnano since the CSS was minified):
* {
font-family: "Comic Sans MS";
}
body {
background-color: red;
}
table {
border: 2px solid salmon;
}
As mentioned in the initial post, shortcode works, but filter does not. I'd like to know if shortcode is the idiomatic/intended way to inline CSS from an async PostCSS flow, or if there's a better way of going about.
Thank you for the setting up the sandbox repo, @pdehaan.
PR https://github.com/pdehaan/11ty-postcss/pull/1 is to provide an example where filters are used instead (which results in {} as styles). Your solution in the sandbox repo is elegant, so inlining itself isn't the issue. I'd like to discuss which way would be the expected way to get it to work.
@jouni-kantola Sorry, this was getting way too complex for me, so I went a simpler route:
eleventyConfig.addFilter("toUPPER", async function (value) {
const upperValue = value.toString().toUpperCase();
return await upperValue;
});
It's a completely pointless filter, but it allowed me to ignore postcss entirely and made the logic simpler.
Note that like your postcss example, I'm trying to stay consistent with an _async_ filter (which I believe is the core issue here).
In my index.njk file, I have this:
>> {{ "PeTeR" | toUPPER }}
And similarly, I dumped that code in the big src/_includes/layouts/layout.liquid layout file:
>> {{ "PeTeR" | toUPPER }}
Nunjucks returns >> [object Promise] whereas Liquid returns >> {} (which was consistent with your findings). If I remove the async/await from my custom toUPPER filter, it all works as expected.
So not sure if there is a way to do a non-promised based version of postcss using the API, or some other way of doing async filters in Liquid/Nunjucks. I thought Nunjucks had something, but I'll poke around a bit more and see if I can get it to work in Nunjucks before looking into the Liquid engine in 11ty to see how async filters might (or don't) work.
Deeper into the abyss: https://www.11ty.dev/docs/languages/nunjucks/#asynchronous-nunjucks-filters
Nunjucks/Eleventy supports async filters (for Nunjucks), but it uses the _callback_ pattern, so when I tried using an actual async function it stops building pages. That took me a few minutes to sort until I re-re-read the docs to see my usage was wrong.
Even if I try resolving a promise it gives me [object Promise] results. Still digging around to see how I could convert a returned promise from postcss into callback pattern and see if it resolves correctly, but even if I do sort it, we'd need to see if we can get it to work in Liquid and not just using eleventyConfig.addNunjucksAsyncFilter(...).
Smart move to simplify and use an async universal filter. That leads us to this issue, which even mentions PostCSS: https://github.com/11ty/eleventy/issues/831
In that case I'd say the answer to the question of this issue is that PostCSS should be registered as a shortcode.
Should I close this issue?
OK, got it working in Nunjucks using an async filter and callback.
const path = require("path");
const postcss = require("postcss");
module.exports = function (code, callback) {
const rawFilepath = path.join(__dirname, "../src/_includes/entry.css");
postcss([
require("precss"),
require("postcss-import"),
require("postcss-custom-selectors"),
require("autoprefixer"),
require("cssnano")
])
.process(code, { from: rawFilepath })
.then(result => callback(null, result.css));
};
And in my .eleventy.js config file:
eleventyConfig.addNunjucksAsyncFilter("postcss", require("./utils/transform-css"));
But that also means I had to convert from Liquid to Nunjucks templates in order to use the async filter, so I kind of broke your question.
<style>
{% set css %}{% include "entry.css" %}{% endset %}
{{ css | postcss }}
</style>
We might need to summon @zachleat and ask if async filters are possible w/ Liquid and we're missing something.
Personally, I'd stick with the shortcode approach since it works. But 🤷♂
As the issue about Liquid filters already exists, then I think we can summarize with that shortcodes does the job. I guess we ended where we started :-)
Thanks @pdehaan for also trying out the filter route. 👍
I guess we ended where we started :-)
Sure, it might be where we started, but we're now both a lot smarter than we were in our youth (~36 hours ago).
Hi @jouni-kantola @pdehaan,
I also learned from your discussion, thanks! 👍
I have a question: if you're doing this (either with a shortcode or a filter), does it mean the CSS is processed by postcss for every single generated page (I have around 1000), or is it done only once for all pages?
@nhoizey In this example (which uses filename instead of content), page specific data is used: https://gist.github.com/jouni-kantola/481d87634f9455ead0397d3f01663036
Given that works, I'd say you build it for every file.
If you want to build once and inline that dist, I'd rather go with a npm script and run that before hooking up the eleventy build.
@nhoizey Try running with shortcode and see how long it takes. Maybe you can build a shortcode that caches the result.
I finally managed to build my CSS with postcss-cli and a few plugins outside Eleventy's build, so just once!
I build two CSS files:
I finally managed to build my CSS with
postcss-cliand a few plugins outside Eleventy's build, so just once!I build two CSS files:
- one for critical styles to be included in HTML
- one for additional styles to load (a little) later
@nhoizey How did you do this? I am struggling for this as well. Creating a postcss build is easy with npm script, but dev is not. Is there a way I use both postcss(watch css files and recompile for changes )and eleventy to watch files together?
Well… I finally (again 😅) switched to Rollup for my assets builds, as I started explaining here: https://pack11ty.dev/documentation/assets/
@nholzey Odd timing. Was just last night reconsidering whether I should’ve left webpack behind, although decided for now to stick with my current non-bundler setup. 🤷
@brycewray I was using Rollup to build my Service Worker with Workbox, so I decided it was interesting using it also for other JS and Sass/CSS.
I had isssues with hashing though, I had to make it myself for CSS: https://github.com/nhoizey/pack11ty/blob/master/rollup.config.js#L23-L49
Most helpful comment
Sure, it might be where we started, but we're now both a lot smarter than we were in our youth (~36 hours ago).