tailwindcss-patch 9.4.3 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{cli-BztQHMRp.js → cli-CGyUnvFc.js} +3 -2
- package/dist/{cli-D0jXMGXf.mjs → cli-DRfALTSo.mjs} +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.mjs +2 -2
- package/dist/commands/cli-runtime.d.mts +1 -1
- package/dist/commands/cli-runtime.d.ts +1 -1
- package/dist/commands/cli-runtime.js +2 -2
- package/dist/commands/cli-runtime.mjs +2 -2
- package/dist/{dist-DDcbvOwe.js → dist-DlC5vuI2.js} +1 -1
- package/dist/index.d.mts +7 -149
- package/dist/index.d.ts +8 -150
- package/dist/index.js +294 -521
- package/dist/index.mjs +6 -471
- package/dist/{validate-BuqRodYI.d.ts → validate-B5-08lrU.d.ts} +7 -251
- package/dist/{validate-oAkURzUC.d.mts → validate-CgrG4aAY.d.mts} +7 -251
- package/dist/{validate-Bug_WYcU.mjs → validate-Q00Ccqht.mjs} +8 -1405
- package/dist/{validate-DbuKewV-.js → validate-XiYmTZcd.js} +82 -1708
- package/package.json +8 -10
- package/src/api/tailwindcss-patcher.ts +8 -5
- package/src/extraction/candidate-extractor.ts +17 -701
- package/src/extraction/split-candidate-tokens.ts +5 -101
- package/src/options/types.ts +1 -2
- package/src/style-candidates.ts +5 -35
- package/src/style-generator.ts +11 -80
- package/src/types.ts +21 -95
- package/src/v3/index.ts +2 -2
- package/src/v4/index.ts +104 -28
- package/src/v3/style-generator.ts +0 -384
- package/src/v4/bare-arbitrary-values.ts +0 -545
- package/src/v4/candidates.ts +0 -316
- package/src/v4/engine.ts +0 -112
- package/src/v4/node-adapter.ts +0 -207
- package/src/v4/source-scan.ts +0 -432
- package/src/v4/source.ts +0 -235
- package/src/v4/style-generator.ts +0 -44
- package/src/v4/types.ts +0 -103
package/src/v4/candidates.ts
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
import type { BareArbitraryValueOptions } from './bare-arbitrary-values'
|
|
2
|
-
import type { TailwindV4DesignSystem } from './types'
|
|
3
|
-
import postcss from 'postcss'
|
|
4
|
-
import { escapeCssClassName, resolveBareArbitraryValueCandidate } from './bare-arbitrary-values'
|
|
5
|
-
|
|
6
|
-
export function resolveValidTailwindV4Candidates(
|
|
7
|
-
designSystem: TailwindV4DesignSystem,
|
|
8
|
-
candidates: Iterable<string>,
|
|
9
|
-
options?: {
|
|
10
|
-
bareArbitraryValues?: boolean | BareArbitraryValueOptions
|
|
11
|
-
},
|
|
12
|
-
): Set<string> {
|
|
13
|
-
const validCandidates = new Set<string>()
|
|
14
|
-
const parsedCandidates: string[] = []
|
|
15
|
-
const originalCandidatesByCanonical = new Map<string, Set<string>>()
|
|
16
|
-
|
|
17
|
-
for (const candidate of candidates) {
|
|
18
|
-
if (!candidate) {
|
|
19
|
-
continue
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const bareArbitrary = resolveBareArbitraryValueCandidate(candidate, options?.bareArbitraryValues)
|
|
23
|
-
const candidateToCheck = bareArbitrary?.canonicalCandidate ?? candidate
|
|
24
|
-
|
|
25
|
-
if (bareArbitrary) {
|
|
26
|
-
const originalCandidates = originalCandidatesByCanonical.get(candidateToCheck) ?? new Set<string>()
|
|
27
|
-
originalCandidates.add(candidate)
|
|
28
|
-
originalCandidatesByCanonical.set(candidateToCheck, originalCandidates)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const alreadyParsed = parsedCandidates.includes(candidateToCheck)
|
|
32
|
-
if (alreadyParsed) {
|
|
33
|
-
continue
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (designSystem.parseCandidate(candidateToCheck).length > 0) {
|
|
37
|
-
parsedCandidates.push(candidateToCheck)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (parsedCandidates.length === 0) {
|
|
42
|
-
return validCandidates
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const cssByCandidate = designSystem.candidatesToCss(parsedCandidates)
|
|
46
|
-
for (let index = 0; index < parsedCandidates.length; index++) {
|
|
47
|
-
const candidate = parsedCandidates[index]
|
|
48
|
-
const candidateCss = cssByCandidate[index]
|
|
49
|
-
if (candidate && typeof candidateCss === 'string' && candidateCss.trim().length > 0) {
|
|
50
|
-
const originalCandidates = originalCandidatesByCanonical.get(candidate)
|
|
51
|
-
if (originalCandidates) {
|
|
52
|
-
for (const originalCandidate of originalCandidates) {
|
|
53
|
-
validCandidates.add(originalCandidate)
|
|
54
|
-
}
|
|
55
|
-
continue
|
|
56
|
-
}
|
|
57
|
-
validCandidates.add(candidate)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return validCandidates
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function createSelectorAliasMap(
|
|
65
|
-
candidates: Iterable<string>,
|
|
66
|
-
options?: boolean | BareArbitraryValueOptions,
|
|
67
|
-
) {
|
|
68
|
-
const aliases = new Map<string, Set<string>>()
|
|
69
|
-
for (const candidate of candidates) {
|
|
70
|
-
const bareArbitrary = resolveBareArbitraryValueCandidate(candidate, options)
|
|
71
|
-
if (!bareArbitrary) {
|
|
72
|
-
continue
|
|
73
|
-
}
|
|
74
|
-
const canonicalSelector = escapeCssClassName(bareArbitrary.canonicalCandidate)
|
|
75
|
-
const bareSelectors = aliases.get(canonicalSelector) ?? new Set<string>()
|
|
76
|
-
bareSelectors.add(escapeCssClassName(bareArbitrary.candidate))
|
|
77
|
-
aliases.set(canonicalSelector, bareSelectors)
|
|
78
|
-
}
|
|
79
|
-
return aliases
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function replaceBareArbitraryValueSelectors(
|
|
83
|
-
css: string,
|
|
84
|
-
candidates: Iterable<string>,
|
|
85
|
-
options?: boolean | BareArbitraryValueOptions,
|
|
86
|
-
) {
|
|
87
|
-
const aliases = createSelectorAliasMap(candidates, options)
|
|
88
|
-
if (aliases.size === 0) {
|
|
89
|
-
return css
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (Array.from(aliases.values()).every(bareSelectors => bareSelectors.size === 1)) {
|
|
93
|
-
let result = css
|
|
94
|
-
for (const [canonicalSelector, bareSelectors] of aliases) {
|
|
95
|
-
const bareSelector = Array.from(bareSelectors)[0]
|
|
96
|
-
if (bareSelector !== undefined) {
|
|
97
|
-
result = result.replaceAll(canonicalSelector, bareSelector)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return result
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const root = postcss.parse(css)
|
|
104
|
-
root.walkRules((rule) => {
|
|
105
|
-
let selectors = rule.selectors
|
|
106
|
-
for (const [canonicalSelector, bareSelectors] of aliases) {
|
|
107
|
-
selectors = selectors.flatMap((selector) => {
|
|
108
|
-
if (!selector.includes(canonicalSelector)) {
|
|
109
|
-
return selector
|
|
110
|
-
}
|
|
111
|
-
return Array.from(bareSelectors, bareSelector => selector.replaceAll(canonicalSelector, bareSelector))
|
|
112
|
-
})
|
|
113
|
-
}
|
|
114
|
-
rule.selectors = selectors
|
|
115
|
-
})
|
|
116
|
-
return root.toString()
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function canonicalizeBareArbitraryValueCandidates(
|
|
120
|
-
candidates: Iterable<string>,
|
|
121
|
-
options?: boolean | BareArbitraryValueOptions,
|
|
122
|
-
) {
|
|
123
|
-
return Array.from(candidates, (candidate) => {
|
|
124
|
-
const bareArbitrary = resolveBareArbitraryValueCandidate(candidate, options)
|
|
125
|
-
return bareArbitrary?.canonicalCandidate ?? candidate
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function splitTopLevel(value: string, separator: string, options?: { keepEmpty?: boolean }) {
|
|
130
|
-
const result: string[] = []
|
|
131
|
-
let start = 0
|
|
132
|
-
let depth = 0
|
|
133
|
-
let quote: string | undefined
|
|
134
|
-
|
|
135
|
-
for (let index = 0; index < value.length; index++) {
|
|
136
|
-
const character = value[index]
|
|
137
|
-
if (character === '\\') {
|
|
138
|
-
index++
|
|
139
|
-
continue
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (quote) {
|
|
143
|
-
if (character === quote) {
|
|
144
|
-
quote = undefined
|
|
145
|
-
}
|
|
146
|
-
continue
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (character === '"' || character === '\'') {
|
|
150
|
-
quote = character
|
|
151
|
-
continue
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (character === '(' || character === '[' || character === '{') {
|
|
155
|
-
depth++
|
|
156
|
-
continue
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (character === ')' || character === ']' || character === '}') {
|
|
160
|
-
depth = Math.max(0, depth - 1)
|
|
161
|
-
continue
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (depth === 0 && character === separator) {
|
|
165
|
-
const item = value.slice(start, index).trim()
|
|
166
|
-
if (item || options?.keepEmpty) {
|
|
167
|
-
result.push(item)
|
|
168
|
-
}
|
|
169
|
-
start = index + 1
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const item = value.slice(start).trim()
|
|
174
|
-
if (item || options?.keepEmpty) {
|
|
175
|
-
result.push(item)
|
|
176
|
-
}
|
|
177
|
-
return result
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const sequencePattern = /^(-?\d+)\.\.(-?\d+)(?:\.\.(-?\d+))?$/
|
|
181
|
-
|
|
182
|
-
function expandSequence(value: string) {
|
|
183
|
-
const match = value.match(sequencePattern)
|
|
184
|
-
if (!match) {
|
|
185
|
-
return [value]
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const [, startValue, endValue, stepValue] = match
|
|
189
|
-
if (startValue === undefined || endValue === undefined) {
|
|
190
|
-
return [value]
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const start = Number.parseInt(startValue, 10)
|
|
194
|
-
const end = Number.parseInt(endValue, 10)
|
|
195
|
-
let step = stepValue === undefined ? (start <= end ? 1 : -1) : Number.parseInt(stepValue, 10)
|
|
196
|
-
if (step === 0) {
|
|
197
|
-
throw new Error('Step cannot be zero in Tailwind CSS v4 inline source sequence.')
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const ascending = start < end
|
|
201
|
-
if (ascending && step < 0) {
|
|
202
|
-
step = -step
|
|
203
|
-
}
|
|
204
|
-
if (!ascending && step > 0) {
|
|
205
|
-
step = -step
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const result: string[] = []
|
|
209
|
-
for (let current = start; ascending ? current <= end : current >= end; current += step) {
|
|
210
|
-
result.push(current.toString())
|
|
211
|
-
}
|
|
212
|
-
return result
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function expandInlinePattern(pattern: string): string[] {
|
|
216
|
-
const openIndex = pattern.indexOf('{')
|
|
217
|
-
if (openIndex === -1) {
|
|
218
|
-
return [pattern]
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const prefix = pattern.slice(0, openIndex)
|
|
222
|
-
const rest = pattern.slice(openIndex)
|
|
223
|
-
let depth = 0
|
|
224
|
-
let closeIndex = -1
|
|
225
|
-
for (let index = 0; index < rest.length; index++) {
|
|
226
|
-
const character = rest[index]
|
|
227
|
-
if (character === '{') {
|
|
228
|
-
depth++
|
|
229
|
-
}
|
|
230
|
-
else if (character === '}') {
|
|
231
|
-
depth--
|
|
232
|
-
if (depth === 0) {
|
|
233
|
-
closeIndex = index
|
|
234
|
-
break
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (closeIndex === -1) {
|
|
240
|
-
throw new Error(`The Tailwind CSS v4 inline source pattern "${pattern}" is not balanced.`)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const body = rest.slice(1, closeIndex)
|
|
244
|
-
const suffix = rest.slice(closeIndex + 1)
|
|
245
|
-
const parts = sequencePattern.test(body)
|
|
246
|
-
? expandSequence(body)
|
|
247
|
-
: splitTopLevel(body, ',', { keepEmpty: true }).flatMap(part => expandInlinePattern(part))
|
|
248
|
-
const suffixes = expandInlinePattern(suffix)
|
|
249
|
-
|
|
250
|
-
const result: string[] = []
|
|
251
|
-
for (const part of parts) {
|
|
252
|
-
for (const expandedSuffix of suffixes) {
|
|
253
|
-
result.push(`${prefix}${part}${expandedSuffix}`)
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return result
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function unquoteCssString(value: string) {
|
|
260
|
-
const quote = value[0]
|
|
261
|
-
if ((quote !== '"' && quote !== '\'') || value[value.length - 1] !== quote) {
|
|
262
|
-
return undefined
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
let result = ''
|
|
266
|
-
for (let index = 1; index < value.length - 1; index++) {
|
|
267
|
-
const character = value[index]
|
|
268
|
-
if (character === '\\') {
|
|
269
|
-
index++
|
|
270
|
-
result += value[index] ?? ''
|
|
271
|
-
continue
|
|
272
|
-
}
|
|
273
|
-
result += character
|
|
274
|
-
}
|
|
275
|
-
return result
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export function extractTailwindV4InlineSourceCandidates(css: string) {
|
|
279
|
-
const included = new Set<string>()
|
|
280
|
-
const excluded = new Set<string>()
|
|
281
|
-
|
|
282
|
-
const root = postcss.parse(css)
|
|
283
|
-
root.walkAtRules('source', (rule) => {
|
|
284
|
-
let params = rule.params.trim()
|
|
285
|
-
if (!params) {
|
|
286
|
-
return
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
let negated = false
|
|
290
|
-
if (params.startsWith('not ')) {
|
|
291
|
-
negated = true
|
|
292
|
-
params = params.slice(4).trim()
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (!params.startsWith('inline(') || !params.endsWith(')')) {
|
|
296
|
-
return
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const inlineValue = unquoteCssString(params.slice(7, -1).trim())
|
|
300
|
-
if (inlineValue === undefined) {
|
|
301
|
-
return
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const target = negated ? excluded : included
|
|
305
|
-
for (const part of splitTopLevel(inlineValue, ' ')) {
|
|
306
|
-
for (const candidate of expandInlinePattern(part)) {
|
|
307
|
-
target.add(candidate)
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
return {
|
|
313
|
-
included,
|
|
314
|
-
excluded,
|
|
315
|
-
}
|
|
316
|
-
}
|
package/src/v4/engine.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
TailwindV4Engine,
|
|
3
|
-
TailwindV4GenerateOptions,
|
|
4
|
-
TailwindV4GenerateResult,
|
|
5
|
-
TailwindV4ResolvedSource,
|
|
6
|
-
TailwindV4SourcePattern,
|
|
7
|
-
} from './types'
|
|
8
|
-
import { extractRawCandidates, extractRawCandidatesWithPositions } from '../extraction/candidate-extractor'
|
|
9
|
-
import {
|
|
10
|
-
canonicalizeBareArbitraryValueCandidates,
|
|
11
|
-
extractTailwindV4InlineSourceCandidates,
|
|
12
|
-
replaceBareArbitraryValueSelectors,
|
|
13
|
-
resolveValidTailwindV4Candidates,
|
|
14
|
-
} from './candidates'
|
|
15
|
-
import { compileTailwindV4Source, loadTailwindV4DesignSystem } from './node-adapter'
|
|
16
|
-
import { createTailwindV4CompiledSourceEntries } from './source-scan'
|
|
17
|
-
|
|
18
|
-
function resolveScanSources(
|
|
19
|
-
options: TailwindV4GenerateOptions | undefined,
|
|
20
|
-
source: TailwindV4ResolvedSource,
|
|
21
|
-
compiledRoot: TailwindV4GenerateResult['root'],
|
|
22
|
-
compiledSources: TailwindV4SourcePattern[],
|
|
23
|
-
) {
|
|
24
|
-
if (Array.isArray(options?.scanSources)) {
|
|
25
|
-
return options.scanSources
|
|
26
|
-
}
|
|
27
|
-
if (options?.scanSources === true) {
|
|
28
|
-
return createTailwindV4CompiledSourceEntries(compiledRoot, compiledSources, source.base)
|
|
29
|
-
}
|
|
30
|
-
return []
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function collectRawCandidates(
|
|
34
|
-
source: TailwindV4ResolvedSource,
|
|
35
|
-
options: TailwindV4GenerateOptions | undefined,
|
|
36
|
-
compiledRoot: TailwindV4GenerateResult['root'],
|
|
37
|
-
compiledSources: TailwindV4SourcePattern[] = [],
|
|
38
|
-
) {
|
|
39
|
-
const rawCandidates = new Set<string>()
|
|
40
|
-
const extractOptions = options?.bareArbitraryValues === undefined
|
|
41
|
-
? undefined
|
|
42
|
-
: { bareArbitraryValues: options.bareArbitraryValues }
|
|
43
|
-
|
|
44
|
-
for (const candidate of options?.candidates ?? []) {
|
|
45
|
-
rawCandidates.add(candidate)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
for (const candidateSource of options?.sources ?? []) {
|
|
49
|
-
const candidates = await extractRawCandidatesWithPositions(candidateSource.content, candidateSource.extension, extractOptions)
|
|
50
|
-
for (const candidate of candidates) {
|
|
51
|
-
rawCandidates.add(candidate.rawCandidate)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const filesystemSources = resolveScanSources(options, source, compiledRoot, compiledSources)
|
|
56
|
-
if (filesystemSources.length > 0) {
|
|
57
|
-
for (const candidate of await extractRawCandidates(filesystemSources, extractOptions)) {
|
|
58
|
-
rawCandidates.add(candidate)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const inlineSources = extractTailwindV4InlineSourceCandidates(source.css)
|
|
63
|
-
for (const candidate of inlineSources.included) {
|
|
64
|
-
rawCandidates.add(candidate)
|
|
65
|
-
}
|
|
66
|
-
for (const candidate of inlineSources.excluded) {
|
|
67
|
-
rawCandidates.delete(candidate)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return rawCandidates
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function createTailwindV4Engine(source: TailwindV4ResolvedSource): TailwindV4Engine {
|
|
74
|
-
return {
|
|
75
|
-
source,
|
|
76
|
-
loadDesignSystem() {
|
|
77
|
-
return loadTailwindV4DesignSystem(source)
|
|
78
|
-
},
|
|
79
|
-
async validateCandidates(candidates) {
|
|
80
|
-
const designSystem = await loadTailwindV4DesignSystem(source)
|
|
81
|
-
return resolveValidTailwindV4Candidates(designSystem, candidates)
|
|
82
|
-
},
|
|
83
|
-
async generate(options): Promise<TailwindV4GenerateResult> {
|
|
84
|
-
const { compiled, dependencies } = await compileTailwindV4Source(source)
|
|
85
|
-
const rawCandidates = await collectRawCandidates(source, options, compiled.root, compiled.sources)
|
|
86
|
-
const designSystem = await loadTailwindV4DesignSystem(source)
|
|
87
|
-
const classSet = resolveValidTailwindV4Candidates(designSystem, rawCandidates, {
|
|
88
|
-
...(options?.bareArbitraryValues === undefined ? {} : { bareArbitraryValues: options.bareArbitraryValues }),
|
|
89
|
-
})
|
|
90
|
-
const inlineSources = extractTailwindV4InlineSourceCandidates(source.css)
|
|
91
|
-
for (const candidate of inlineSources.excluded) {
|
|
92
|
-
classSet.delete(candidate)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const buildCandidates = canonicalizeBareArbitraryValueCandidates(classSet, options?.bareArbitraryValues)
|
|
96
|
-
const css = replaceBareArbitraryValueSelectors(
|
|
97
|
-
compiled.build(buildCandidates),
|
|
98
|
-
classSet,
|
|
99
|
-
options?.bareArbitraryValues,
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
css,
|
|
104
|
-
classSet,
|
|
105
|
-
rawCandidates,
|
|
106
|
-
dependencies: Array.from(dependencies),
|
|
107
|
-
sources: compiled.sources,
|
|
108
|
-
root: compiled.root,
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
}
|
|
112
|
-
}
|
package/src/v4/node-adapter.ts
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
TailwindV4CompiledSourceRoot,
|
|
3
|
-
TailwindV4DesignSystem,
|
|
4
|
-
TailwindV4ResolvedSource,
|
|
5
|
-
TailwindV4SourcePattern,
|
|
6
|
-
} from './types'
|
|
7
|
-
import { createRequire } from 'node:module'
|
|
8
|
-
import { pathToFileURL } from 'node:url'
|
|
9
|
-
import path from 'pathe'
|
|
10
|
-
|
|
11
|
-
interface TailwindV4CompiledSource {
|
|
12
|
-
sources: TailwindV4SourcePattern[]
|
|
13
|
-
root: TailwindV4CompiledSourceRoot
|
|
14
|
-
build: (candidates: string[]) => string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface TailwindV4NodeModule {
|
|
18
|
-
compile: (css: string, options: {
|
|
19
|
-
base: string
|
|
20
|
-
onDependency: (dependency: string) => void
|
|
21
|
-
customCssResolver?: (id: string, base: string) => Promise<string | false | undefined>
|
|
22
|
-
}) => Promise<TailwindV4CompiledSource>
|
|
23
|
-
__unstable__loadDesignSystem: (css: string, options: { base: string }) => Promise<TailwindV4DesignSystem>
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const nodeModulePromiseCache = new Map<string, Promise<TailwindV4NodeModule>>()
|
|
27
|
-
const designSystemPromiseCache = new Map<string, Promise<TailwindV4DesignSystem>>()
|
|
28
|
-
|
|
29
|
-
function unique(values: Iterable<string>) {
|
|
30
|
-
return Array.from(new Set(Array.from(values).filter(Boolean).map(value => path.resolve(value))))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function createRequireBase(base: string) {
|
|
34
|
-
return path.join(base, 'package.json')
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function isRelativeSpecifier(id: string) {
|
|
38
|
-
return id.startsWith('./') || id.startsWith('../') || id === '.' || id === '..'
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function isAbsoluteSpecifier(id: string) {
|
|
42
|
-
return path.isAbsolute(id)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function isCssSpecifier(id: string) {
|
|
46
|
-
return path.extname(id) === '.css'
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function createCssResolutionCandidates(id: string) {
|
|
50
|
-
if (isCssSpecifier(id)) {
|
|
51
|
-
return [id]
|
|
52
|
-
}
|
|
53
|
-
return [`${id}/index.css`, id]
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function createFallbackCssResolver(baseCandidates: string[]) {
|
|
57
|
-
const bases = unique(baseCandidates)
|
|
58
|
-
return async (id: string) => {
|
|
59
|
-
if (isRelativeSpecifier(id) || isAbsoluteSpecifier(id)) {
|
|
60
|
-
return undefined
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
for (const base of bases) {
|
|
64
|
-
const requireFromBase = createRequire(createRequireBase(base))
|
|
65
|
-
for (const candidate of createCssResolutionCandidates(id)) {
|
|
66
|
-
try {
|
|
67
|
-
return requireFromBase.resolve(candidate)
|
|
68
|
-
}
|
|
69
|
-
catch {}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return undefined
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function importResolvedModule(resolved: string): Promise<TailwindV4NodeModule> {
|
|
77
|
-
return import(pathToFileURL(resolved).href) as unknown as Promise<TailwindV4NodeModule>
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function importTailwindNodeFromBase(base: string): Promise<TailwindV4NodeModule | undefined> {
|
|
81
|
-
try {
|
|
82
|
-
const resolved = createRequire(createRequireBase(base)).resolve('@tailwindcss/node')
|
|
83
|
-
return await importResolvedModule(resolved)
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
return undefined
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function importFallbackTailwindNode(): Promise<TailwindV4NodeModule> {
|
|
91
|
-
return import('@tailwindcss/node') as unknown as Promise<TailwindV4NodeModule>
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export async function loadTailwindV4NodeModule(baseCandidates: string[]): Promise<TailwindV4NodeModule> {
|
|
95
|
-
const bases = unique(baseCandidates)
|
|
96
|
-
const cacheKey = JSON.stringify(bases)
|
|
97
|
-
const cached = nodeModulePromiseCache.get(cacheKey)
|
|
98
|
-
if (cached) {
|
|
99
|
-
return cached
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const promise = (async () => {
|
|
103
|
-
for (const base of bases) {
|
|
104
|
-
const loaded = await importTailwindNodeFromBase(base)
|
|
105
|
-
if (loaded) {
|
|
106
|
-
return loaded
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return importFallbackTailwindNode()
|
|
111
|
-
})()
|
|
112
|
-
|
|
113
|
-
nodeModulePromiseCache.set(cacheKey, promise)
|
|
114
|
-
promise.catch(() => {
|
|
115
|
-
if (nodeModulePromiseCache.get(cacheKey) === promise) {
|
|
116
|
-
nodeModulePromiseCache.delete(cacheKey)
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
return promise
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function createDesignSystemCacheKey(css: string, bases: string[]) {
|
|
123
|
-
return JSON.stringify({
|
|
124
|
-
css,
|
|
125
|
-
bases: unique(bases),
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function getTailwindV4DesignSystemCacheKey(source: Pick<TailwindV4ResolvedSource, 'css' | 'base' | 'baseFallbacks'>) {
|
|
130
|
-
return createDesignSystemCacheKey(source.css, [source.base, ...source.baseFallbacks])
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export async function loadTailwindV4DesignSystem(source: TailwindV4ResolvedSource): Promise<TailwindV4DesignSystem> {
|
|
134
|
-
const bases = unique([source.base, ...source.baseFallbacks])
|
|
135
|
-
if (bases.length === 0) {
|
|
136
|
-
throw new Error('No base directories provided for Tailwind CSS v4 design system.')
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const cacheKey = createDesignSystemCacheKey(source.css, bases)
|
|
140
|
-
const cached = designSystemPromiseCache.get(cacheKey)
|
|
141
|
-
if (cached) {
|
|
142
|
-
return cached
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const promise = (async () => {
|
|
146
|
-
const node = await loadTailwindV4NodeModule([source.projectRoot, ...bases])
|
|
147
|
-
let lastError: unknown
|
|
148
|
-
|
|
149
|
-
for (const base of bases) {
|
|
150
|
-
try {
|
|
151
|
-
return await node.__unstable__loadDesignSystem(source.css, { base })
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
lastError = error
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (lastError instanceof Error) {
|
|
159
|
-
throw lastError
|
|
160
|
-
}
|
|
161
|
-
throw new Error('Failed to load Tailwind CSS v4 design system.')
|
|
162
|
-
})()
|
|
163
|
-
|
|
164
|
-
designSystemPromiseCache.set(cacheKey, promise)
|
|
165
|
-
promise.catch(() => {
|
|
166
|
-
if (designSystemPromiseCache.get(cacheKey) === promise) {
|
|
167
|
-
designSystemPromiseCache.delete(cacheKey)
|
|
168
|
-
}
|
|
169
|
-
})
|
|
170
|
-
return promise
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export async function compileTailwindV4Source(source: TailwindV4ResolvedSource) {
|
|
174
|
-
const bases = unique([source.base, ...source.baseFallbacks])
|
|
175
|
-
if (bases.length === 0) {
|
|
176
|
-
throw new Error('No base directories provided for Tailwind CSS v4 compiler.')
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const node = await loadTailwindV4NodeModule([source.projectRoot, ...bases])
|
|
180
|
-
let lastError: unknown
|
|
181
|
-
|
|
182
|
-
for (const base of bases) {
|
|
183
|
-
const dependencies = new Set(source.dependencies)
|
|
184
|
-
try {
|
|
185
|
-
const compiled = await node.compile(source.css, {
|
|
186
|
-
base,
|
|
187
|
-
customCssResolver: createFallbackCssResolver([source.projectRoot, ...bases]),
|
|
188
|
-
onDependency(dependency) {
|
|
189
|
-
dependencies.add(path.resolve(dependency))
|
|
190
|
-
},
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
compiled,
|
|
195
|
-
dependencies,
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
lastError = error
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (lastError instanceof Error) {
|
|
204
|
-
throw lastError
|
|
205
|
-
}
|
|
206
|
-
throw new Error('Failed to compile Tailwind CSS v4 source.')
|
|
207
|
-
}
|