mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Prevent nesting plugin from breaking other plugins (#7563)
* Prevent nesting plugin from breaking other plugins This uses a private API but it’s the only solution we have right now. It’s guarded to hopefully be less breaking if the API disappears. * Update changelog
This commit is contained in:
parent
9effea5d28
commit
af64d7190c
@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Nothing yet!
|
||||
- Prevent nesting plugin from breaking other plugins ([#7563](https://github.com/tailwindlabs/tailwindcss/pull/7563))
|
||||
|
||||
## [3.0.23] - 2022-02-16
|
||||
|
||||
|
||||
@ -39,6 +39,42 @@ export function nesting(opts = postcssNested) {
|
||||
decl.remove()
|
||||
})
|
||||
|
||||
/**
|
||||
* Use a private PostCSS API to remove the "clean" flag from the entire AST.
|
||||
* This is done because running process() on the AST will set the "clean"
|
||||
* flag on all nodes, which we don't want.
|
||||
*
|
||||
* This causes downstream plugins using the visitor API to be skipped.
|
||||
*
|
||||
* This is guarded because the PostCSS API is not public
|
||||
* and may change in future versions of PostCSS.
|
||||
*
|
||||
* See https://github.com/postcss/postcss/issues/1712 for more details
|
||||
*
|
||||
* @param {import('postcss').Node} node
|
||||
*/
|
||||
function markDirty(node) {
|
||||
if (!('markDirty' in node)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Traverse the tree down to the leaf nodes
|
||||
if (node.nodes) {
|
||||
node.nodes.forEach((n) => markDirty(n))
|
||||
}
|
||||
|
||||
// If it's a leaf node mark it as dirty
|
||||
// We do this here because marking a node as dirty
|
||||
// will walk up the tree and mark all parents as dirty
|
||||
// resulting in a lot of unnecessary work if we did this
|
||||
// for every single node
|
||||
if (!node.nodes) {
|
||||
node.markDirty()
|
||||
}
|
||||
}
|
||||
|
||||
markDirty(root)
|
||||
|
||||
return root
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import postcss from 'postcss'
|
||||
import postcssNested from 'postcss-nested'
|
||||
import plugin from '../../../src/postcss-plugins/nesting'
|
||||
import { visitorSpyPlugin } from './plugins.js'
|
||||
|
||||
it('should be possible to load a custom nesting plugin', async () => {
|
||||
let input = css`
|
||||
@ -166,6 +167,46 @@ test('@screen rules can work with `@apply`', async () => {
|
||||
`)
|
||||
})
|
||||
|
||||
test('nesting does not break downstream plugin visitors', async () => {
|
||||
let input = css`
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
@suppoerts (color: blue) {
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
/* Comment */
|
||||
`
|
||||
|
||||
let spyPlugin = visitorSpyPlugin()
|
||||
|
||||
let plugins = [plugin(postcssNested), spyPlugin.plugin]
|
||||
|
||||
let result = await run(input, plugins)
|
||||
|
||||
expect(result).toMatchCss(css`
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
@suppoerts (color: blue) {
|
||||
.foo {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
/* Comment */
|
||||
`)
|
||||
|
||||
expect(spyPlugin.spies.Once).toHaveBeenCalled()
|
||||
expect(spyPlugin.spies.OnceExit).toHaveBeenCalled()
|
||||
expect(spyPlugin.spies.Root).toHaveBeenCalled()
|
||||
expect(spyPlugin.spies.Rule).toHaveBeenCalled()
|
||||
expect(spyPlugin.spies.AtRule).toHaveBeenCalled()
|
||||
expect(spyPlugin.spies.Comment).toHaveBeenCalled()
|
||||
expect(spyPlugin.spies.Declaration).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// ---
|
||||
|
||||
function indentRecursive(node, indent = 0) {
|
||||
@ -187,11 +228,21 @@ function formatNodes(root) {
|
||||
}
|
||||
|
||||
async function run(input, options) {
|
||||
return (
|
||||
await postcss([options === undefined ? plugin : plugin(options), formatNodes]).process(input, {
|
||||
from: undefined,
|
||||
})
|
||||
).toString()
|
||||
let plugins = []
|
||||
|
||||
if (Array.isArray(options)) {
|
||||
plugins = options
|
||||
} else {
|
||||
plugins.push(options === undefined ? plugin : plugin(options))
|
||||
}
|
||||
|
||||
plugins.push(formatNodes)
|
||||
|
||||
let result = await postcss(plugins).process(input, {
|
||||
from: undefined,
|
||||
})
|
||||
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
function css(templates) {
|
||||
|
||||
42
tests/postcss-plugins/nesting/plugins.js
Normal file
42
tests/postcss-plugins/nesting/plugins.js
Normal file
@ -0,0 +1,42 @@
|
||||
export function visitorSpyPlugin() {
|
||||
let Once = jest.fn()
|
||||
let OnceExit = jest.fn()
|
||||
let Root = jest.fn()
|
||||
let AtRule = jest.fn()
|
||||
let Rule = jest.fn()
|
||||
let Comment = jest.fn()
|
||||
let Declaration = jest.fn()
|
||||
|
||||
let plugin = Object.assign(
|
||||
function () {
|
||||
return {
|
||||
postcssPlugin: 'visitor-test',
|
||||
|
||||
// These work fine
|
||||
Once,
|
||||
OnceExit,
|
||||
|
||||
// These break
|
||||
Root,
|
||||
Rule,
|
||||
AtRule,
|
||||
Declaration,
|
||||
Comment,
|
||||
}
|
||||
},
|
||||
{ postcss: true }
|
||||
)
|
||||
|
||||
return {
|
||||
plugin,
|
||||
spies: {
|
||||
Once,
|
||||
OnceExit,
|
||||
Root,
|
||||
AtRule,
|
||||
Rule,
|
||||
Comment,
|
||||
Declaration,
|
||||
},
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user