Skip to content

Commit 97a1fe6

Browse files
Emit variables necessary for the styles when using @reference "…"
1 parent e443b08 commit 97a1fe6

File tree

5 files changed

+236
-57
lines changed

5 files changed

+236
-57
lines changed

packages/@tailwindcss-postcss/src/index.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,12 @@ test('handle CSS when only using a `@reference` (we should not bail early)', asy
267267
)
268268

269269
expect(result.css.trim()).toMatchInlineSnapshot(`
270-
"@media (width >= 48rem) {
270+
":root, :host {
271+
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
272+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
273+
}
274+
275+
@media (width >= 48rem) {
271276
.foo {
272277
bar: baz;
273278
}

packages/tailwindcss/src/ast.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,11 @@ export function walkDepth(
256256

257257
// Optimize the AST for printing where all the special nodes that require custom
258258
// handling are handled such that the printing is a 1-to-1 transformation.
259-
export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) {
259+
export function optimizeAst(
260+
ast: AstNode[],
261+
designSystem: DesignSystem,
262+
firstThemeRule: StyleRule | null,
263+
) {
260264
let atRoots: AstNode[] = []
261265
let seenAtProperties = new Set<string>()
262266
let cssThemeVariables = new DefaultMap<
@@ -376,13 +380,32 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) {
376380

377381
// Context
378382
else if (node.kind === 'context') {
379-
// Remove reference imports from printing
380383
if (node.context.reference) {
381-
return
382-
}
384+
if (firstThemeRule) {
385+
let path = findNode(node.nodes, (node) => node === firstThemeRule)
386+
if (path) {
387+
let newPathToFirstThemeRule = path
388+
.filter((node) => node.kind === 'at-rule')
389+
.map((node) => ({ ...node, nodes: [] }))
390+
.reverse() as AtRule[]
391+
392+
let child = firstThemeRule as AstNode
393+
for (let node of newPathToFirstThemeRule) {
394+
node.nodes = [child]
395+
child = node
396+
}
383397

384-
for (let child of node.nodes) {
385-
transform(child, parent, { ...context, ...node.context }, depth)
398+
let newParent: AstNode[] = []
399+
transform(child, newParent, { ...context, ...node.context, reference: false }, depth)
400+
parent.push(...newParent)
401+
}
402+
}
403+
404+
return
405+
} else {
406+
for (let child of node.nodes) {
407+
transform(child, parent, { ...context, ...node.context }, depth)
408+
}
386409
}
387410
}
388411

@@ -401,7 +424,6 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) {
401424
for (let node of ast) {
402425
transform(node, newAst, {}, 0)
403426
}
404-
405427
// Remove unused theme variables
406428
if (enableRemoveUnusedThemeVariables) {
407429
next: for (let [parent, declarations] of cssThemeVariables) {
@@ -522,3 +544,14 @@ export function toCss(ast: AstNode[]) {
522544

523545
return css
524546
}
547+
548+
function findNode(ast: AstNode[], fn: (node: AstNode) => boolean): AstNode[] | null {
549+
let foundPath: AstNode[] = []
550+
walk(ast, (node, { path }) => {
551+
if (fn(node)) {
552+
foundPath = [...path]
553+
return WalkAction.Stop
554+
}
555+
})
556+
return foundPath
557+
}

packages/tailwindcss/src/at-import.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,11 @@ test('resolves @reference as `@import "…" reference`', async () => {
580580
{ loadStylesheet, candidates: ['text-red-500'] },
581581
),
582582
).resolves.toMatchInlineSnapshot(`
583-
".text-red-500 {
583+
":root, :host {
584+
--color-red-500: red;
585+
}
586+
587+
.text-red-500 {
584588
color: var(--color-red-500);
585589
}
586590
"

packages/tailwindcss/src/index.test.ts

Lines changed: 175 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3516,7 +3516,7 @@ it("should error when `layer(…)` is used, but it's not the first param", async
35163516
)
35173517
})
35183518

