Skip to content

Commit 1a9abfe

Browse files
committed
add cloneCandidate and cloneVariant helpers
These are much faster compared to `structuredClone`. This is again because we're dealing with simple plain objects.
1 parent 367f755 commit 1a9abfe

File tree

3 files changed

+85
-9
lines changed

3 files changed

+85
-9
lines changed
Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,40 @@
11
import { Scanner } from '@tailwindcss/oxide'
2-
import { bench } from 'vitest'
3-
import { parseCandidate } from './candidate'
2+
import { bench, describe } from 'vitest'
3+
import { cloneCandidate, parseCandidate } from './candidate'
44
import { buildDesignSystem } from './design-system'
55
import { Theme } from './theme'
66

77
// FOLDER=path/to/folder vitest bench
88
const root = process.env.FOLDER || process.cwd()
99

1010
// Auto content detection
11-
const scanner = new Scanner({ sources: [{ base: root, pattern: '**/*' }] })
11+
const scanner = new Scanner({ sources: [{ base: root, pattern: '**/*', negated: false }] })
1212

1313
const candidates = scanner.scan()
1414
const designSystem = buildDesignSystem(new Theme())
1515

16-
bench('parseCandidate', () => {
17-
for (let candidate of candidates) {
18-
Array.from(parseCandidate(candidate, designSystem))
19-
}
16+
describe('parsing', () => {
17+
bench('parseCandidate', () => {
18+
for (let candidate of candidates) {
19+
Array.from(parseCandidate(candidate, designSystem))
20+
}
21+
})
22+
})
23+
24+
describe('Candidate cloning', async () => {
25+
let parsedCanddiates = candidates.flatMap((candidate) =>
26+
Array.from(parseCandidate(candidate, designSystem)),
27+
)
28+
29+
bench('structuredClone', () => {
30+
for (let candidate of parsedCanddiates) {
31+
structuredClone(candidate)
32+
}
33+
})
34+
35+
bench('cloneCandidate', () => {
36+
for (let candidate of parsedCanddiates) {
37+
cloneCandidate(candidate)
38+
}
39+
})
2040
})

packages/tailwindcss/src/candidate.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,60 @@ export type Candidate =
215215
raw: string
216216
}
217217

218+
export function cloneCandidate(candidate: Candidate): Candidate {
219+
switch (candidate.kind) {
220+
case 'arbitrary':
221+
return {
222+
...candidate,
223+
variants: candidate.variants.map(cloneVariant),
224+
modifier: candidate.modifier ? { ...candidate.modifier } : null,
225+
}
226+
227+
case 'static':
228+
return { ...candidate, variants: candidate.variants.map(cloneVariant) }
229+
230+
case 'functional':
231+
return {
232+
...candidate,
233+
variants: candidate.variants.map(cloneVariant),
234+
value: candidate.value ? { ...candidate.value } : null,
235+
modifier: candidate.modifier ? { ...candidate.modifier } : null,
236+
}
237+
238+
default:
239+
candidate satisfies never
240+
throw new Error('Unknown candidate kind')
241+
}
242+
}
243+
244+
export function cloneVariant(variant: Variant): Variant {
245+
switch (variant.kind) {
246+
case 'arbitrary':
247+
return { ...variant }
248+
249+
case 'static':
250+
return { ...variant }
251+
252+
case 'functional':
253+
return {
254+
...variant,
255+
value: variant.value ? { ...variant.value } : null,
256+
modifier: variant.modifier ? { ...variant.modifier } : null,
257+
}
258+
259+
case 'compound':
260+
return {
261+
...variant,
262+
variant: cloneVariant(variant.variant),
263+
modifier: variant.modifier ? { ...variant.modifier } : null,
264+
}
265+
266+
default:
267+
variant satisfies never
268+
throw new Error('Unknown variant kind')
269+
}
270+
}
271+
218272
export function* parseCandidate(input: string, designSystem: DesignSystem): Iterable<Candidate> {
219273
// hover:focus:underline
220274
// ^^^^^ ^^^^^^ -> Variants

packages/tailwindcss/src/canonicalize-candidates.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as AttributeSelectorParser from './attribute-selector-parser'
22
import {
3+
cloneCandidate,
4+
cloneVariant,
35
printModifier,
46
type Candidate,
57
type CandidateModifier,
@@ -107,7 +109,7 @@ const canonicalizeVariantCache = new DefaultMap((ds: DesignSystem) => {
107109
for (let current of replacement.splice(0)) {
108110
// A single variant can result in multiple variants, e.g.:
109111
// `[&>[data-selected]]:flex` → `*:data-selected:flex`
110-
let result = fn(ds, structuredClone(current))
112+
let result = fn(ds, cloneVariant(current))
111113
if (Array.isArray(result)) {
112114
replacement.push(...result)
113115
continue
@@ -134,7 +136,7 @@ const UTILITY_CANONICALIZATIONS = [
134136
const canonicalizeUtilityCache = new DefaultMap((ds: DesignSystem) => {
135137
return new DefaultMap((rawCandidate: string): string => {
136138
for (let readonlyCandidate of ds.parseCandidate(rawCandidate)) {
137-
let replacement = structuredClone(readonlyCandidate) as Writable<typeof readonlyCandidate>
139+
let replacement = cloneCandidate(readonlyCandidate) as Writable<typeof readonlyCandidate>
138140

139141
for (let fn of UTILITY_CANONICALIZATIONS) {
140142
replacement = fn(ds, replacement)

0 commit comments

Comments
 (0)