tailwindcss-patch 9.0.0 → 9.1.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/chunk-8l464Juk.js +28 -0
- package/dist/cli.d.mts +1 -2
- package/dist/cli.d.ts +1 -2
- package/dist/cli.js +18 -22
- package/dist/cli.mjs +17 -22
- package/dist/commands/cli-runtime.d.mts +5 -10
- package/dist/commands/cli-runtime.d.ts +5 -10
- package/dist/commands/cli-runtime.js +582 -1217
- package/dist/commands/cli-runtime.mjs +570 -1216
- package/dist/dist-B1VBpHtd.js +21 -0
- package/dist/dist-BjUV1yEM.mjs +19 -0
- package/dist/index.bundle-0Fe7Jx8V.mjs +194 -0
- package/dist/index.bundle-C4Y53Ygf.js +232 -0
- package/dist/index.d.mts +133 -67
- package/dist/index.d.ts +133 -67
- package/dist/index.js +33 -57
- package/dist/index.mjs +3 -57
- package/dist/validate-4FCU-Ql3.mjs +3525 -0
- package/dist/validate-Bu_rkfQF.d.ts +686 -0
- package/dist/validate-BuhhSYBe.js +3700 -0
- package/dist/validate-CIMnzW8O.d.mts +685 -0
- package/package.json +11 -11
- package/src/api/tailwindcss-patcher.ts +33 -6
- package/src/commands/basic-handlers.ts +0 -1
- package/src/config/index.ts +1 -1
- package/src/config/workspace.ts +1 -1
- package/src/extraction/candidate-extractor.ts +14 -69
- package/src/index.bundle.ts +21 -0
- package/src/index.ts +16 -0
- package/src/options/legacy.ts +2 -2
- package/src/options/normalize.ts +10 -5
- package/src/runtime/collector.ts +7 -7
- package/src/runtime/process-tailwindcss.ts +33 -17
- package/src/v4/candidates.ts +224 -0
- package/src/v4/engine.ts +70 -0
- package/src/v4/index.ts +25 -0
- package/src/v4/node-adapter.ts +149 -0
- package/src/v4/source.ts +193 -0
- package/src/v4/types.ts +57 -0
- package/dist/chunk-4RRHMRLJ.js +0 -4378
- package/dist/chunk-AOSPLINO.mjs +0 -4378
- package/dist/chunk-OSH52QWA.mjs +0 -10
- package/dist/chunk-QQXAOMUH.js +0 -25
- package/dist/chunk-YYBY7EM5.mjs +0 -21
- package/dist/chunk-ZPLR2UEW.js +0 -7
- package/dist/dist-22YCNIJW.js +0 -269
- package/dist/dist-7W73GIRD.mjs +0 -269
- package/dist/validate-nbmOI2w8.d.mts +0 -677
- package/dist/validate-nbmOI2w8.d.ts +0 -677
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import type { TailwindV4DesignSystem } from './types'
|
|
2
|
+
import postcss from 'postcss'
|
|
3
|
+
|
|
4
|
+
export function resolveValidTailwindV4Candidates(
|
|
5
|
+
designSystem: TailwindV4DesignSystem,
|
|
6
|
+
candidates: Iterable<string>,
|
|
7
|
+
): Set<string> {
|
|
8
|
+
const validCandidates = new Set<string>()
|
|
9
|
+
const parsedCandidates: string[] = []
|
|
10
|
+
|
|
11
|
+
for (const candidate of candidates) {
|
|
12
|
+
if (!candidate || parsedCandidates.includes(candidate)) {
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (designSystem.parseCandidate(candidate).length > 0) {
|
|
17
|
+
parsedCandidates.push(candidate)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (parsedCandidates.length === 0) {
|
|
22
|
+
return validCandidates
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const cssByCandidate = designSystem.candidatesToCss(parsedCandidates)
|
|
26
|
+
for (let index = 0; index < parsedCandidates.length; index++) {
|
|
27
|
+
const candidate = parsedCandidates[index]
|
|
28
|
+
const candidateCss = cssByCandidate[index]
|
|
29
|
+
if (candidate && typeof candidateCss === 'string' && candidateCss.trim().length > 0) {
|
|
30
|
+
validCandidates.add(candidate)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return validCandidates
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function splitTopLevel(value: string, separator: string) {
|
|
38
|
+
const result: string[] = []
|
|
39
|
+
let start = 0
|
|
40
|
+
let depth = 0
|
|
41
|
+
let quote: string | undefined
|
|
42
|
+
|
|
43
|
+
for (let index = 0; index < value.length; index++) {
|
|
44
|
+
const character = value[index]
|
|
45
|
+
if (character === '\\') {
|
|
46
|
+
index++
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (quote) {
|
|
51
|
+
if (character === quote) {
|
|
52
|
+
quote = undefined
|
|
53
|
+
}
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (character === '"' || character === '\'') {
|
|
58
|
+
quote = character
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (character === '(' || character === '[' || character === '{') {
|
|
63
|
+
depth++
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (character === ')' || character === ']' || character === '}') {
|
|
68
|
+
depth = Math.max(0, depth - 1)
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (depth === 0 && character === separator) {
|
|
73
|
+
const item = value.slice(start, index).trim()
|
|
74
|
+
if (item) {
|
|
75
|
+
result.push(item)
|
|
76
|
+
}
|
|
77
|
+
start = index + 1
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const item = value.slice(start).trim()
|
|
82
|
+
if (item) {
|
|
83
|
+
result.push(item)
|
|
84
|
+
}
|
|
85
|
+
return result
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const sequencePattern = /^(-?\d+)\.\.(-?\d+)(?:\.\.(-?\d+))?$/
|
|
89
|
+
|
|
90
|
+
function expandSequence(value: string) {
|
|
91
|
+
const match = value.match(sequencePattern)
|
|
92
|
+
if (!match) {
|
|
93
|
+
return [value]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const [, startValue, endValue, stepValue] = match
|
|
97
|
+
if (startValue === undefined || endValue === undefined) {
|
|
98
|
+
return [value]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const start = Number.parseInt(startValue, 10)
|
|
102
|
+
const end = Number.parseInt(endValue, 10)
|
|
103
|
+
let step = stepValue === undefined ? (start <= end ? 1 : -1) : Number.parseInt(stepValue, 10)
|
|
104
|
+
if (step === 0) {
|
|
105
|
+
throw new Error('Step cannot be zero in Tailwind CSS v4 inline source sequence.')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const ascending = start < end
|
|
109
|
+
if (ascending && step < 0) {
|
|
110
|
+
step = -step
|
|
111
|
+
}
|
|
112
|
+
if (!ascending && step > 0) {
|
|
113
|
+
step = -step
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const result: string[] = []
|
|
117
|
+
for (let current = start; ascending ? current <= end : current >= end; current += step) {
|
|
118
|
+
result.push(current.toString())
|
|
119
|
+
}
|
|
120
|
+
return result
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function expandInlinePattern(pattern: string): string[] {
|
|
124
|
+
const openIndex = pattern.indexOf('{')
|
|
125
|
+
if (openIndex === -1) {
|
|
126
|
+
return [pattern]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const prefix = pattern.slice(0, openIndex)
|
|
130
|
+
const rest = pattern.slice(openIndex)
|
|
131
|
+
let depth = 0
|
|
132
|
+
let closeIndex = -1
|
|
133
|
+
for (let index = 0; index < rest.length; index++) {
|
|
134
|
+
const character = rest[index]
|
|
135
|
+
if (character === '{') {
|
|
136
|
+
depth++
|
|
137
|
+
}
|
|
138
|
+
else if (character === '}') {
|
|
139
|
+
depth--
|
|
140
|
+
if (depth === 0) {
|
|
141
|
+
closeIndex = index
|
|
142
|
+
break
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (closeIndex === -1) {
|
|
148
|
+
throw new Error(`The Tailwind CSS v4 inline source pattern "${pattern}" is not balanced.`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const body = rest.slice(1, closeIndex)
|
|
152
|
+
const suffix = rest.slice(closeIndex + 1)
|
|
153
|
+
const parts = sequencePattern.test(body)
|
|
154
|
+
? expandSequence(body)
|
|
155
|
+
: splitTopLevel(body, ',').flatMap(part => expandInlinePattern(part))
|
|
156
|
+
const suffixes = expandInlinePattern(suffix)
|
|
157
|
+
|
|
158
|
+
const result: string[] = []
|
|
159
|
+
for (const part of parts) {
|
|
160
|
+
for (const expandedSuffix of suffixes) {
|
|
161
|
+
result.push(`${prefix}${part}${expandedSuffix}`)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return result
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function unquoteCssString(value: string) {
|
|
168
|
+
const quote = value[0]
|
|
169
|
+
if ((quote !== '"' && quote !== '\'') || value[value.length - 1] !== quote) {
|
|
170
|
+
return undefined
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let result = ''
|
|
174
|
+
for (let index = 1; index < value.length - 1; index++) {
|
|
175
|
+
const character = value[index]
|
|
176
|
+
if (character === '\\') {
|
|
177
|
+
index++
|
|
178
|
+
result += value[index] ?? ''
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
result += character
|
|
182
|
+
}
|
|
183
|
+
return result
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function extractTailwindV4InlineSourceCandidates(css: string) {
|
|
187
|
+
const included = new Set<string>()
|
|
188
|
+
const excluded = new Set<string>()
|
|
189
|
+
|
|
190
|
+
const root = postcss.parse(css)
|
|
191
|
+
root.walkAtRules('source', (rule) => {
|
|
192
|
+
let params = rule.params.trim()
|
|
193
|
+
if (!params) {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let negated = false
|
|
198
|
+
if (params.startsWith('not ')) {
|
|
199
|
+
negated = true
|
|
200
|
+
params = params.slice(4).trim()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!params.startsWith('inline(') || !params.endsWith(')')) {
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const inlineValue = unquoteCssString(params.slice(7, -1).trim())
|
|
208
|
+
if (inlineValue === undefined) {
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const target = negated ? excluded : included
|
|
213
|
+
for (const part of splitTopLevel(inlineValue, ' ')) {
|
|
214
|
+
for (const candidate of expandInlinePattern(part)) {
|
|
215
|
+
target.add(candidate)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
included,
|
|
222
|
+
excluded,
|
|
223
|
+
}
|
|
224
|
+
}
|
package/src/v4/engine.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TailwindV4Engine,
|
|
3
|
+
TailwindV4GenerateOptions,
|
|
4
|
+
TailwindV4GenerateResult,
|
|
5
|
+
TailwindV4ResolvedSource,
|
|
6
|
+
} from './types'
|
|
7
|
+
import { extractRawCandidatesWithPositions } from '../extraction/candidate-extractor'
|
|
8
|
+
import { extractTailwindV4InlineSourceCandidates, resolveValidTailwindV4Candidates } from './candidates'
|
|
9
|
+
import { compileTailwindV4Source, loadTailwindV4DesignSystem } from './node-adapter'
|
|
10
|
+
|
|
11
|
+
async function collectRawCandidates(source: TailwindV4ResolvedSource, options: TailwindV4GenerateOptions | undefined) {
|
|
12
|
+
const rawCandidates = new Set<string>()
|
|
13
|
+
|
|
14
|
+
for (const candidate of options?.candidates ?? []) {
|
|
15
|
+
rawCandidates.add(candidate)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (const candidateSource of options?.sources ?? []) {
|
|
19
|
+
const candidates = await extractRawCandidatesWithPositions(candidateSource.content, candidateSource.extension)
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
rawCandidates.add(candidate.rawCandidate)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const inlineSources = extractTailwindV4InlineSourceCandidates(source.css)
|
|
26
|
+
for (const candidate of inlineSources.included) {
|
|
27
|
+
rawCandidates.add(candidate)
|
|
28
|
+
}
|
|
29
|
+
for (const candidate of inlineSources.excluded) {
|
|
30
|
+
rawCandidates.delete(candidate)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return rawCandidates
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createTailwindV4Engine(source: TailwindV4ResolvedSource): TailwindV4Engine {
|
|
37
|
+
return {
|
|
38
|
+
source,
|
|
39
|
+
loadDesignSystem() {
|
|
40
|
+
return loadTailwindV4DesignSystem(source)
|
|
41
|
+
},
|
|
42
|
+
async validateCandidates(candidates) {
|
|
43
|
+
const designSystem = await loadTailwindV4DesignSystem(source)
|
|
44
|
+
return resolveValidTailwindV4Candidates(designSystem, candidates)
|
|
45
|
+
},
|
|
46
|
+
async generate(options): Promise<TailwindV4GenerateResult> {
|
|
47
|
+
const rawCandidates = await collectRawCandidates(source, options)
|
|
48
|
+
const designSystem = await loadTailwindV4DesignSystem(source)
|
|
49
|
+
const classSet = resolveValidTailwindV4Candidates(designSystem, rawCandidates)
|
|
50
|
+
const inlineSources = extractTailwindV4InlineSourceCandidates(source.css)
|
|
51
|
+
// TODO: Non-inline `@source not "..."` is surfaced through `compiled.sources`;
|
|
52
|
+
// apply it here if generate() grows filesystem SourceEntry scanning.
|
|
53
|
+
for (const candidate of inlineSources.excluded) {
|
|
54
|
+
classSet.delete(candidate)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const { compiled, dependencies } = await compileTailwindV4Source(source)
|
|
58
|
+
const css = compiled.build(Array.from(classSet))
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
css,
|
|
62
|
+
classSet,
|
|
63
|
+
rawCandidates,
|
|
64
|
+
dependencies: Array.from(dependencies),
|
|
65
|
+
sources: compiled.sources,
|
|
66
|
+
root: compiled.root,
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/v4/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export {
|
|
2
|
+
extractTailwindV4InlineSourceCandidates,
|
|
3
|
+
resolveValidTailwindV4Candidates,
|
|
4
|
+
} from './candidates'
|
|
5
|
+
export { createTailwindV4Engine } from './engine'
|
|
6
|
+
export {
|
|
7
|
+
compileTailwindV4Source,
|
|
8
|
+
loadTailwindV4DesignSystem,
|
|
9
|
+
loadTailwindV4NodeModule,
|
|
10
|
+
} from './node-adapter'
|
|
11
|
+
export {
|
|
12
|
+
resolveTailwindV4Source,
|
|
13
|
+
resolveTailwindV4SourceFromPatchOptions,
|
|
14
|
+
tailwindV4SourceOptionsFromPatchOptions,
|
|
15
|
+
} from './source'
|
|
16
|
+
export type {
|
|
17
|
+
TailwindV4CandidateSource,
|
|
18
|
+
TailwindV4DesignSystem,
|
|
19
|
+
TailwindV4Engine,
|
|
20
|
+
TailwindV4GenerateOptions,
|
|
21
|
+
TailwindV4GenerateResult,
|
|
22
|
+
TailwindV4ResolvedSource,
|
|
23
|
+
TailwindV4SourceOptions,
|
|
24
|
+
TailwindV4SourcePattern,
|
|
25
|
+
} from './types'
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TailwindV4DesignSystem,
|
|
3
|
+
TailwindV4ResolvedSource,
|
|
4
|
+
TailwindV4SourcePattern,
|
|
5
|
+
} from './types'
|
|
6
|
+
import { createRequire } from 'node:module'
|
|
7
|
+
import { pathToFileURL } from 'node:url'
|
|
8
|
+
import path from 'pathe'
|
|
9
|
+
|
|
10
|
+
interface TailwindV4CompiledSource {
|
|
11
|
+
sources: TailwindV4SourcePattern[]
|
|
12
|
+
root: null | 'none' | {
|
|
13
|
+
base: string
|
|
14
|
+
pattern: string
|
|
15
|
+
}
|
|
16
|
+
build: (candidates: string[]) => string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface TailwindV4NodeModule {
|
|
20
|
+
compile: (css: string, options: {
|
|
21
|
+
base: string
|
|
22
|
+
onDependency: (dependency: string) => void
|
|
23
|
+
}) => Promise<TailwindV4CompiledSource>
|
|
24
|
+
__unstable__loadDesignSystem: (css: string, options: { base: string }) => Promise<TailwindV4DesignSystem>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const nodeModulePromiseCache = new Map<string, Promise<TailwindV4NodeModule>>()
|
|
28
|
+
const designSystemPromiseCache = new Map<string, Promise<TailwindV4DesignSystem>>()
|
|
29
|
+
|
|
30
|
+
function unique(values: Iterable<string>) {
|
|
31
|
+
return Array.from(new Set(Array.from(values).filter(Boolean).map(value => path.resolve(value))))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createRequireBase(base: string) {
|
|
35
|
+
return path.join(base, 'package.json')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function importResolvedModule(resolved: string): Promise<TailwindV4NodeModule> {
|
|
39
|
+
return import(pathToFileURL(resolved).href) as unknown as Promise<TailwindV4NodeModule>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function importTailwindNodeFromBase(base: string): Promise<TailwindV4NodeModule | undefined> {
|
|
43
|
+
try {
|
|
44
|
+
const resolved = createRequire(createRequireBase(base)).resolve('@tailwindcss/node')
|
|
45
|
+
return await importResolvedModule(resolved)
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return undefined
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function importFallbackTailwindNode(): Promise<TailwindV4NodeModule> {
|
|
53
|
+
return import('@tailwindcss/node') as unknown as Promise<TailwindV4NodeModule>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function loadTailwindV4NodeModule(baseCandidates: string[]): Promise<TailwindV4NodeModule> {
|
|
57
|
+
const bases = unique(baseCandidates)
|
|
58
|
+
const cacheKey = JSON.stringify(bases)
|
|
59
|
+
const cached = nodeModulePromiseCache.get(cacheKey)
|
|
60
|
+
if (cached) {
|
|
61
|
+
return cached
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const promise = (async () => {
|
|
65
|
+
for (const base of bases) {
|
|
66
|
+
const loaded = await importTailwindNodeFromBase(base)
|
|
67
|
+
if (loaded) {
|
|
68
|
+
return loaded
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return importFallbackTailwindNode()
|
|
73
|
+
})()
|
|
74
|
+
|
|
75
|
+
nodeModulePromiseCache.set(cacheKey, promise)
|
|
76
|
+
promise.catch(() => {
|
|
77
|
+
if (nodeModulePromiseCache.get(cacheKey) === promise) {
|
|
78
|
+
nodeModulePromiseCache.delete(cacheKey)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
return promise
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function createDesignSystemCacheKey(css: string, bases: string[]) {
|
|
85
|
+
return JSON.stringify({
|
|
86
|
+
css,
|
|
87
|
+
bases: unique(bases),
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getTailwindV4DesignSystemCacheKey(source: Pick<TailwindV4ResolvedSource, 'css' | 'base' | 'baseFallbacks'>) {
|
|
92
|
+
return createDesignSystemCacheKey(source.css, [source.base, ...source.baseFallbacks])
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function loadTailwindV4DesignSystem(source: TailwindV4ResolvedSource): Promise<TailwindV4DesignSystem> {
|
|
96
|
+
const bases = unique([source.base, ...source.baseFallbacks])
|
|
97
|
+
if (bases.length === 0) {
|
|
98
|
+
throw new Error('No base directories provided for Tailwind CSS v4 design system.')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const cacheKey = createDesignSystemCacheKey(source.css, bases)
|
|
102
|
+
const cached = designSystemPromiseCache.get(cacheKey)
|
|
103
|
+
if (cached) {
|
|
104
|
+
return cached
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const promise = (async () => {
|
|
108
|
+
const node = await loadTailwindV4NodeModule([source.projectRoot, ...bases])
|
|
109
|
+
let lastError: unknown
|
|
110
|
+
|
|
111
|
+
for (const base of bases) {
|
|
112
|
+
try {
|
|
113
|
+
return await node.__unstable__loadDesignSystem(source.css, { base })
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
lastError = error
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (lastError instanceof Error) {
|
|
121
|
+
throw lastError
|
|
122
|
+
}
|
|
123
|
+
throw new Error('Failed to load Tailwind CSS v4 design system.')
|
|
124
|
+
})()
|
|
125
|
+
|
|
126
|
+
designSystemPromiseCache.set(cacheKey, promise)
|
|
127
|
+
promise.catch(() => {
|
|
128
|
+
if (designSystemPromiseCache.get(cacheKey) === promise) {
|
|
129
|
+
designSystemPromiseCache.delete(cacheKey)
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
return promise
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function compileTailwindV4Source(source: TailwindV4ResolvedSource) {
|
|
136
|
+
const node = await loadTailwindV4NodeModule([source.projectRoot, source.base, ...source.baseFallbacks])
|
|
137
|
+
const dependencies = new Set(source.dependencies)
|
|
138
|
+
const compiled = await node.compile(source.css, {
|
|
139
|
+
base: source.base,
|
|
140
|
+
onDependency(dependency) {
|
|
141
|
+
dependencies.add(path.resolve(dependency))
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
compiled,
|
|
147
|
+
dependencies,
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/v4/source.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { NormalizedTailwindCssPatchOptions, TailwindCssPatchOptions } from '../config'
|
|
2
|
+
import type { TailwindV4ResolvedSource, TailwindV4SourceOptions } from './types'
|
|
3
|
+
import { promises as fs } from 'node:fs'
|
|
4
|
+
import process from 'node:process'
|
|
5
|
+
import path from 'pathe'
|
|
6
|
+
import { normalizeOptions } from '../config'
|
|
7
|
+
|
|
8
|
+
function resolveBase(value: string | undefined, fallback: string) {
|
|
9
|
+
return value === undefined
|
|
10
|
+
? fallback
|
|
11
|
+
: path.isAbsolute(value)
|
|
12
|
+
? path.resolve(value)
|
|
13
|
+
: path.resolve(fallback, value)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function uniquePaths(values: Iterable<string | undefined>) {
|
|
17
|
+
const result: string[] = []
|
|
18
|
+
for (const value of values) {
|
|
19
|
+
if (!value) {
|
|
20
|
+
continue
|
|
21
|
+
}
|
|
22
|
+
const resolved = path.resolve(value)
|
|
23
|
+
if (!result.includes(resolved)) {
|
|
24
|
+
result.push(resolved)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toCssImportPath(value: string) {
|
|
31
|
+
return value.replaceAll('\\', '/')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function quoteCssImport(value: string) {
|
|
35
|
+
return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isPostcssPluginSpecifier(packageName: string) {
|
|
39
|
+
return packageName === '@tailwindcss/postcss'
|
|
40
|
+
|| /(?:^|[/\\])@tailwindcss[/\\]postcss(?:[/\\]|$)/.test(packageName)
|
|
41
|
+
|| /(?:^|[/\\])postcss(?:[/\\]|$)/i.test(packageName)
|
|
42
|
+
|| /postcss\.config\.[cm]?[jt]s$/i.test(packageName)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createDefaultCss(packageName: string | undefined) {
|
|
46
|
+
const cssPackageName = packageName && !isPostcssPluginSpecifier(packageName)
|
|
47
|
+
? packageName
|
|
48
|
+
: 'tailwindcss'
|
|
49
|
+
return `@import "${quoteCssImport(toCssImportPath(cssPackageName))}";`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function pathExists(filePath: string) {
|
|
53
|
+
try {
|
|
54
|
+
await fs.access(filePath)
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function resolveCssEntries(entries: string[], projectRoot: string, base: string | undefined) {
|
|
63
|
+
const resolvedEntries = entries.map(entry => ({
|
|
64
|
+
original: entry,
|
|
65
|
+
absolute: path.isAbsolute(entry) ? path.resolve(entry) : path.resolve(projectRoot, entry),
|
|
66
|
+
}))
|
|
67
|
+
const resolvedBase = base ?? path.dirname(resolvedEntries[0]?.absolute ?? projectRoot)
|
|
68
|
+
const dependencies = resolvedEntries.map(entry => entry.absolute)
|
|
69
|
+
const cssParts: string[] = []
|
|
70
|
+
|
|
71
|
+
for (const entry of resolvedEntries) {
|
|
72
|
+
if (await pathExists(entry.absolute)) {
|
|
73
|
+
cssParts.push(await fs.readFile(entry.absolute, 'utf8'))
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const importPath = path.isAbsolute(entry.original)
|
|
78
|
+
? entry.absolute
|
|
79
|
+
: path.relative(resolvedBase, entry.absolute)
|
|
80
|
+
cssParts.push(`@import "${quoteCssImport(toCssImportPath(importPath))}";`)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
base: resolvedBase,
|
|
85
|
+
css: cssParts.join('\n'),
|
|
86
|
+
dependencies,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizeResolvedSource(
|
|
91
|
+
source: {
|
|
92
|
+
projectRoot: string
|
|
93
|
+
cwd: string
|
|
94
|
+
base: string
|
|
95
|
+
baseFallbacks: string[]
|
|
96
|
+
css: string
|
|
97
|
+
dependencies: string[]
|
|
98
|
+
},
|
|
99
|
+
): TailwindV4ResolvedSource {
|
|
100
|
+
const baseFallbacks = uniquePaths([
|
|
101
|
+
...source.baseFallbacks,
|
|
102
|
+
source.projectRoot,
|
|
103
|
+
source.cwd,
|
|
104
|
+
]).filter(base => base !== source.base)
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
projectRoot: source.projectRoot,
|
|
108
|
+
base: source.base,
|
|
109
|
+
baseFallbacks,
|
|
110
|
+
css: source.css,
|
|
111
|
+
dependencies: Array.from(new Set(source.dependencies.map(dependency => path.resolve(dependency)))),
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function resolveTailwindV4Source(options: TailwindV4SourceOptions = {}): Promise<TailwindV4ResolvedSource> {
|
|
116
|
+
const projectRoot = resolveBase(options.projectRoot, process.cwd())
|
|
117
|
+
const cwd = resolveBase(options.cwd, projectRoot)
|
|
118
|
+
const configuredBase = options.base === undefined ? undefined : resolveBase(options.base, projectRoot)
|
|
119
|
+
const baseFallbacks = uniquePaths(options.baseFallbacks?.map(base => resolveBase(base, projectRoot)) ?? [])
|
|
120
|
+
|
|
121
|
+
if (options.css !== undefined) {
|
|
122
|
+
return normalizeResolvedSource({
|
|
123
|
+
projectRoot,
|
|
124
|
+
cwd,
|
|
125
|
+
base: configuredBase ?? cwd,
|
|
126
|
+
baseFallbacks,
|
|
127
|
+
css: options.css,
|
|
128
|
+
dependencies: [],
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (options.cssEntries?.length) {
|
|
133
|
+
const entries = await resolveCssEntries(options.cssEntries, projectRoot, configuredBase)
|
|
134
|
+
return normalizeResolvedSource({
|
|
135
|
+
projectRoot,
|
|
136
|
+
cwd,
|
|
137
|
+
base: entries.base,
|
|
138
|
+
baseFallbacks,
|
|
139
|
+
css: entries.css,
|
|
140
|
+
dependencies: entries.dependencies,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return normalizeResolvedSource({
|
|
145
|
+
projectRoot,
|
|
146
|
+
cwd,
|
|
147
|
+
base: configuredBase ?? cwd,
|
|
148
|
+
baseFallbacks,
|
|
149
|
+
css: createDefaultCss(options.packageName),
|
|
150
|
+
dependencies: [],
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function resolveConfigDir(config: string | undefined, projectRoot: string) {
|
|
155
|
+
if (!config) {
|
|
156
|
+
return undefined
|
|
157
|
+
}
|
|
158
|
+
const configPath = path.isAbsolute(config) ? config : path.resolve(projectRoot, config)
|
|
159
|
+
return path.dirname(configPath)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function createSourceOptionsFromNormalizedPatchOptions(
|
|
163
|
+
options: NormalizedTailwindCssPatchOptions,
|
|
164
|
+
): TailwindV4SourceOptions {
|
|
165
|
+
const v4 = options.tailwind.v4
|
|
166
|
+
const configDir = resolveConfigDir(options.tailwind.config, options.projectRoot)
|
|
167
|
+
const baseFallbacks = uniquePaths([
|
|
168
|
+
v4?.configuredBase,
|
|
169
|
+
options.tailwind.cwd,
|
|
170
|
+
options.projectRoot,
|
|
171
|
+
configDir,
|
|
172
|
+
])
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
projectRoot: options.projectRoot,
|
|
176
|
+
...(options.tailwind.cwd === undefined ? {} : { cwd: options.tailwind.cwd }),
|
|
177
|
+
...(v4?.configuredBase === undefined ? {} : { base: v4.configuredBase }),
|
|
178
|
+
baseFallbacks,
|
|
179
|
+
...(v4?.css === undefined ? {} : { css: v4.css }),
|
|
180
|
+
...(v4?.cssEntries === undefined ? {} : { cssEntries: v4.cssEntries }),
|
|
181
|
+
packageName: options.tailwind.packageName,
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function tailwindV4SourceOptionsFromPatchOptions(options: TailwindCssPatchOptions): TailwindV4SourceOptions {
|
|
186
|
+
return createSourceOptionsFromNormalizedPatchOptions(normalizeOptions(options))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function resolveTailwindV4SourceFromPatchOptions(
|
|
190
|
+
options: TailwindCssPatchOptions,
|
|
191
|
+
): Promise<TailwindV4ResolvedSource> {
|
|
192
|
+
return resolveTailwindV4Source(tailwindV4SourceOptionsFromPatchOptions(options))
|
|
193
|
+
}
|