mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Remove all @keyframes in reference import mode (#15581)
This PR fixes an issue where JavaScript plugins were still able to contribute `@keyframes` when loaded inside an `@reference` import. This was possible because we only gated the `addBase` API and not the `addUtilities` one which also has a special branch to handle `@keyframe` rules. To make this work, we have to create a new instance of the plugin API that has awareness of wether the plugin accessing it is inside reference import mode. ## Test plan Added a unit test that reproduces the issue observed via #15544
This commit is contained in:
parent
a3aec17908
commit
d7c8448eec
@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Improve performance and memory usage ([#15529](https://github.com/tailwindlabs/tailwindcss/pull/15529))
|
||||
- Ensure `@apply` rules are processed in the correct order ([#15542](https://github.com/tailwindlabs/tailwindcss/pull/15542))
|
||||
- Allow negative utility names in `@utilty` ([#15573](https://github.com/tailwindlabs/tailwindcss/pull/15573))
|
||||
- Remove all `@keyframes` contributed by JavaScript plugins when using `@reference` imports ([#15581](https://github.com/tailwindlabs/tailwindcss/pull/15581))
|
||||
- _Upgrade (experimental)_: Do not extract class names from functions (e.g. `shadow` in `filter: 'drop-shadow(…)'`) ([#15566](https://github.com/tailwindlabs/tailwindcss/pull/15566))
|
||||
|
||||
### Changed
|
||||
|
||||
@ -325,6 +325,11 @@ export function optimizeAst(ast: AstNode[]) {
|
||||
|
||||
// Context
|
||||
else if (node.kind === 'context') {
|
||||
// Remove reference imports from printing
|
||||
if (node.context.reference) {
|
||||
return
|
||||
}
|
||||
|
||||
for (let child of node.nodes) {
|
||||
transform(child, parent, depth)
|
||||
}
|
||||
|
||||
@ -276,14 +276,27 @@ function upgradeToFullPluginSupport({
|
||||
}
|
||||
}
|
||||
|
||||
let pluginApi = buildPluginApi(designSystem, ast, resolvedConfig, {
|
||||
set current(value: number) {
|
||||
features |= value
|
||||
let pluginApiConfig = {
|
||||
designSystem,
|
||||
ast,
|
||||
resolvedConfig,
|
||||
featuresRef: {
|
||||
set current(value: number) {
|
||||
features |= value
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
let pluginApi = buildPluginApi({ ...pluginApiConfig, referenceMode: false })
|
||||
let referenceModePluginApi = undefined
|
||||
|
||||
for (let { handler, reference } of resolvedConfig.plugins) {
|
||||
handler(reference ? { ...pluginApi, addBase: () => {} } : pluginApi)
|
||||
if (reference) {
|
||||
referenceModePluginApi ||= buildPluginApi({ ...pluginApiConfig, referenceMode: true })
|
||||
handler(referenceModePluginApi)
|
||||
} else {
|
||||
handler(pluginApi)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the user-configured theme keys into the design system. The compat
|
||||
|
||||
@ -86,14 +86,22 @@ export type PluginAPI = {
|
||||
|
||||
const IS_VALID_UTILITY_NAME = /^[a-z@][a-zA-Z0-9/%._-]*$/
|
||||
|
||||
export function buildPluginApi(
|
||||
designSystem: DesignSystem,
|
||||
ast: AstNode[],
|
||||
resolvedConfig: ResolvedConfig,
|
||||
featuresRef: { current: Features },
|
||||
): PluginAPI {
|
||||
export function buildPluginApi({
|
||||
designSystem,
|
||||
ast,
|
||||
resolvedConfig,
|
||||
featuresRef,
|
||||
referenceMode,
|
||||
}: {
|
||||
designSystem: DesignSystem
|
||||
ast: AstNode[]
|
||||
resolvedConfig: ResolvedConfig
|
||||
featuresRef: { current: Features }
|
||||
referenceMode: boolean
|
||||
}): PluginAPI {
|
||||
let api: PluginAPI = {
|
||||
addBase(css) {
|
||||
if (referenceMode) return
|
||||
let baseNodes = objectToAst(css)
|
||||
featuresRef.current |= substituteFunctions(baseNodes, designSystem)
|
||||
ast.push(atRule('@layer', 'base', baseNodes))
|
||||
@ -212,7 +220,9 @@ export function buildPluginApi(
|
||||
|
||||
for (let [name, css] of entries) {
|
||||
if (name.startsWith('@keyframes ')) {
|
||||
ast.push(rule(name, objectToAst(css)))
|
||||
if (!referenceMode) {
|
||||
ast.push(rule(name, objectToAst(css)))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@ -3212,7 +3212,7 @@ describe('`@import "…" reference`', () => {
|
||||
{ loadStylesheet },
|
||||
)
|
||||
|
||||
expect(build(['text-underline', 'border']).trim()).toMatchInlineSnapshot(`"@layer utilities;"`)
|
||||
expect(build(['text-underline', 'border']).trim()).toMatchInlineSnapshot(`""`)
|
||||
})
|
||||
|
||||
test('removes styles when the import resolver was handled outside of Tailwind CSS', async () => {
|
||||
@ -3241,13 +3241,91 @@ describe('`@import "…" reference`', () => {
|
||||
[],
|
||||
),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
"@layer theme;
|
||||
|
||||
@media (width >= 48rem) {
|
||||
"@media (width >= 48rem) {
|
||||
.bar:hover, .bar:focus {
|
||||
color: red;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('removes all @keyframes, even those contributed by JavasScript plugins', async () => {
|
||||
await expect(
|
||||
compileCss(
|
||||
css`
|
||||
@media reference {
|
||||
@layer theme, base, components, utilities;
|
||||
@layer theme {
|
||||
@theme {
|
||||
--animate-spin: spin 1s linear infinite;
|
||||
--animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@layer base {
|
||||
@keyframes ping {
|
||||
75%,
|
||||
100% {
|
||||
transform: scale(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@plugin "my-plugin";
|
||||
}
|
||||
|
||||
.bar {
|
||||
@apply animate-spin;
|
||||
}
|
||||
`,
|
||||
['animate-spin', 'match-utility-initial', 'match-components-initial'],
|
||||
{
|
||||
loadModule: async () => ({
|
||||
module: ({
|
||||
addBase,
|
||||
addUtilities,
|
||||
addComponents,
|
||||
matchUtilities,
|
||||
matchComponents,
|
||||
}: PluginAPI) => {
|
||||
addBase({
|
||||
'@keyframes base': { '100%': { opacity: '0' } },
|
||||
})
|
||||
addUtilities({
|
||||
'@keyframes utilities': { '100%': { opacity: '0' } },
|
||||
})
|
||||
addComponents({
|
||||
'@keyframes components ': { '100%': { opacity: '0' } },
|
||||
})
|
||||
matchUtilities(
|
||||
{
|
||||
'match-utility': (value) => ({
|
||||
'@keyframes match-utilities': { '100%': { opacity: '0' } },
|
||||
}),
|
||||
},
|
||||
{ values: { initial: 'initial' } },
|
||||
)
|
||||
matchComponents(
|
||||
{
|
||||
'match-components': (value) => ({
|
||||
'@keyframes match-components': { '100%': { opacity: '0' } },
|
||||
}),
|
||||
},
|
||||
{ values: { initial: 'initial' } },
|
||||
)
|
||||
},
|
||||
base: '/root',
|
||||
}),
|
||||
},
|
||||
),
|
||||
).resolves.toMatchInlineSnapshot(`
|
||||
".bar {
|
||||
animation: var(--animate-spin);
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
@ -362,42 +362,6 @@ async function parseCss(
|
||||
|
||||
// Handle `@import "…" reference`
|
||||
else if (param === 'reference') {
|
||||
walk(node.nodes, (child, { replaceWith }) => {
|
||||
if (child.kind !== 'at-rule') {
|
||||
replaceWith([])
|
||||
return WalkAction.Skip
|
||||
}
|
||||
switch (child.name) {
|
||||
case '@theme': {
|
||||
let themeParams = segment(child.params, ' ')
|
||||
if (!themeParams.includes('reference')) {
|
||||
child.params = (child.params === '' ? '' : ' ') + 'reference'
|
||||
}
|
||||
return WalkAction.Skip
|
||||
}
|
||||
case '@import':
|
||||
case '@config':
|
||||
case '@plugin':
|
||||
case '@variant':
|
||||
case '@utility': {
|
||||
return WalkAction.Skip
|
||||
}
|
||||
|
||||
case '@media':
|
||||
case '@supports':
|
||||
case '@layer': {
|
||||
// These rules should be recursively traversed as these might be
|
||||
// inserted by the `@import` resolution.
|
||||
return
|
||||
}
|
||||
|
||||
default: {
|
||||
replaceWith([])
|
||||
return WalkAction.Skip
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
node.nodes = [contextNode({ reference: true }, node.nodes)]
|
||||
}
|
||||
|
||||
@ -420,6 +384,10 @@ async function parseCss(
|
||||
if (node.name === '@theme') {
|
||||
let [themeOptions, themePrefix] = parseThemeOptions(node.params)
|
||||
|
||||
if (context.reference) {
|
||||
themeOptions |= ThemeOptions.REFERENCE
|
||||
}
|
||||
|
||||
if (themePrefix) {
|
||||
if (!IS_VALID_PREFIX.test(themePrefix)) {
|
||||
throw new Error(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user