-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Use AST transformations in @tailwindcss/postcss
#15297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
a5407e3
allow to pass in the `!important` flag
RobinMalfait 29cc49d
implement `postCssAstToCssAst` and `cssAstToPostCssAst`
RobinMalfait 94aeeab
optimize AST before printing
RobinMalfait 7eefb19
implement new `compileAst(…)` and update `compile(…)`
RobinMalfait 531670b
expose `compileAst` from `@tailwindcss/node`
RobinMalfait 6cabeea
WIP: PostCSS implementation
RobinMalfait 42401b4
Tweak code
thecrypticace 0e954b2
Convert our AST to PostCSS’s AST
thecrypticace 79cadb8
Pass through source information
thecrypticace 2adaf42
print CSS from our own AST
RobinMalfait ef17c45
only convert our AST into a PostCSS AST when not optimizing
RobinMalfait f006e36
remove unused variable
RobinMalfait 709947c
rename variables to be more explicit
RobinMalfait a4df476
add timing info around `cssAstToPostCssAst(…)`
RobinMalfait 71c1c0d
restructure `build(…)`
RobinMalfait a24e4c4
track the `tailwindCssAst` for subsequent cache hits
RobinMalfait 260beed
ensure PostCSS AST nodes have semicolons
RobinMalfait 87065a8
handle comments correctly
RobinMalfait 3de1864
compute current_mtimes in parallel per file/directory
RobinMalfait 98f66cb
remove instrumentation from `read_dir`
RobinMalfait 84bde5e
trick PostCSS into using 2 spaces instead of 4
RobinMalfait 7aeea03
use `??=` shorthand
RobinMalfait 1a04957
ensure `compile` and `compileAst` re-use the same logic
RobinMalfait 5f22986
run format
RobinMalfait c5c7c9f
clone the cached PostCSS tree before appending it to the root
RobinMalfait File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import dedent from 'dedent' | ||
| import postcss from 'postcss' | ||
| import { expect, it } from 'vitest' | ||
| import { toCss } from '../../tailwindcss/src/ast' | ||
| import { parse } from '../../tailwindcss/src/css-parser' | ||
| import { cssAstToPostCssAst, postCssAstToCssAst } from './ast' | ||
|
|
||
| let css = dedent | ||
|
|
||
| it('should convert a PostCSS AST into a Tailwind CSS AST', () => { | ||
| let input = css` | ||
| @charset "UTF-8"; | ||
|
|
||
| @layer foo, bar, baz; | ||
|
|
||
| @import 'tailwindcss'; | ||
|
|
||
| .foo { | ||
| color: red; | ||
|
|
||
| &:hover { | ||
| color: blue; | ||
| } | ||
|
|
||
| .bar { | ||
| color: green !important; | ||
| background-color: yellow; | ||
|
|
||
| @media (min-width: 640px) { | ||
| color: orange; | ||
| } | ||
| } | ||
| } | ||
| ` | ||
|
|
||
| let ast = postcss.parse(input) | ||
| let transformedAst = postCssAstToCssAst(ast) | ||
|
|
||
| expect(toCss(transformedAst)).toMatchInlineSnapshot(` | ||
| "@charset "UTF-8"; | ||
| @layer foo, bar, baz; | ||
| @import 'tailwindcss'; | ||
| .foo { | ||
| color: red; | ||
| &:hover { | ||
| color: blue; | ||
| } | ||
| .bar { | ||
| color: green !important; | ||
| background-color: yellow; | ||
| @media (min-width: 640px) { | ||
| color: orange; | ||
| } | ||
| } | ||
| } | ||
| " | ||
| `) | ||
| }) | ||
|
|
||
| it('should convert a Tailwind CSS AST into a PostCSS AST', () => { | ||
| let input = css` | ||
| @charset "UTF-8"; | ||
|
|
||
| @layer foo, bar, baz; | ||
|
|
||
| @import 'tailwindcss'; | ||
|
|
||
| .foo { | ||
| color: red; | ||
|
|
||
| &:hover { | ||
| color: blue; | ||
| } | ||
|
|
||
| .bar { | ||
| color: green !important; | ||
| background-color: yellow; | ||
|
|
||
| @media (min-width: 640px) { | ||
| color: orange; | ||
| } | ||
| } | ||
| } | ||
| ` | ||
|
|
||
| let ast = parse(input) | ||
| let transformedAst = cssAstToPostCssAst(ast) | ||
|
|
||
| expect(transformedAst.toString()).toMatchInlineSnapshot(` | ||
| "@charset "UTF-8"; | ||
| @layer foo, bar, baz; | ||
| @import 'tailwindcss'; | ||
| .foo { | ||
| color: red; | ||
| &:hover { | ||
| color: blue | ||
| } | ||
| .bar { | ||
| color: green !important; | ||
| background-color: yellow; | ||
| @media (min-width: 640px) { | ||
| color: orange | ||
| } | ||
| } | ||
| }" | ||
| `) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| import postcss, { | ||
| type ChildNode as PostCssChildNode, | ||
| type Container as PostCssContainerNode, | ||
| type Root as PostCssRoot, | ||
| type Source as PostcssSource, | ||
| } from 'postcss' | ||
| import { atRule, comment, decl, rule, type AstNode } from '../../tailwindcss/src/ast' | ||
|
|
||
| export function cssAstToPostCssAst(ast: AstNode[], source: PostcssSource | undefined): PostCssRoot { | ||
| let root = postcss.root() | ||
| root.source = source | ||
|
|
||
| function transform(node: AstNode, parent: PostCssContainerNode) { | ||
| // Declaration | ||
| if (node.kind === 'declaration') { | ||
| let astNode = postcss.decl({ | ||
| prop: node.property, | ||
| value: node.value ?? '', | ||
| important: node.important, | ||
| }) | ||
| astNode.source = source | ||
| parent.append(astNode) | ||
| } | ||
|
|
||
| // Rule | ||
| else if (node.kind === 'rule') { | ||
| let astNode = postcss.rule({ selector: node.selector }) | ||
| astNode.source = source | ||
| parent.append(astNode) | ||
| for (let child of node.nodes) { | ||
| transform(child, astNode) | ||
| } | ||
| } | ||
|
|
||
| // AtRule | ||
| else if (node.kind === 'at-rule') { | ||
| let astNode = postcss.atRule({ name: node.name.slice(1), params: node.params }) | ||
| astNode.source = source | ||
| parent.append(astNode) | ||
| for (let child of node.nodes) { | ||
| transform(child, astNode) | ||
| } | ||
| } | ||
|
|
||
| // Comment | ||
| else if (node.kind === 'comment') { | ||
| let astNode = postcss.comment({ text: node.value }) | ||
| astNode.source = source | ||
| parent.append(astNode) | ||
| } | ||
|
|
||
| // AtRoot & Context should not happen | ||
| else if (node.kind === 'at-root' || node.kind === 'context') { | ||
| } | ||
|
|
||
| // Unknown | ||
| else { | ||
| node satisfies never | ||
| } | ||
| } | ||
|
|
||
| for (let node of ast) { | ||
| transform(node, root) | ||
| } | ||
|
|
||
| return root | ||
| } | ||
|
|
||
| export function postCssAstToCssAst(root: PostCssRoot): AstNode[] { | ||
| function transform( | ||
| node: PostCssChildNode, | ||
| parent: Extract<AstNode, { nodes: AstNode[] }>['nodes'], | ||
| ) { | ||
| // Declaration | ||
| if (node.type === 'decl') { | ||
| parent.push(decl(node.prop, node.value, node.important)) | ||
| } | ||
|
|
||
| // Rule | ||
| else if (node.type === 'rule') { | ||
| let astNode = rule(node.selector) | ||
| node.each((child) => transform(child, astNode.nodes)) | ||
| parent.push(astNode) | ||
| } | ||
|
|
||
| // AtRule | ||
| else if (node.type === 'atrule') { | ||
| let astNode = atRule(`@${node.name}`, node.params) | ||
| node.each((child) => transform(child, astNode.nodes)) | ||
| parent.push(astNode) | ||
| } | ||
|
|
||
| // Comment | ||
| else if (node.type === 'comment') { | ||
| parent.push(comment(node.text)) | ||
| } | ||
|
|
||
| // Unknown | ||
| else { | ||
| node satisfies never | ||
| } | ||
| } | ||
|
|
||
| let ast: AstNode[] = [] | ||
| root.each((node) => transform(node, ast)) | ||
|
|
||
| return ast | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.