tailwindlabs / tailwindcss

A utility-first CSS framework for rapid UI development.
https://tailwindcss.com/
MIT License
80.21k stars 4.04k forks source link

PostCSS warning for custom utility used with before:/after: #13591

Open gynekolog opened 2 months ago

gynekolog commented 2 months ago

What version of Tailwind CSS are you using?

3.4.3

What build tool (or framework if it abstracts the build tool) are you using?

Vite

What version of Node.js are you using?

v21.7.3

What browser are you using?

Chrome

What operating system are you using?

macOS

Reproduction URL

https://stackblitz.com/edit/vitest-dev-vitest-cp1r2y

Describe your issue

There's a PostCSS warning in the console:

A PostCSS plugin did not pass the `from` option to `postcss.parse`. This may cause imported assets to be incorrectly transformed. If you've recently added a PostCSS plugin that raised this warning, please contact the package author to fix the issue.

It starts when you define your own CSS utility and use it with the before: or after:. Example:

css file:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
  .bg-own {
    background-color: red;
  }
}

Usage:

<div class="before:bg-own" />
wongjn commented 2 months ago

This could be related to the before variant core plugin (this would happen with after too), it adds a new Declaration without a source property:

https://github.com/tailwindlabs/tailwindcss/blob/f1f419a9ecfcd00a2001ee96ab252739fca47564/src/corePlugins.js#L64

The vite-url-rewrite PostCSS plugin internally in Vite relies on this source property:

const importer = declaration.source?.input.file
if (!importer) {
  opts.logger.warnOnce(
    '\nA PostCSS plugin did not pass the `from` option to `postcss.parse`. ' +

As per the 2.4 of the PostCSS Plugin Guidelines:

Every node must have a relevant source so PostCSS can generate an accurate source map.

So if you add a new declaration based on some existing declaration, you should clone the existing declaration in order to save that original source.

if (needPrefix(decl.prop)) {
  decl.cloneBefore({ prop: '-webkit-' + decl.prop })
}

So perhaps we could clone a declaration inside the variant plugin, instead of creating a new one from scratch? But why only with your own declared utilities? The plot thickens!


It seems when we are generating the CSS:

https://github.com/tailwindlabs/tailwindcss/blob/f1f419a9ecfcd00a2001ee96ab252739fca47564/src/lib/expandTailwindAtRules.js#L244-L246

We skip setting source recursively if preserveSource is true:

https://github.com/tailwindlabs/tailwindcss/blob/f1f419a9ecfcd00a2001ee96ab252739fca47564/src/util/cloneNodes.js#L22-L25

And for @layer-defined class rules, this preserveSource is indeed set to true:

https://github.com/tailwindlabs/tailwindcss/blob/f1f419a9ecfcd00a2001ee96ab252739fca47564/src/lib/setupContextUtils.js#L724-L726

Thus, for plugin-defined utilities, we don't have to worry about retaining source in the before/after plugin but we should do for @layer-defined class rules if seems.


So what would be the best way to solve this? Push the onus on to the plugins that use the PostCSS API to ensure they create appropriate source values? This would mean we could fix the core plugins to match this. Or try to fill in gaps in cloneNodes()? If we do want to fill in the gaps ourselves, should the source point to the original source of the @layer rule or the relevant @tailwind declaration?

Example test for this: ```js test('source maps for layer rules with injected nodes from plugins', async () => { let config = { content: [{ raw: `foo:bar` }], plugins: [ function ({ addVariant }) { addVariant('foo', ({ container }) => { container.walkRules((rule) => { rule.prepend(postcss.decl({ prop: 'foo', value: 'baz' })) }) }) }, ], } let input = postcss.parse( css` @tailwind utilities; @layer utilities { .bar { background-color: red; } } `, { from: 'input.css', map: { prev: map } } ) let result = await run(input, config) let { sources, annotations } = parseSourceMaps(result) // All CSS generated by Tailwind CSS should be annotated with source maps // And always be able to point to the original source file expect(sources).not.toContain('') }) ```
eugster commented 1 month ago

Found this, because I just got the same error when using a custom utility with before. As I got the error after moving the utility-class from an apply to markup I found some additional information: If used with @apply custom utility combined with before: doesn't throw the error mentioned. I verified this in the in the Reproduction-Url of gynekolog

EDIT: using @apply custom utility combined with before: doesn't throw an error in develop, but it breaks the build process. At least in one component. In the other the build throws just the same error as with develop, but runs.