Support @apply for classes outside of a @layer (#5378)
* support `@apply` for classes outside of a `@layer`
* Add failing test for respecting source order
* sort rules when using `@apply`
The `layer` was not taken into account yet when we resolved the rules
from the applyCache. This is because we set the `classCache` to the
`matches` inside of the `generateRules` function. You can think of them
as "raw" rules I guess. However, it is later in that function that we
apply the `layerOrder` to the `sort`.
This does mean that when you `@apply font-bold text-red-500` that the
rules inside the `.target {}` will be in order of the "normal" css as
well.
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This commit is contained in:
parent
4142b3fbaf
commit
f99302c626
4 changed files with 196 additions and 4 deletions
|
|
@ -97,6 +97,7 @@ function processApply(root, context) {
|
|||
for (let util of candidates) {
|
||||
applyCandidates.add(util)
|
||||
}
|
||||
|
||||
applies.push(rule)
|
||||
})
|
||||
|
||||
|
|
@ -210,7 +211,12 @@ function processApply(root, context) {
|
|||
})
|
||||
}
|
||||
|
||||
siblings.push([meta, root.nodes[0]])
|
||||
// Insert it
|
||||
siblings.push([
|
||||
// Ensure that when we are sorting, that we take the layer order into account
|
||||
{ ...meta, sort: meta.sort | context.layerOrder[meta.layer] },
|
||||
root.nodes[0],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,16 @@ function buildStylesheet(rules, context) {
|
|||
components: new Set(),
|
||||
utilities: new Set(),
|
||||
variants: new Set(),
|
||||
|
||||
// All the CSS that is not Tailwind related can be put in this bucket. This
|
||||
// will make it easier to later use this information when we want to
|
||||
// `@apply` for example. The main reason we do this here is because we
|
||||
// still need to make sure the order is correct. Last but not least, we
|
||||
// will make sure to always re-inject this section into the css, even if
|
||||
// certain rules were not used. This means that it will look like a no-op
|
||||
// from the user's perspective, but we gathered all the useful information
|
||||
// we need.
|
||||
user: new Set(),
|
||||
}
|
||||
|
||||
for (let [sort, rule] of sortedRules) {
|
||||
|
|
@ -131,6 +141,11 @@ function buildStylesheet(rules, context) {
|
|||
returnValue.utilities.add(rule)
|
||||
continue
|
||||
}
|
||||
|
||||
if (sort & context.layerOrder.user) {
|
||||
returnValue.user.add(rule)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue
|
||||
|
|
|
|||
|
|
@ -201,6 +201,17 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|||
|
||||
return getConfigValue(['variants', path], defaultValue)
|
||||
},
|
||||
addUserCss(userCss) {
|
||||
for (let [identifier, rule] of withIdentifiers(userCss)) {
|
||||
let offset = offsets.user++
|
||||
|
||||
if (!context.candidateRuleMap.has(identifier)) {
|
||||
context.candidateRuleMap.set(identifier, [])
|
||||
}
|
||||
|
||||
context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
|
||||
}
|
||||
},
|
||||
addBase(base) {
|
||||
for (let [identifier, rule] of withIdentifiers(base)) {
|
||||
let prefixedIdentifier = prefixIdentifier(identifier, {})
|
||||
|
|
@ -404,6 +415,15 @@ function collectLayerPlugins(root) {
|
|||
}
|
||||
})
|
||||
|
||||
root.walkRules((rule) => {
|
||||
// At this point it is safe to include all the left-over css from the
|
||||
// user's css file. This is because the `@tailwind` and `@layer` directives
|
||||
// will already be handled and will be removed from the css tree.
|
||||
layerPlugins.push(function ({ addUserCss }) {
|
||||
addUserCss(rule, { respectPrefix: false })
|
||||
})
|
||||
})
|
||||
|
||||
return layerPlugins
|
||||
}
|
||||
|
||||
|
|
@ -448,6 +468,7 @@ function registerPlugins(plugins, context) {
|
|||
base: 0n,
|
||||
components: 0n,
|
||||
utilities: 0n,
|
||||
user: 0n,
|
||||
}
|
||||
|
||||
let pluginApi = buildPluginApi(context.tailwindConfig, context, {
|
||||
|
|
@ -470,6 +491,7 @@ function registerPlugins(plugins, context) {
|
|||
offsets.base,
|
||||
offsets.components,
|
||||
offsets.utilities,
|
||||
offsets.user,
|
||||
])
|
||||
let reservedBits = BigInt(highestOffset.toString(2).length)
|
||||
|
||||
|
|
@ -477,9 +499,10 @@ function registerPlugins(plugins, context) {
|
|||
base: (1n << reservedBits) << 0n,
|
||||
components: (1n << reservedBits) << 1n,
|
||||
utilities: (1n << reservedBits) << 2n,
|
||||
user: (1n << reservedBits) << 3n,
|
||||
}
|
||||
|
||||
reservedBits += 3n
|
||||
reservedBits += 4n
|
||||
|
||||
let offset = 0
|
||||
context.variantOrder = new Map(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import { run, css } from './util/run'
|
||||
import { run, html, css } from './util/run'
|
||||
|
||||
test('@apply', () => {
|
||||
let config = {
|
||||
|
|
@ -236,7 +236,7 @@ test('@apply error when using a prefixed .group utility', async () => {
|
|||
let config = {
|
||||
prefix: 'tw-',
|
||||
darkMode: 'class',
|
||||
content: [{ raw: '<div class="foo"></div>' }],
|
||||
content: [{ raw: html`<div class="foo"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
|
|
@ -254,3 +254,151 @@ test('@apply error when using a prefixed .group utility', async () => {
|
|||
`@apply should not be used with the 'tw-group' utility`
|
||||
)
|
||||
})
|
||||
|
||||
test('@apply classes from outside a @layer', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="font-bold foo bar baz"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.foo {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
.bar {
|
||||
@apply foo text-red-500 hover:text-green-500;
|
||||
}
|
||||
|
||||
.baz {
|
||||
@apply bar underline;
|
||||
}
|
||||
|
||||
.keep-me-even-though-I-am-not-used-in-content {
|
||||
color: green;
|
||||
}
|
||||
`
|
||||
|
||||
await run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.foo {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bar {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(239, 68, 68, var(--tw-text-opacity));
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bar:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(34, 197, 94, var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.baz {
|
||||
text-decoration: underline;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(239, 68, 68, var(--tw-text-opacity));
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.baz:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(34, 197, 94, var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.keep-me-even-though-I-am-not-used-in-content {
|
||||
color: green;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('@applying classes from outside a @layer respects the source order', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="container font-bold foo bar baz"></div>` }],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
.baz {
|
||||
@apply bar underline;
|
||||
}
|
||||
|
||||
@tailwind components;
|
||||
|
||||
.keep-me-even-though-I-am-not-used-in-content {
|
||||
color: green;
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
|
||||
.foo {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
.bar {
|
||||
@apply no-underline;
|
||||
}
|
||||
`
|
||||
|
||||
await run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.baz {
|
||||
text-decoration: underline;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
|
||||
.keep-me-even-though-I-am-not-used-in-content {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.foo {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bar {
|
||||
text-decoration: none;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue