Skip to content

Commit 11b511e

Browse files
committed
chore(deps): upgrade
1 parent 2201e07 commit 11b511e

File tree

11 files changed

+889
-15
lines changed

11 files changed

+889
-15
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
"rimraf": "^6.0.1",
9797
"rollup": "^4.32.0",
9898
"set-value": "^4.1.0",
99-
"tailwindcss": "^4.0.0",
99+
"tailwindcss": "^3.4.17",
100100
"tailwindcss-patch": "workspace:*",
101101
"tailwindcss2": "npm:@tailwindcss/postcss7-compat@^2.2.17",
102102
"tslib": "^2.8.1",

packages/tailwindcss-patch/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@
9090
"@tailwindcss/oxide": "^4.0.0",
9191
"@tailwindcss/postcss": "^4.0.0",
9292
"@tailwindcss/vite": "^4.0.0",
93-
"tailwindcss": "^4.0.0",
94-
"tailwindcss-3": "npm:tailwindcss@^3"
93+
"tailwindcss": "^4",
94+
"tailwindcss-3": "npm:tailwindcss@^3",
95+
"tailwindcss-4": "npm:tailwindcss@^4"
9596
}
9697
}

packages/tailwindcss-patch/src/core/postcss.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ export interface ProcessTailwindcssOptions {
1212
cwd?: string
1313
config?: string
1414
majorVersion?: number
15+
postcssPlugin?: string
1516
}
1617

1718
export async function processTailwindcss(options: ProcessTailwindcssOptions) {
18-
const { config: userConfig, cwd, majorVersion } = defu<ProcessTailwindcssOptions, ProcessTailwindcssOptions[]>(options, {
19+
const { config: userConfig, cwd, majorVersion, postcssPlugin } = defu<ProcessTailwindcssOptions, ProcessTailwindcssOptions[]>(options, {
1920
cwd: process.cwd(),
2021
majorVersion: 3,
2122
})
@@ -48,10 +49,10 @@ export async function processTailwindcss(options: ProcessTailwindcssOptions) {
4849
}
4950
config = result.filepath
5051
}
51-
52+
const targetPostcssPlugin = postcssPlugin ?? (majorVersion === 4 ? '@tailwindcss/postcss' : 'tailwindcss')
5253
if (majorVersion === 4) {
5354
return await postcss([
54-
require('@tailwindcss/postcss')({
55+
require(targetPostcssPlugin)({
5556
config,
5657
}),
5758
]).process('@import \'tailwindcss\';', {
@@ -60,7 +61,7 @@ export async function processTailwindcss(options: ProcessTailwindcssOptions) {
6061
}
6162

6263
return await postcss([
63-
require('tailwindcss')({
64+
require(targetPostcssPlugin)({
6465
config,
6566
}),
6667
]).process('@tailwind base;@tailwind components;@tailwind utilities;', {
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
import type { Candidate, Variant } from '../../../tailwindcss/src/candidate'
2+
import type { DesignSystem } from '../../../tailwindcss/src/design-system'
3+
import { Scanner } from '@tailwindcss/oxide'
4+
import * as ValueParser from './src/value-parser'
5+
6+
export async function extractRawCandidates(
7+
content: string,
8+
extension: string = 'html',
9+
): Promise<{ rawCandidate: string, start: number, end: number }[]> {
10+
const scanner = new Scanner({})
11+
const result = scanner.getCandidatesWithPositions({ content, extension })
12+
13+
const candidates: { rawCandidate: string, start: number, end: number }[] = []
14+
for (const { candidate: rawCandidate, position: start } of result) {
15+
candidates.push({ rawCandidate, start, end: start + rawCandidate.length })
16+
}
17+
return candidates
18+
}
19+
20+
export function printCandidate(designSystem: DesignSystem, candidate: Candidate) {
21+
const parts: string[] = []
22+
23+
for (const variant of candidate.variants) {
24+
parts.unshift(printVariant(variant))
25+
}
26+
27+
// Handle prefix
28+
if (designSystem.theme.prefix) {
29+
parts.unshift(designSystem.theme.prefix)
30+
}
31+
32+
let base: string = ''
33+
34+
// Handle static
35+
if (candidate.kind === 'static') {
36+
base += candidate.root
37+
}
38+
39+
// Handle functional
40+
if (candidate.kind === 'functional') {
41+
base += candidate.root
42+
43+
if (candidate.value) {
44+
if (candidate.value.kind === 'arbitrary') {
45+
if (candidate.value !== null) {
46+
const isVarValue = isVar(candidate.value.value)
47+
const value = isVarValue ? candidate.value.value.slice(4, -1) : candidate.value.value
48+
const [open, close] = isVarValue ? ['(', ')'] : ['[', ']']
49+
50+
if (candidate.value.dataType) {
51+
base += `-${open}${candidate.value.dataType}:${printArbitraryValue(value)}${close}`
52+
}
53+
else {
54+
base += `-${open}${printArbitraryValue(value)}${close}`
55+
}
56+
}
57+
}
58+
else if (candidate.value.kind === 'named') {
59+
base += `-${candidate.value.value}`
60+
}
61+
}
62+
}
63+
64+
// Handle arbitrary
65+
if (candidate.kind === 'arbitrary') {
66+
base += `[${candidate.property}:${printArbitraryValue(candidate.value)}]`
67+
}
68+
69+
// Handle modifier
70+
if (candidate.kind === 'arbitrary' || candidate.kind === 'functional') {
71+
if (candidate.modifier) {
72+
const isVarValue = isVar(candidate.modifier.value)
73+
const value = isVarValue ? candidate.modifier.value.slice(4, -1) : candidate.modifier.value
74+
const [open, close] = isVarValue ? ['(', ')'] : ['[', ']']
75+
76+
if (candidate.modifier.kind === 'arbitrary') {
77+
base += `/${open}${printArbitraryValue(value)}${close}`
78+
}
79+
else if (candidate.modifier.kind === 'named') {
80+
base += `/${candidate.modifier.value}`
81+
}
82+
}
83+
}
84+
85+
// Handle important
86+
if (candidate.important) {
87+
base += '!'
88+
}
89+
90+
parts.push(base)
91+
92+
return parts.join(':')
93+
}
94+
95+
function printVariant(variant: Variant) {
96+
// Handle static variants
97+
if (variant.kind === 'static') {
98+
return variant.root
99+
}
100+
101+
// Handle arbitrary variants
102+
if (variant.kind === 'arbitrary') {
103+
return `[${printArbitraryValue(simplifyArbitraryVariant(variant.selector))}]`
104+
}
105+
106+
let base: string = ''
107+
108+
// Handle functional variants
109+
if (variant.kind === 'functional') {
110+
base += variant.root
111+
if (variant.value) {
112+
if (variant.value.kind === 'arbitrary') {
113+
const isVarValue = isVar(variant.value.value)
114+
const value = isVarValue ? variant.value.value.slice(4, -1) : variant.value.value
115+
const [open, close] = isVarValue ? ['(', ')'] : ['[', ']']
116+
117+
base += `-${open}${printArbitraryValue(value)}${close}`
118+
}
119+
else if (variant.value.kind === 'named') {
120+
base += `-${variant.value.value}`
121+
}
122+
}
123+
}
124+
125+
// Handle compound variants
126+
if (variant.kind === 'compound') {
127+
base += variant.root
128+
base += '-'
129+
base += printVariant(variant.variant)
130+
}
131+
132+
// Handle modifiers
133+
if (variant.kind === 'functional' || variant.kind === 'compound') {
134+
if (variant.modifier) {
135+
if (variant.modifier.kind === 'arbitrary') {
136+
base += `/[${printArbitraryValue(variant.modifier.value)}]`
137+
}
138+
else if (variant.modifier.kind === 'named') {
139+
base += `/${variant.modifier.value}`
140+
}
141+
}
142+
}
143+
144+
return base
145+
}
146+
147+
function printArbitraryValue(input: string) {
148+
const ast = ValueParser.parse(input)
149+
150+
const drop = new Set<ValueParser.ValueAstNode>()
151+
152+
ValueParser.walk(ast, (node, { parent }) => {
153+
const parentArray = parent === null ? ast : (parent.nodes ?? [])
154+
155+
// Handle operators (e.g.: inside of `calc(…)`)
156+
if (
157+
node.kind === 'word'
158+
// Operators
159+
&& (node.value === '+' || node.value === '-' || node.value === '*' || node.value === '/')
160+
) {
161+
const idx = parentArray.indexOf(node) ?? -1
162+
163+
// This should not be possible
164+
if (idx === -1) { return }
165+
166+
const previous = parentArray[idx - 1]
167+
if (previous?.kind !== 'separator' || previous.value !== ' ') { return }
168+
169+
const next = parentArray[idx + 1]
170+
if (next?.kind !== 'separator' || next.value !== ' ') { return }
171+
172+
drop.add(previous)
173+
drop.add(next)
174+
}
175+
176+
// The value parser handles `/` as a separator in some scenarios. E.g.:
177+
// `theme(colors.red/50%)`. Because of this, we have to handle this case
178+
// separately.
179+
else if (node.kind === 'separator' && node.value.trim() === '/') {
180+
node.value = '/'
181+
}
182+
183+
// Leading and trailing whitespace
184+
else if (node.kind === 'separator' && node.value.length > 0 && node.value.trim() === '') {
185+
if (parentArray[0] === node || parentArray[parentArray.length - 1] === node) {
186+
drop.add(node)
187+
}
188+
}
189+
190+
// Whitespace around `,` separators can be removed.
191+
// E.g.: `min(1px , 2px)` -> `min(1px,2px)`
192+
else if (node.kind === 'separator' && node.value.trim() === ',') {
193+
node.value = ','
194+
}
195+
})
196+
197+
if (drop.size > 0) {
198+
ValueParser.walk(ast, (node, { replaceWith }) => {
199+
if (drop.has(node)) {
200+
drop.delete(node)
201+
replaceWith([])
202+
}
203+
})
204+
}
205+
206+
recursivelyEscapeUnderscores(ast)
207+
208+
return ValueParser.toCss(ast)
209+
}
210+
211+
function simplifyArbitraryVariant(input: string) {
212+
const ast = ValueParser.parse(input)
213+
214+
// &:is(…)
215+
if (
216+
ast.length === 3
217+
// &
218+
&& ast[0].kind === 'word'
219+
&& ast[0].value === '&'
220+
// :
221+
&& ast[1].kind === 'separator'
222+
&& ast[1].value === ':'
223+
// is(…)
224+
&& ast[2].kind === 'function'
225+
&& ast[2].value === 'is'
226+
) {
227+
return ValueParser.toCss(ast[2].nodes)
228+
}
229+
230+
return input
231+
}
232+
233+
function recursivelyEscapeUnderscores(ast: ValueParser.ValueAstNode[]) {
234+
for (const node of ast) {
235+
switch (node.kind) {
236+
case 'function': {
237+
if (node.value === 'url' || node.value.endsWith('_url')) {
238+
// Don't decode underscores in url() but do decode the function name
239+
node.value = escapeUnderscore(node.value)
240+
break
241+
}
242+
243+
if (
244+
node.value === 'var'
245+
|| node.value.endsWith('_var')
246+
|| node.value === 'theme'
247+
|| node.value.endsWith('_theme')
248+
) {
249+
// Don't decode underscores in the first argument of var() and theme()
250+
// but do decode the function name
251+
node.value = escapeUnderscore(node.value)
252+
for (let i = 0; i < node.nodes.length; i++) {
253+
if (i == 0 && node.nodes[i].kind === 'word') {
254+
continue
255+
}
256+
recursivelyEscapeUnderscores([node.nodes[i]])
257+
}
258+
break
259+
}
260+
261+
node.value = escapeUnderscore(node.value)
262+
recursivelyEscapeUnderscores(node.nodes)
263+
break
264+
}
265+
case 'separator':
266+
node.value = escapeUnderscore(node.value)
267+
break
268+
case 'word': {
269+
// Dashed idents and variables `var(--my-var)` and `--my-var` should not
270+
// have underscores escaped
271+
if (node.value[0] !== '-' && node.value[1] !== '-') {
272+
node.value = escapeUnderscore(node.value)
273+
}
274+
break
275+
}
276+
default:
277+
never(node)
278+
}
279+
}
280+
}
281+
282+
function isVar(value: string) {
283+
const ast = ValueParser.parse(value)
284+
return ast.length === 1 && ast[0].kind === 'function' && ast[0].value === 'var'
285+
}
286+
287+
function never(value: never): never {
288+
throw new Error(`Unexpected value: ${value}`)
289+
}
290+
291+
function escapeUnderscore(value: string): string {
292+
return value
293+
.replaceAll('_', String.raw`\_`) // Escape underscores to keep them as-is
294+
.replaceAll(' ', '_') // Replace spaces with underscores
295+
}

packages/tailwindcss-patch/test/postcss8-v3.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ describe('postcss', () => {
1313
const twPatcher = new TailwindcssPatcher()
1414
const res = await processTailwindcss({
1515
cwd: p,
16+
postcssPlugin: 'tailwindcss-3',
1617
})
1718
expect(res.css).toMatchSnapshot()
1819
const res0 = twPatcher.getContexts()

0 commit comments

Comments
 (0)