tailwindlabs / tailwindcss

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

JIT x CSS Modules x `@layer` directive results in no CSS module class emitted #4008

Closed sumanthratna closed 3 years ago

sumanthratna commented 3 years ago

What version of Tailwind CSS are you using?

v2.1.1

What version of Node.js are you using?

v12.14.1

What browser are you using?

Chrome

What operating system are you using?

macOS

Reproduction repository

repro steps below

The Bug

HTML elements aren't being assigned classnames when the Tailwind JIT engine is used with CSS modules and @layer components. This happens in both dev and prod

I think most people can remove @layer but I'm reporting this anyway because this seems like a regression

Reproduction

  1. yarn create next-app --example with-tailwindcss, then cd into the project dir
  2. apply this patch:
    
    diff --git a/package.json b/package.json
    index 46f0c5d..8132319 100644
    --- a/package.json
    +++ b/package.json
    @@ -13,9 +13,8 @@
     "react-dom": "^17.0.1"
    },
    "devDependencies": {
    -    "@tailwindcss/jit": "0.1.3",
     "autoprefixer": "^10.0.4",
     "postcss": "^8.1.10",
    -    "tailwindcss": "^2.0.2"
    +    "tailwindcss": "^2.1.1"
    }
    }
    diff --git a/pages/index.js b/pages/index.js
    index 5cb31bc..5084680 100644
    --- a/pages/index.js
    +++ b/pages/index.js
    @@ -1,8 +1,10 @@
    import Head from 'next/head'

+import styles from "./index.module.css"; + export default function Home() { return (

-"@tailwindcss/jit@0.1.3":

-tailwindcss@^2.0.2: +tailwindcss@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.1.1.tgz#642f6038c9283a8e1454da34585b8b7c1a1e8877" integrity sha512-zZ6axGqpSZOCBS7wITm/WNHkBzDt5CIZlDlx0eCVldwTxFPELCVGbgh7Xpb3/kZp3cUxOmK7bZUjqhuMrbN6xQ==


3. `yarn run dev`, notice that the root div has no class

This issue occurs: 
- if you switch to webpack 5

This issue does not occur: 
- if you disable JIT
- if you remove `@layer components`
- if you use `@apply` in a `@layer` directive in a global stylesheet, not in a CSS module (I think)
- if you use `@tailwindcss/jit` (I think)
birtles commented 3 years ago

I am seeing the same symptoms in a similar setup (but using just css-loader and mini-css-extract-plugin).

adamwathan commented 3 years ago

I think this is probably expected if I understand it right — CSS modules are processed in total isolation and it's not possible for a @layer rule there to be hoisted out into Tailwind's main @tailwind components layer, those two trees of CSS are just totally unable to communicate or manipulate each other.

What's the desired behavior here exactly?

adamwathan commented 3 years ago

So looking into it more, the reason this works without JIT is that the layer stuff is just ignored completely. So it's exactly the same as not using @layer at all. We should probably throw an explicit error or something if a layer is detected in a file where it's not possible for it to work.

sumanthratna commented 3 years ago

I think this is probably expected if I understand it right — CSS modules are processed in total isolation and it's not possible for a @layer rule there to be hoisted out into Tailwind's main @tailwind components layer, those two trees of CSS are just totally unable to communicate or manipulate each other.

This makes sense, but I still don't think I understand why JIT behaves differently than "traditional" compiling. I would expect that the traditional compiler and the JIT compiler would receive the same CSS input, so I think the difference is that in traditional mode, Tailwind simply removes @layer, while JIT completely skips @layer blocks—is this true?

I don't think I really need @layer if I'm using CSS modules, but I still feel like traditional mode and JIT mode should return identical outputs in all cases.

adamwathan commented 3 years ago

The difference is just that they are completely different chunks of code so there are just differences in how they work.

In the JIT engine, anything in a layer is read and converted into an in-memory Tailwind plugin so it can be used to generate styles on demand, and this happens only when the build server first starts. Then, all layer rules are deleted from the source file, including all the CSS within them, since instead those rules are inserted on demand when detected in the HTML.

In the traditional engine, layers are just blocks of CSS, and there's a step that gathers them all up and colocates code for the same layers together, and then late in the build stage the layer rules are converted to purgecss control comments which we use to make sure we only purge layers, and not regular custom CSS that lives outside of a layer.

So it's just totally different code that needs to do totally different things for each engine — just bound to be differences in the failure modes like you've detected here.

So in the traditional engine, what's happening with your CSS module is that the layer statements are being converted to comments, which means they end up just doing nothing. In the JIT engine, the layers are deleted since they are captured into memory as plugins, and now even though the HTML contains rules that are coming from those layer plugins, the code is never output because there is no @tailwind components block in the CSS module for the layer code to be inserted into.

I agree that the output should be the same, this is just one of those cases where an error situation I didn't really think much about ends with different output in each engine because of the design of the separate engines.

Both engines should probably error, but it's a little tricky because there's a real @layer rule coming to CSS and it might not be wise for us to assume all use of layer is Tailwind related. Can probably safely make assumptions about the base, components, and utilities layer names I suppose though.

adamwathan commented 3 years ago

In the next version this will throw an explicit error 👍🏻