diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f63760dc3f..aec79153c6a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index e75aefdbc6b0..f86f5e3d31d9 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -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) diff --git a/tests/apply.test.js b/tests/apply.test.js index ebba23526352..31b19bfa937e 100644 --- a/tests/apply.test.js +++ b/tests/apply.test.js @@ -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`
` }], + 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`
` }], + 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`
` }], + 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 = {