Skip to content

Commit dfd601f

Browse files
committed
WIP
1 parent 19c8fe3 commit dfd601f

File tree

2 files changed

+73
-2
lines changed

2 files changed

+73
-2
lines changed

packages/tailwindcss/src/index.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,47 @@ describe('@apply', () => {
308308
}"
309309
`)
310310
})
311+
312+
it('should be possible to apply user-defined CSS', () => {
313+
expect(
314+
compileCss(css`
315+
@theme {
316+
--spacing-2: 0.5rem;
317+
--spacing-3: 0.75rem;
318+
--color-red-600: #e53e3e;
319+
}
320+
321+
.btn {
322+
@apply py-2 px-3;
323+
}
324+
325+
.btn-red {
326+
@apply btn bg-red-600;
327+
}
328+
`),
329+
).toMatchInlineSnapshot(`
330+
":root {
331+
--spacing-2: .5rem;
332+
--spacing-3: .75rem;
333+
--color-red-600: #e53e3e;
334+
}
335+
336+
.btn {
337+
padding-top: var(--spacing-2, .5rem);
338+
padding-bottom: var(--spacing-2, .5rem);
339+
padding-left: var(--spacing-3, .75rem);
340+
padding-right: var(--spacing-3, .75rem);
341+
}
342+
343+
.btn-red {
344+
padding-top: var(--spacing-2, .5rem);
345+
padding-bottom: var(--spacing-2, .5rem);
346+
padding-left: var(--spacing-3, .75rem);
347+
padding-right: var(--spacing-3, .75rem);
348+
background-color: var(--color-red-600, #e53e3e);
349+
}"
350+
`)
351+
})
311352
})
312353

313354
describe('arbitrary variants', () => {

packages/tailwindcss/src/index.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export function compile(css: string): {
2020
invalidCandidates.add(candidate)
2121
}
2222

23+
// Track `@apply` information
24+
let isApplyUsed = css.includes('@apply')
25+
let applyables = new Map<string, AstNode[]>()
26+
2327
// Find all `@theme` declarations
2428
let theme = new Theme()
2529
let firstThemeRule: Rule | null = null
@@ -28,6 +32,14 @@ export function compile(css: string): {
2832
walk(ast, (node, { replaceWith }) => {
2933
if (node.kind !== 'rule') return
3034

35+
// Track all user-defined classes for `@apply` support
36+
if (isApplyUsed && node.selector[0] === '.' && !node.selector.includes(' ')) {
37+
let candidate = node.selector.slice(1)
38+
let existing = applyables.get(candidate) ?? []
39+
existing.push(node)
40+
applyables.set(candidate, existing)
41+
}
42+
3143
// Drop instances of `@media reference`
3244
//
3345
// We support `@import "tailwindcss/theme" reference` as a way to import an external theme file
@@ -143,7 +155,7 @@ export function compile(css: string): {
143155
})
144156

145157
// Replace `@apply` rules with the actual utility classes.
146-
if (css.includes('@apply')) {
158+
if (isApplyUsed) {
147159
walk(ast, (node, { replaceWith }) => {
148160
if (node.kind === 'rule' && node.selector[0] === '@' && node.selector.startsWith('@apply')) {
149161
let candidates = node.selector
@@ -153,6 +165,25 @@ export function compile(css: string): {
153165

154166
// Replace the `@apply` rule with the actual utility classes
155167
{
168+
let newNodes: AstNode[] = []
169+
for (let candidate of candidates.splice(0)) {
170+
if (applyables.has(candidate)) {
171+
for (let candidateNode of applyables.get(candidate) ?? []) {
172+
candidateNode = structuredClone(candidateNode)
173+
174+
if (candidateNode.kind === 'rule' && candidateNode.selector[0] !== '@') {
175+
for (let child of candidateNode.nodes) {
176+
newNodes.push(child)
177+
}
178+
} else {
179+
newNodes.push(candidateNode)
180+
}
181+
}
182+
} else {
183+
candidates.push(candidate)
184+
}
185+
}
186+
156187
// Parse the candidates to an AST that we can replace the `@apply` rule with.
157188
let candidateAst = compileCandidates(candidates, designSystem, {
158189
onInvalidCandidate: (candidate) => {
@@ -163,7 +194,6 @@ export function compile(css: string): {
163194
// Collect the nodes to insert in place of the `@apply` rule. When a
164195
// rule was used, we want to insert its children instead of the rule
165196
// because we don't want the wrapping selector.
166-
let newNodes: AstNode[] = []
167197
for (let candidateNode of candidateAst) {
168198
if (candidateNode.kind === 'rule' && candidateNode.selector[0] !== '@') {
169199
for (let child of candidateNode.nodes) {

0 commit comments

Comments
 (0)