Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ensure content globs defined in `@config` files are relative to that file ([#14314](https://github.com/tailwindlabs/tailwindcss/pull/14314))
- Ensure CSS `theme()` functions are evaluated in media query ranges with collapsed whitespace ((#14321)[https://github.com/tailwindlabs/tailwindcss/pull/14321])
- Fix support for Nuxt projects in the Vite plugin (requires Nuxt 3.13.1+) ([#14319](https://github.com/tailwindlabs/tailwindcss/pull/14319))
- Evaluate theme functions in plugins and JS config files ([#14326](https://github.com/tailwindlabs/tailwindcss/pull/14326))

## [4.0.0-alpha.21] - 2024-09-02

Expand Down
122 changes: 121 additions & 1 deletion packages/tailwindcss/src/functions.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import { describe, expect, test } from 'vitest'
import { compileCss } from './test-utils/run'
import { compile } from '.'
import plugin from './plugin'
import { compileCss, optimizeCss } from './test-utils/run'

const css = String.raw

Expand Down Expand Up @@ -618,3 +620,121 @@ describe('theme function', () => {
})
})
})

describe('in plugins', () => {
test('CSS theme functions in plugins are properly evaluated', async () => {
let compiled = await compile(
css`
@layer base, utilities;
@plugin "my-plugin";
@theme reference {
--color-red: red;
--color-orange: orange;
--color-blue: blue;
--color-pink: pink;
}
@layer utilities {
@tailwind utilities;
}
`,
{
async loadPlugin() {
return plugin(({ addBase, addUtilities }) => {
addBase({
'.my-base-rule': {
color: 'theme(colors.red)',
'outline-color': 'theme(colors.orange / 15%)',
'background-color': 'theme(--color-blue)',
'border-color': 'theme(--color-pink / 10%)',
},
})
Comment on lines +643 to +650
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably also add an addUtility to test values inside utilities (since these won't be added to the initial CSS AST so it will be replaced at a different layer.

Suggested change
addBase({
'.my-base-rule': {
color: 'theme(colors.red)',
'outline-color': 'theme(colors.orange / 15%)',
'background-color': 'theme(--color-blue)',
'border-color': 'theme(--color-pink / 10%)',
},
})
addBase({
'.my-base-rule': {
color: 'theme(colors.red)',
'outline-color': 'theme(colors.orange / 15%)',
'background-color': 'theme(--color-blue)',
'border-color': 'theme(--color-pink / 10%)',
},
})
addUtilities({
'.my-utility': {
color: 'theme(colors.red)',
},
})


addUtilities({
'.my-utility': {
color: 'theme(colors.red)',
},
})
})
},
},
)

expect(optimizeCss(compiled.build(['my-utility'])).trim()).toMatchInlineSnapshot(`
"@layer base {
.my-base-rule {
color: red;
background-color: #00f;
border-color: #ffc0cb1a;
outline-color: #ffa50026;
}
}

@layer utilities {
.my-utility {
color: red;
}
}"
`)
})
})

describe('in JS config files', () => {
test('CSS theme functions in config files are properly evaluated', async () => {
let compiled = await compile(
css`
@layer base, utilities;
@config "./my-config.js";
@theme reference {
--color-red: red;
--color-orange: orange;
}
@layer utilities {
@tailwind utilities;
}
`,
{
loadConfig: async () => ({
theme: {
extend: {
colors: {
primary: 'theme(colors.red)',
secondary: 'theme(--color-orange)',
},
},
},
plugins: [
plugin(({ addBase, addUtilities }) => {
addBase({
'.my-base-rule': {
background: 'theme(colors.primary)',
color: 'theme(colors.secondary)',
},
})

addUtilities({
'.my-utility': {
color: 'theme(colors.red)',
},
})
}),
],
}),
},
)

expect(optimizeCss(compiled.build(['my-utility'])).trim()).toMatchInlineSnapshot(`
"@layer base {
.my-base-rule {
color: orange;
background: red;
}
}

@layer utilities {
.my-utility {
color: red;
}
}"
`)
})
})
11 changes: 9 additions & 2 deletions packages/tailwindcss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,11 @@ async function parseCss(
substituteAtApply(ast, designSystem)
}

// Replace `theme()` function calls with the actual theme variables.
if (css.includes(THEME_FUNCTION_INVOCATION)) {
// Replace `theme()` function calls with the actual theme variables. Plugins
// could register new rules that include functions, and JS config files could
// also contain functions or plugins that use functions so we need to evaluate
// functions if either of those are present.
if (plugins.length > 0 || configs.length > 0 || css.includes(THEME_FUNCTION_INVOCATION)) {
substituteFunctions(ast, pluginApi)
}

Expand Down Expand Up @@ -482,6 +485,10 @@ export async function compile(
return compiledCss
}

// Arbitrary values (`text-[theme(--color-red-500)]`) and arbitrary
// properties (`[--my-var:theme(--color-red-500)]`) can contain function
// calls so we need evaluate any functions we find there that weren't in
// the source CSS.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comment above but this is where theme() calls output by utilities (doing like addUtilities or matchUtilities) are processed.

substituteFunctions(newNodes, pluginApi)

previousAstNodeCount = newNodes.length
Expand Down