Evaluate theme functions in plugins (#14326)
This PR fixes a bug where CSS `theme()` functions were not evaluated
when present in rules added by plugins, using either `@plugin` or
registering a plugin in a JS config file.
For example, prior to this PR the `theme()` functions in this plugin
would make it into the final CSS without being evaluated:
```js
// ./my-plugin.js
export default plugin(({ addBase }) => {
addBase({
'.my-rule': {
background: 'theme(colors.primary)',
color: 'theme(colors.secondary)',
},
})
})
```
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This commit is contained in:
parent
44dd6da4fe
commit
6d0371afb1
3 changed files with 131 additions and 3 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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%)',
|
||||
},
|
||||
})
|
||||
|
||||
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;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
@ -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.
|
||||
substituteFunctions(newNodes, pluginApi)
|
||||
|
||||
previousAstNodeCount = newNodes.length
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue