question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Running postcss inside a `Once` visitor can cause other plugins to stop working.

See original GitHub issue

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 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

  1. npm install
  2. npm run dev
  3. 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:closed
  • Created 2 years ago
  • Comments:10 (10 by maintainers)

github_iconTop GitHub Comments

1reaction
aicommented, Feb 2, 2022

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 why Once-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.

0reactions
aicommented, Feb 24, 2022

Oh, process() inside Once. 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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Migrate to PostCSS 8 · Issue #4942 - GitHub
Migrate to built-in rules to visitor API to remove deprecation warnings ... It will not work anymore, because now PostCSS plugin returns an...
Read more >
css - Using grunt-postcss - how do we run autoprefixer without ...
I have a default grunt build that will work to build everything for deployment. It includes the call to postcss, like so: grunt.registerTask(' ......
Read more >
So you want to make a PostCSS plugin - CSS-Tricks
Let's write a really simple PostCSS plugin. We can call it PostCSS-backwards. PostCSS-backwards will let you reverse CSS declaration values ...
Read more >
Using with Preprocessors - Tailwind CSS
A guide to using Tailwind with common CSS preprocessors like Sass, Less, and Stylus. Since Tailwind is a PostCSS plugin, there's nothing stopping...
Read more >
PostCSS – Sass Killer or Preprocessing Pretender?
The only trickery can come when specifying the order that your PostCSS plugins run in. Some plugins are required to run before others...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found