Skip to content
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

Ensure @apply works consistently with or without @layer #6938

Merged
merged 5 commits into from Jan 7, 2022

Conversation

RobinMalfait
Copy link
Contributor

@RobinMalfait RobinMalfait commented Jan 6, 2022

Resolves #6321

Time to write another story on @apply...

When we write code like this:

.a {
  @apply b;
}

.b {
  @apply uppercase;
  color: red;
}

Then we create 2 Nodes in our context to keep track of. One has identifier a, the other has identifier b. However, when we have an @apply and it contains multiple declarations/atrules, then we have to split up the (aka partition) node into multiple nodes so that we can guarantee the correct expected sort order.

This means that the above example technically looks like this:

.a {
  @apply b;
}

.b {
  @apply uppercase;
}

.b {
  color: red;
}

If this was your input, then we would still have 1 node for identifier 'a', but we would have 2 nodes for identifier 'b'.

As mentioned earlier, this is important to guarantee the correct order, here is an example:

.b {
  @apply md:font-bold xl:font-normal; /* Here we can sort by our
  internal rules. This means that the `md` comes before `xl`. */
}

... however

.b {
  @apply xl:font-normal; /* This now exists _before_ the example below */
}

.b {
  @apply md:font-bold; /* Because we respect the order of the user's css */
}

So to guarantee the order when doing this:

.b {
  @apply xl:font-normal;
  @apply lg:font-normal;
}

We also split this up into 2 nodes like this:

.b {
  @apply xl:font-normal;
}
.b {
  @apply lg:font-normal;
}

The tricky part is that now only 1 empty .b node exists in our context because we partitioned the orginal node into multiple nodes and moved the children to the new nodes and because they are new nodes it means that they have a different identity.

This partitioning used to happen in the expandApplyAtRules code, but this is a bit too late because the context has already been filled at this time. Instead, we move the code more to the front, as if you wrote those separated blocks yourself. Now the code to inject those nodes into the context happens in a single spot instead of multiple places.

Another good part about this is that we have better consistency between each layer because it turns out that these two examples generated different results...

.a {
  @apply b;
}
.b {
  @apply uppercase;
  color: red;
}

... is different compared to:

@tailwind components;
@layer components {
  .a {
    @apply b;
  }
  .b {
    @apply uppercase;
    color: red;
  }
}

Even if both a and b are being used in one of your content paths... Yeah.. sigh

Time to write another story on `@apply`...

When we write code like this:

```css
.a {
  @apply b;
}

.b {
  @apply uppercase;
  color: red;
}
```

Then we create 2 Nodes in our context to keep track of. One has
identifier `a`, the other has identifier `b`. However, when we have an
`@apply` and it contains multiple declarations/atrules, then we have to
split up the (aka partition) node into multiple nodes so that we can
guarantee the correct expected sort order.

This means that the above example technically looks like this:

```css
.a {
  @apply b;
}

.b {
  @apply uppercase;
}

.b {
  color: red;
}
```

If this was your input, then we would still have 1 node for identifier
'a', but we would have 2 nodes for identifier 'b'.

As mentioned earlier, this is important to guarantee the correct order,
here is an example:

```css
.b {
  @apply md:font-bold xl:font-normal; /* Here we can sort by our
  internal rules. This means that the `md` comes before `xl`. */
}
```

... however

```css
.b {
  @apply xl:font-normal; /* This now exists _before_ the example below */
}

.b {
  @apply md:font-bold; /* Because we respect the order of the user's css */
}
```

So to guarantee the order when doing this:
```css
.b {
  @apply xl:font-normal;
  @apply lg:font-normal;
}
```

We also split this up into 2 nodes like this:
```css
.b {
  @apply xl:font-normal;
}
.b {
  @apply lg:font-normal;
}
```

The tricky part is that now only 1 empty `.b` node exists in our context
because we partitioned the orginal node into multiple nodes and moved
the children to the new nodes and because they are new nodes it means
that they have a different identity.

This partitioning used to happen in the expandApplyAtRules code, but
this is a bit too late because the context has already been filled at
this time. Instead, we move the code more to the front, as if you wrote
those separated blocks yourself. Now the code to inject those nodes into
the context happens in a single spot instead of multiple places.

Another good part about this is that we have better consistency between
each layer because it turns out that these two examples generated
different results...

```css
.a {
  @apply b;
}
.b {
  @apply uppercase;
  color: red;
}
```

... is different compared to:

