Running postcss inside a `Once` visitor can cause other plugins to stop working.
See original GitHub issueHi, I’m looking into an issue where the tailwindcss/nesting
plugin breaks other PostCSS plugins and would like some information on what the expected behavior is and what we should be doing / can do to fix this issue.
Summary of the problem
Our nesting plugin wraps existing an nesting plugin and runs it synchronously to completion so Tailwind doesn’t have to deal with nested CSS. However, running postcss inside of a postcss Once
visitor breaks visitors for plugins that use anything other than Once
/ OnceExit
.
Why the plugin exists
Tailwind can’t reliably handle nested CSS internally. As such, we rely on users using a nesting plugin to prevent this from being an issue. However, since Tailwind is effectively a Once
plugin (though it doesn’t use the visitor API), the visitors for Rule
, AtRule
, etc… are all run after Tailwind runs — even for plugins that are listed before Tailwind in the postcss plugin list. Because of this we register a plugin that wraps a nesting plugin and runs it synchronously to completion before anything else (like Tailwind) sees them.
Our implementation
First, we perform a few transformations on the tree to ensure that nesting plugins handle hoisting @screen
and @apply
appropriately (e.g. @screen
is the same as a media query so should be hoisted but @apply
produces a list of declarations so it shouldn’t be).
Next, we run the plugin using the following:
postcss([nestingPlugin]).process(root, result.opts).sync()
Lastly, we do some final cleanup on the @apply
fixups so Tailwind can look for @apply
like it usually does.
The problem (in some more detail)
Running postcss().process().sync()
on a root will mark every node in it as “clean” once it’s done. This is an internal marker that postcss uses to determine if a node has been processed. If a node is marked as “clean” then no further transformations on it are run on those nodes. However, doing this breaks plugins that use the visitor API (for example css-has-pseudo
registers a Rule
visitor) because once the node is clean it doesn’t have to be processed any further. Which means that css-has-pseudo
’s Rule
visitor never runs.
The question
I discovered we can work around this by importing LazyResult
from the postcss/lib/lazy-result
and instantiating it with the root node. However, this feels very hacky and I worry that referencing internals and using the cleaning side effect could break at any time. What would be the best solution to this problem? Is there something we should be doing differently?
You can see the core implementation of our plugin here: https://github.com/tailwindlabs/tailwindcss/blob/50802e1aed1a8ed9b0eead1d45722793a86d3069/src/postcss-plugins/nesting/plugin.js#L5
Reproduction
This is a minimal reproduction that doesn’t use our plugin but still runs postcss in the exact same manner which shows the issue. There are only three plugins involved and all are inlined the the postcss config. I can also prep one using the actual nesting plugins themselves if necessary.
Link: https://github.com/thecrypticace/postcss-visitor-question
npm install
npm run dev
- Look at the output in the CLI versus the console.log calls in the postcss config file.
I’m more than happy to provide further details, reproductions, etc… as needed. I appreciate you taking your time to look at and think through this!
Issue Analytics
- State:
- Created 2 years ago
- Comments:10 (10 by maintainers)
I will look deeply tomorrow.
The general policy is that
Once
is some sort or legacy-hack. This plugin-by-plugin sequence always create a problem with plugin order conflict. We added visitor events (Rule
,Declaration
, etc) to solve the problem with plugin order. This is whyOnce
-way plugins is treated as legacy where the problem can’t be solved. And the only good way is to migrate to full event system.But I will try to find a better solution in the next few days.
Oh,
process()
insideOnce
. Got it.What do you think if we will stick with
markDirty
? It is a very unexpected (and I hope rare) way to use PostCSS API.We will not add auto-dirty to PostCSS for this case since it is hard. It is better spend time of rewriting Tailwind to PostCSS 8 event system. If you have any question about migration, I can answer quick in Telegram or by email.