mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Ensure we can apply classes defined with non-"on-demandable" selectors (#6922)
* improve extractCandidates
When we have a css rule that is defined as `.foo, .bar {}`, then we will
crawl each selector and link it to the same node. This is useful because
now our Map looks something like this:
```js
Map(2) { 'foo' => Node {}, 'bar' => Node {} }
```
This allows us to later on `@apply foo` or `@apply bar` and we can do a
direct lookup for this "candidate".
When we have css defined as `span {}`, then we consider this
"non-ondemandable". This means that we will _always_ inject these rules
into the `*` section and call it a day.
However, it could happen that you have something like this: `span, .foo
{}` up until now this was totally fine. It contains a non-ondemandable
selector (`span`) and therefore we injected this into that `*` section.
However, the issue occurs if you now try to `@apply foo`. Since we had
an early return for this use case it didn't endup in our Map from above
and now you get an error like:
```
The `foo` class does not exist. If `foo` is a custom class, make sure it
is defined within a `@layer` directive."
```
So instead what we will do is keep track whether or not a css rule
contains any on-demandable classes. If this is the case then we still
generate it always by putting it in that `*` section. However, we will
still register all on-demandable classes in our Map (in this case `.foo`).
This allows us to `@apply foo` again!
* update changelog
This commit is contained in:
parent
82f163d425
commit
21fe083db0
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Fixed
|
||||
|
||||
- Allow use of falsy values in theme config ([#6917](https://github.com/tailwindlabs/tailwindcss/pull/6917))
|
||||
- Ensure we can apply classes defined with non-"on-demandable" selectors ([#6922](https://github.com/tailwindlabs/tailwindcss/pull/6922))
|
||||
|
||||
|
||||
## [3.0.11] - 2022-01-05
|
||||
|
||||
@ -89,39 +89,55 @@ function getClasses(selector) {
|
||||
return parser.transformSync(selector)
|
||||
}
|
||||
|
||||
function extractCandidates(node) {
|
||||
function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
|
||||
let classes = []
|
||||
|
||||
// Handle normal rules
|
||||
if (node.type === 'rule') {
|
||||
for (let selector of node.selectors) {
|
||||
let classCandidates = getClasses(selector)
|
||||
// At least one of the selectors contains non-"on-demandable" candidates.
|
||||
if (classCandidates.length === 0) return []
|
||||
if (classCandidates.length === 0) {
|
||||
state.containsNonOnDemandable = true
|
||||
}
|
||||
|
||||
classes = [...classes, ...classCandidates]
|
||||
for (let classCandidate of classCandidates) {
|
||||
classes.push(classCandidate)
|
||||
}
|
||||
}
|
||||
return classes
|
||||
}
|
||||
|
||||
if (node.type === 'atrule') {
|
||||
// Handle at-rules (which contains nested rules)
|
||||
else if (node.type === 'atrule') {
|
||||
node.walkRules((rule) => {
|
||||
classes = [...classes, ...rule.selectors.flatMap((selector) => getClasses(selector))]
|
||||
for (let classCandidate of rule.selectors.flatMap((selector) =>
|
||||
getClasses(selector, state, depth + 1)
|
||||
)) {
|
||||
classes.push(classCandidate)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (depth === 0) {
|
||||
return [state.containsNonOnDemandable || classes.length === 0, classes]
|
||||
}
|
||||
|
||||
return classes
|
||||
}
|
||||
|
||||
function withIdentifiers(styles) {
|
||||
return parseStyles(styles).flatMap((node) => {
|
||||
let nodeMap = new Map()
|
||||
let candidates = extractCandidates(node)
|
||||
let [containsNonOnDemandableSelectors, candidates] = extractCandidates(node)
|
||||
|
||||
// If this isn't "on-demandable", assign it a universal candidate.
|
||||
if (candidates.length === 0) {
|
||||
return [['*', node]]
|
||||
// If this isn't "on-demandable", assign it a universal candidate to always include it.
|
||||
if (containsNonOnDemandableSelectors) {
|
||||
candidates.unshift('*')
|
||||
}
|
||||
|
||||
// However, it could be that it also contains "on-demandable" candidates.
|
||||
// E.g.: `span, .foo {}`, in that case it should still be possible to use
|
||||
// `@apply foo` for example.
|
||||
return candidates.map((c) => {
|
||||
if (!nodeMap.has(node)) {
|
||||
nodeMap.set(node, node)
|
||||
|
||||
@ -812,6 +812,104 @@ it('should be possible to apply user css without tailwind directives', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should be possible to apply a class from another rule with multiple selectors (2 classes)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="c"></div>` }],
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@layer utilities {
|
||||
.a,
|
||||
.b {
|
||||
@apply underline;
|
||||
}
|
||||
|
||||
.c {
|
||||
@apply b;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
.c {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should be possible to apply a class from another rule with multiple selectors (1 class, 1 tag)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="c"></div>` }],
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
span,
|
||||
.b {
|
||||
@apply underline;
|
||||
}
|
||||
|
||||
.c {
|
||||
@apply b;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
span,
|
||||
.b {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.c {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should be possible to apply a class from another rule with multiple selectors (1 class, 1 id)', () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="c"></div>` }],
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@layer utilities {
|
||||
#a,
|
||||
.b {
|
||||
@apply underline;
|
||||
}
|
||||
|
||||
.c {
|
||||
@apply b;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(css`
|
||||
#a,
|
||||
.b {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.c {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
it('apply can emit defaults in isolated environments without @tailwind directives', () => {
|
||||
let config = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user