```css
@tailwind components;
@layer components {
  .a {
    @apply b;
  }
  .b {
    @apply uppercase;
    color: red;
  }
}
```

Even if both `a` and `b` are being used in one of your content paths...
Yeah.. *sigh*
@allaire
Copy link

allaire commented Jan 7, 2022

@RobinMalfait since this new version (3.0.12, I suspect this commit), the following input is always white (any css applied by @apply has precedence). in Tailwind 3.0.11 and in V2, the input was red.

    .input-text {
      @apply w-full h-4 px-6 rounded-full bg-white appearance-none text-white font-semibold;

      background-color: red;

My main site.scss file is structured as follow:

// Tailwind
@import "~tailwindcss/base";
@import "~tailwindcss/components";

// ... All my files

// Keep last
@import "~tailwindcss/utilities";

I tried to reproduce in https://play.tailwindcss.com/ but I'm not able to. Maybe because I use PostCSS or Saas?

Am I missing something? Downgrading to 3.0.11 fixed it.

@thecrypticace
Copy link
Contributor

Hey @allaire that seems super weird. I can't reproduce what you're seeing. Could you provide a reproduction we can take a look at?

@allaire
Copy link

allaire commented Jan 7, 2022

Hi @thecrypticace, I can't create a reproduction, but after investigation, it seems to be related to a hot reload issue with Webpack finally, because I get the same bug in 3.0.11 and 3.0.12 finally:

First compile of my CSS, color is red. if I save and Webpack hot reload the file, background is white.

@thecrypticace
Copy link
Contributor

That is odd — not sure if that's a tailwind issue or something with webpack but that's helpful to know. Thanks!

@allaire
Copy link

allaire commented Jan 7, 2022

@thecrypticace So I just tested with Tailwind 2.x with jit mode enabled and the same webpack/postcss config/versions and I don't have the issue, so this must be related to Tailwind 3.x.

It looks like when I edit the file, the order of the properties is different than the initial webpack build.

SCSS:

...
  &__form {
    @apply relative;

    &-input {
      @apply w-full h-4 px-6 rounded-full bg-white appearance-none text-white font-semibold;

      background-color: rgba($white, 0.15);
      transition: background-color 0.2s;

      &::placeholder {
        @apply font-semibold opacity-100;

        color: rgba($white, 0.75);
      }

      @include hover {
        background-color: rgba($white, 0.2);

        &::placeholder {
          @apply text-white;
        }
      }

      @include focus {
        @apply text-greenery bg-white;

        &::placeholder {
          @apply text-greenery-300 opacity-100;
        }
      }

      @screen md {
        padding-right: 220px;
      }
    }

Initial build compiled CSS:

.hero__form-input {
    height: 4rem;
    width: 100%;
    -webkit-appearance: none;
    appearance: none;
    border-radius: 9999px;
    --tw-bg-opacity: 1;
    background-color: rgb(255 255 255 / var(--tw-bg-opacity));
    padding-left: 1.5rem;
    padding-right: 1.5rem;
    font-weight: 600;
    --tw-text-opacity: 1;
    color: rgb(255 255 255 / var(--tw-text-opacity));
    background-color: rgba(255, 255, 255, 0.15);
    transition: background-color 0.2s;
}

Compiled CSS after file save:

.hero__form-input {
    background-color: rgba(255, 255, 255, 0.15);
    transition: background-color 0.2s;
    height: 4rem;
    width: 100%;
    -webkit-appearance: none;
    appearance: none;
    border-radius: 9999px;
    --tw-bg-opacity: 1;
    background-color: rgb(255 255 255 / var(--tw-bg-opacity));
    padding-left: 1.5rem;
    padding-right: 1.5rem;
    font-weight: 600;
    --tw-text-opacity: 1;
    color: rgb(255 255 255 / var(--tw-text-opacity));
}

It looks like the @apply propeties are compiled after my properties in the CSS element (background-color and transition)

Should I create a new issue?

@thecrypticace
Copy link
Contributor

I confirmed this is an issue with tailwind & multiple postcss builds. But yes please do open an issue. I've prototyped a fix for it but want to be sure there's no fallout from it and will look over it on Monday and hopefully get the fix out. Thanks for reporting it!

@allaire
Copy link

allaire commented Jan 10, 2022

@thecrypticace I haven't open an issue yet, let me know if you want me to test something in the meantime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Utility classes can't be built with @apply and custom props
3 participants