3519-
describe('`@import "…" reference`', () => {
3519+
describe('`@reference "…" reference`', () => {
35203520
test('recursively removes styles', async () => {
35213521
let loadStylesheet = async (id: string, base: string) => {
35223522
if (id === './foo/baz.css') {
@@ -3547,7 +3547,7 @@ describe('`@import "…" reference`', () => {
35473547
await expect(
35483548
compileCss(
35493549
`
3550-
@import './foo/bar.css' reference;
3550+
@reference './foo/bar.css';
35513551
35523552
.bar {
35533553
@apply md:hocus:foo;
@@ -3587,48 +3587,14 @@ describe('`@import "…" reference`', () => {
35873587

35883588
let { build } = await compile(
35893589
css`
3590-
@import './foo/bar.css' reference;
3590+
@reference './foo/bar.css';
35913591
`,
35923592
{ loadStylesheet },
35933593
)
35943594

35953595
expect(build(['text-underline', 'border']).trim()).toMatchInlineSnapshot(`""`)
35963596
})
35973597

3598-
test('removes styles when the import resolver was handled outside of Tailwind CSS', async () => {
3599-
await expect(
3600-
compileCss(
3601-
`
3602-
@media reference {
3603-
@layer theme {
3604-
@theme {
3605-
--breakpoint-md: 48rem;
3606-
}
3607-
.foo {
3608-
color: red;
3609-
}
3610-
}
3611-
@utility foo {
3612-
color: red;
3613-
}
3614-
@custom-variant hocus (&:hover, &:focus);
3615-
}
3616-
3617-
.bar {
3618-
@apply md:hocus:foo;
3619-
}
3620-
`,
3621-
[],
3622-
),
3623-
).resolves.toMatchInlineSnapshot(`
3624-
"@media (width >= 48rem) {
3625-
.bar:hover, .bar:focus {
3626-
color: red;
3627-
}
3628-
}"
3629-
`)
3630-
})
3631-
36323598
test('removes all @keyframes, even those contributed by JavasScript plugins', async () => {
36333599
await expect(
36343600
compileCss(
@@ -3703,8 +3669,179 @@ describe('`@import "…" reference`', () => {
37033669
},
37043670
),
37053671
).resolves.toMatchInlineSnapshot(`
3706-
".bar {
3672+
"@layer theme {
3673+
:root, :host {
3674+
--animate-spin: spin 1s linear infinite;
3675+
}
3676+
}
3677+
3678+
.bar {
37073679
animation: var(--animate-spin);
3680+
}
3681+
3682+
@keyframes spin {
3683+
to {
3684+
transform: rotate(360deg);
3685+
}
3686+
}"
3687+
`)
3688+
})
3689+
3690+
test('emits theme variables and keyframes defined inside @reference-ed files', async () => {
3691+
let loadStylesheet = async (id: string, base: string) => {
3692+
switch (id) {
3693+
case './one.css': {
3694+
return {
3695+
content: css`
3696+
@import './two.css' layer(two);
3697+
`,
3698+
base: '/root',
3699+
}
3700+
}
3701+
case './two.css': {
3702+
return {
3703+
content: css`
3704+
@import './three.css' layer(three);
3705+
`,
3706+
base: '/root',
3707+
}
3708+
}
3709+
case './three.css': {
3710+
return {
3711+
content: css`
3712+
.foo {
3713+
color: red;
3714+
}
3715+
@theme {
3716+
--color-red: red;
3717+
--animate-wiggle: wiggle 1s ease-in-out infinite;
3718+
@keyframes wiggle {
3719+
0%,
3720+
100% {
3721+
transform: rotate(-3deg);
3722+
}
3723+
50% {
3724+
transform: rotate(3deg);
3725+
}
3726+
}
3727+
}
3728+
`,
3729+
base: '/root',
3730+
}
3731+
}
3732+
}
3733+
}
3734+
3735+
await expect(
3736+
compileCss(
3737+
`
3738+
@reference './one.css';
3739+
.bar {
3740+
@apply text-red animate-wiggle;
3741+
}
3742+
`,
3743+
[],
3744+
{ loadStylesheet },
3745+
),
3746+
).resolves.toMatchInlineSnapshot(`
3747+
"@layer two {
3748+
@layer three {
3749+
:root, :host {
3750+
--color-red: red;
3751+
--animate-wiggle: wiggle 1s ease-in-out infinite;
3752+
}
3753+
}
3754+
}
3755+
3756+
.bar {
3757+
animation: var(--animate-wiggle);
3758+
color: var(--color-red);
3759+
}
3760+
3761+
@keyframes wiggle {
3762+
0%, 100% {
3763+
transform: rotate(-3deg);
3764+
}
3765+
3766+
50% {
3767+
transform: rotate(3deg);
3768+
}
3769+
}"
3770+
`)
3771+
})
3772+
3773+
test('supports `@import "…" reference` syntax', async () => {
3774+
let loadStylesheet = async () => {
3775+
return {
3776+
content: css`
3777+
.foo {
3778+
color: red;
3779+
}
3780+
@utility foo {
3781+
color: red;
3782+
}
3783+
@theme {
3784+
--breakpoint-md: 768px;
3785+
}
3786+
@custom-variant hocus (&:hover, &:focus);
3787+
`,
3788+
base: '/root/foo',
3789+
}
3790+
}
3791+
3792+
await expect(
3793+
compileCss(
3794+
`
3795+
@import './foo/bar.css' reference;
3796+
3797+
.bar {
3798+
@apply md:hocus:foo;
3799+
}
3800+
`,
3801+
[],
3802+
{ loadStylesheet },
3803+
),
3804+
).resolves.toMatchInlineSnapshot(`
3805+
"@media (width >= 768px) {
3806+
.bar:hover, .bar:focus {
3807+
color: red;
3808+
}
3809+
}"
3810+
`)
3811+
})
3812+
3813+
test('removes styles when the import resolver was handled outside of Tailwind CSS', async () => {
3814+
await expect(
3815+
compileCss(
3816+
`
3817+
@media reference {
3818+
@layer theme {
3819+
@theme {
3820+
--breakpoint-md: 48rem;
3821+
}
3822+
.foo {
3823+
color: red;
3824+
}
3825+
}
3826+
@utility foo {
3827+
color: red;
3828+
}
3829+
@custom-variant hocus (&:hover, &:focus);
3830+
}
3831+
3832+
.bar {
3833+
@apply md:hocus:foo;
3834+
}
3835+
`,
3836+
[],
3837+
),
3838+
).resolves.toMatchInlineSnapshot(`
3839+
"@layer theme;
3840+
3841+
@media (width >= 48rem) {
3842+
.bar:hover, .bar:focus {
3843+
color: red;
3844+
}
37083845
}"
37093846
`)
37103847
})

0 commit comments

Comments
 (0)