New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Running postcss inside a Once
visitor can cause other plugins to stop working.
#1712
Comments
I will look deeply tomorrow. The general policy is that But I will try to find a better solution in the next few days. |
Maybe to step back to our bigger picture problem in case it's helpful, the main thing we need to solve is we need to make sure that Tailwind itself never has to work with nested CSS. We need anyone using nesting to flatten their CSS before Tailwind sees it, because Tailwind just doesn't understand the semantics of nested CSS, and can't realistically be expected to in my opinion (since it's not valid CSS, and every nesting plugin could transform the nested structure in different ways). So our biggest problem here is just how do we guarantee that if someone is using nesting, that the CSS is flattened before the tree hits Tailwind. |
The simplest idea with current API is to use For instance, you can ask to use |
Hm. Maybe (as a quick fix without API breaking changes) we can use unique You can use Will it solve the problem for you? |
Ah I didn't notice Given that |
Yeap, it is “private” API. Can you try to call it to check it will help? I will think about the best implementation (I am not sure that we can't avoid a single mark, but we can always call |
Just checked. A lone call to However, this works since it cleans the entire tree (though it's a bit inefficient since it walks back up the ancestor tree for every single node): root.markDirty()
root.walk(node => node.markDirty()) |
Very strange. PostCSS already has a code to clean How do you call PostCSS? Maybe you use another API which do not clean marks? |
It has to do with the order plugins run in and when marks are cleared. They are removed when So, if you were to do this: postcss(plugins).process(root).sync()
postcss(otherPlugins).process(root).sync() This order of calls looks like this:
This works as expected because a However, if you run So, if you were to do this: postcss([
{
Once(root, { result }) {
postcss(otherPlugins).process(root, result.opts).sync()
}
},
plugin2,
plugin3,
]).process(root).sync() The order of operations looks like this, which exhibits the weird behavior:
|
Oh, What do you think if we will stick with 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. |
Hi, 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 thanOnce
/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 forRule
,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:
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 examplecss-has-pseudo
registers aRule
visitor) because once the node is clean it doesn't have to be processed any further. Which means thatcss-has-pseudo
'sRule
visitor never runs.The question
I discovered we can work around this by importing
LazyResult
from thepostcss/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
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!
The text was updated successfully, but these errors were encountered: