tailwindcss-patch 9.4.1 → 9.4.3
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/README.md +69 -0
- package/dist/{cli-CBVPia5Z.js → cli-BztQHMRp.js} +63 -64
- package/dist/{cli-CgBdW1U5.mjs → cli-D0jXMGXf.mjs} +1 -1
- package/dist/cli.js +5 -6
- 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 +6 -6
- package/dist/commands/cli-runtime.mjs +2 -2
- package/dist/{dist-B1VBpHtd.js → dist-DDcbvOwe.js} +2 -2
- package/dist/index.d.mts +105 -5
- package/dist/index.d.ts +105 -5
- package/dist/index.js +325 -62
- package/dist/index.mjs +253 -4
- package/dist/{migrate-config-DqknZpUe.mjs → validate-Bug_WYcU.mjs} +193 -93
- package/dist/{validate-CaJv2g5K.d.ts → validate-BuqRodYI.d.ts} +65 -16
- package/dist/{migrate-config-Dn9OTXpO.js → validate-DbuKewV-.js} +257 -100
- package/dist/{validate-Dr7IkGU8.d.mts → validate-oAkURzUC.d.mts} +65 -15
- package/package.json +9 -9
- package/src/extraction/candidate-extractor.ts +76 -12
- package/src/public-api.ts +54 -13
- package/src/style-candidates.ts +35 -0
- package/src/style-generator.ts +80 -0
- package/src/v3/index.ts +11 -0
- package/src/v3/style-generator.ts +384 -0
- package/src/v4/bare-arbitrary-values.ts +127 -2
- package/src/v4/engine.ts +5 -2
- package/src/v4/index.ts +20 -4
- package/src/v4/node-adapter.ts +1 -1
- package/src/v4/source-scan.ts +1 -1
- package/src/v4/style-generator.ts +44 -0
- package/src/v4/types.ts +23 -0
- package/dist/chunk-8l464Juk.js +0 -28
- /package/dist/{dist-BjUV1yEM.mjs → dist-CxmNpfyy.mjs} +0 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import type { Node } from 'postcss'
|
|
2
|
+
import type { Config } from 'tailwindcss'
|
|
3
|
+
import type { TailwindStyleCandidateOptions, TailwindStyleSource } from '../style-candidates'
|
|
4
|
+
import type { TailwindcssRuntimeContext } from '../types'
|
|
5
|
+
import { createRequire } from 'node:module'
|
|
6
|
+
import process from 'node:process'
|
|
7
|
+
import path from 'pathe'
|
|
8
|
+
import postcss from 'postcss'
|
|
9
|
+
import { collectTailwindStyleCandidates } from '../style-candidates'
|
|
10
|
+
|
|
11
|
+
type NodeRequire = ReturnType<typeof createRequire>
|
|
12
|
+
|
|
13
|
+
interface TailwindV3CreateContextModule {
|
|
14
|
+
createContext: (
|
|
15
|
+
tailwindConfig: Config,
|
|
16
|
+
changedContent?: Array<{ content?: string, extension?: string }>,
|
|
17
|
+
root?: ReturnType<typeof postcss.root>,
|
|
18
|
+
) => TailwindcssRuntimeContext
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface TailwindV3GenerateRulesModule {
|
|
22
|
+
generateRules: (
|
|
23
|
+
candidates: Set<string>,
|
|
24
|
+
context: TailwindcssRuntimeContext,
|
|
25
|
+
) => Array<[unknown, Node]>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface TailwindV3ProcessResult {
|
|
29
|
+
css: string
|
|
30
|
+
messages: Array<Record<string, unknown>>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface TailwindV3CollapseRulesModule {
|
|
34
|
+
default?: (context: TailwindcssRuntimeContext) => (root: postcss.Root, result: TailwindV3ProcessResult) => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface TailwindV3ProcessTailwindFeaturesModule {
|
|
38
|
+
default?: (
|
|
39
|
+
setupContext: () => (root: postcss.Root) => TailwindcssRuntimeContext,
|
|
40
|
+
) => (root: postcss.Root, result: TailwindV3ProcessResult) => Promise<TailwindcssRuntimeContext>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface TailwindV3ResolveDefaultsAtRulesModule {
|
|
44
|
+
default?: (context: TailwindcssRuntimeContext) => (root: postcss.Root, result: TailwindV3ProcessResult) => void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface TailwindV3ValidateConfigModule {
|
|
48
|
+
validateConfig: (config: unknown) => Config
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface TailwindV3SharedStateModule {
|
|
52
|
+
NOT_ON_DEMAND?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type ResolveConfig = (config: Config) => Config
|
|
56
|
+
|
|
57
|
+
interface TailwindV3Offsets {
|
|
58
|
+
sort: <T>(rules: Array<[unknown, T]>) => Array<[{
|
|
59
|
+
layer: 'base' | 'components' | 'defaults' | 'utilities' | 'variants'
|
|
60
|
+
}, T]>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface TailwindV3StyleGenerateOptions extends TailwindStyleCandidateOptions {
|
|
64
|
+
/**
|
|
65
|
+
* Tailwind v3 package name or alias. Defaults to `tailwindcss`.
|
|
66
|
+
*/
|
|
67
|
+
packageName?: string
|
|
68
|
+
/**
|
|
69
|
+
* `createRequire` base used to resolve the Tailwind v3 package.
|
|
70
|
+
*/
|
|
71
|
+
cwd?: string
|
|
72
|
+
/**
|
|
73
|
+
* Inline Tailwind v3 config. `content` is injected from collected candidates.
|
|
74
|
+
*/
|
|
75
|
+
config?: Partial<Config>
|
|
76
|
+
/**
|
|
77
|
+
* Generate all layers by default. Pass a subset to emit only selected layers.
|
|
78
|
+
*/
|
|
79
|
+
layers?: TailwindV3StyleLayer[]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type TailwindV3StyleLayer = 'base' | 'components' | 'utilities' | 'variants'
|
|
83
|
+
|
|
84
|
+
export interface TailwindV3RawStyleGenerateOptions extends TailwindStyleCandidateOptions {
|
|
85
|
+
/**
|
|
86
|
+
* Tailwind v3 package name or alias. Defaults to `tailwindcss`.
|
|
87
|
+
*/
|
|
88
|
+
packageName?: string
|
|
89
|
+
/**
|
|
90
|
+
* `createRequire` base used to resolve the Tailwind v3 package.
|
|
91
|
+
*/
|
|
92
|
+
cwd?: string
|
|
93
|
+
/**
|
|
94
|
+
* Tailwind v3 entry CSS. Defaults to `@tailwind utilities;`.
|
|
95
|
+
*/
|
|
96
|
+
css?: string
|
|
97
|
+
/**
|
|
98
|
+
* Inline Tailwind v3 config. Candidate content is injected automatically.
|
|
99
|
+
*/
|
|
100
|
+
config?: Partial<Config>
|
|
101
|
+
/**
|
|
102
|
+
* Directly append generated utility rules when the CSS is exactly `@tailwind utilities;`.
|
|
103
|
+
* This mirrors Tailwind v3's internal fast path and keeps output order aligned.
|
|
104
|
+
*/
|
|
105
|
+
directUtilitiesOnly?: boolean | 'auto'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface TailwindV3RawStyleGenerateResult {
|
|
109
|
+
version: 3
|
|
110
|
+
css: string
|
|
111
|
+
tokens: Set<string>
|
|
112
|
+
classSet: Set<string>
|
|
113
|
+
context: TailwindcssRuntimeContext
|
|
114
|
+
dependencies: string[]
|
|
115
|
+
sources: TailwindStyleSource[]
|
|
116
|
+
config: Config
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface TailwindV3StyleGenerateResult {
|
|
120
|
+
version: 3
|
|
121
|
+
css: string
|
|
122
|
+
tokens: Set<string>
|
|
123
|
+
classSet: Set<string>
|
|
124
|
+
sources: TailwindStyleSource[]
|
|
125
|
+
config: Config
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function createPackageRequire(cwd?: string): NodeRequire {
|
|
129
|
+
return createRequire(path.join(path.resolve(cwd ?? process.cwd()), 'package.json'))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function createDefaultTailwindV3Config(tokens: Set<string>): Config {
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{
|
|
136
|
+
raw: [...tokens].join(' '),
|
|
137
|
+
extension: 'html',
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
theme: {
|
|
141
|
+
extend: {},
|
|
142
|
+
},
|
|
143
|
+
plugins: [],
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getDefaultExport<T>(module: { default?: T } | T): T {
|
|
148
|
+
if (module && typeof module === 'object' && 'default' in module) {
|
|
149
|
+
return module.default as T
|
|
150
|
+
}
|
|
151
|
+
return module as T
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function loadTailwindV3Modules(options: Pick<TailwindV3StyleGenerateOptions, 'cwd' | 'packageName'>) {
|
|
155
|
+
const packageName = options.packageName ?? 'tailwindcss'
|
|
156
|
+
const moduleRequire = createPackageRequire(options.cwd)
|
|
157
|
+
const resolveConfig = getDefaultExport<ResolveConfig>(
|
|
158
|
+
moduleRequire(`${packageName}/lib/public/resolve-config`) as { default?: ResolveConfig } | ResolveConfig,
|
|
159
|
+
)
|
|
160
|
+
const contextModule = moduleRequire(`${packageName}/lib/lib/setupContextUtils`) as TailwindV3CreateContextModule
|
|
161
|
+
const generateRulesModule = moduleRequire(`${packageName}/lib/lib/generateRules`) as TailwindV3GenerateRulesModule
|
|
162
|
+
const collapseAdjacentRulesModule = moduleRequire(`${packageName}/lib/lib/collapseAdjacentRules`) as TailwindV3CollapseRulesModule
|
|
163
|
+
const collapseDuplicateDeclarationsModule = moduleRequire(`${packageName}/lib/lib/collapseDuplicateDeclarations`) as TailwindV3CollapseRulesModule
|
|
164
|
+
const processTailwindFeaturesModule = moduleRequire(`${packageName}/lib/processTailwindFeatures`) as TailwindV3ProcessTailwindFeaturesModule
|
|
165
|
+
const resolveDefaultsAtRulesModule = moduleRequire(`${packageName}/lib/lib/resolveDefaultsAtRules`) as TailwindV3ResolveDefaultsAtRulesModule
|
|
166
|
+
const sharedStateModule = moduleRequire(`${packageName}/lib/lib/sharedState`) as TailwindV3SharedStateModule
|
|
167
|
+
const validateConfigModule = moduleRequire(`${packageName}/lib/util/validateConfig.js`) as TailwindV3ValidateConfigModule
|
|
168
|
+
return {
|
|
169
|
+
collapseAdjacentRules: getDefaultExport(collapseAdjacentRulesModule),
|
|
170
|
+
collapseDuplicateDeclarations: getDefaultExport(collapseDuplicateDeclarationsModule),
|
|
171
|
+
createContext: contextModule.createContext,
|
|
172
|
+
generateRules: generateRulesModule.generateRules,
|
|
173
|
+
notOnDemandCandidate: sharedStateModule.NOT_ON_DEMAND ?? '*',
|
|
174
|
+
processTailwindFeatures: getDefaultExport(processTailwindFeaturesModule),
|
|
175
|
+
resolveDefaultsAtRules: getDefaultExport(resolveDefaultsAtRulesModule),
|
|
176
|
+
resolveConfig,
|
|
177
|
+
validateConfig: validateConfigModule.validateConfig,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function createRawContentEntries(candidates: Iterable<string>, sources: TailwindStyleSource[]) {
|
|
182
|
+
const entries: Array<{ raw: string, extension: string }> = []
|
|
183
|
+
const candidateContent = [...candidates].join(' ')
|
|
184
|
+
if (candidateContent.length > 0) {
|
|
185
|
+
entries.push({
|
|
186
|
+
raw: candidateContent,
|
|
187
|
+
extension: 'html',
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
for (const source of sources) {
|
|
191
|
+
entries.push({
|
|
192
|
+
raw: source.content,
|
|
193
|
+
extension: source.extension ?? 'html',
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
return entries
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function createChangedContentEntries(candidates: Iterable<string>, sources: TailwindStyleSource[]) {
|
|
200
|
+
return createRawContentEntries(candidates, sources).map(entry => ({
|
|
201
|
+
content: entry.raw,
|
|
202
|
+
extension: entry.extension,
|
|
203
|
+
}))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function createTailwindConfigWithContent(
|
|
207
|
+
config: Partial<Config> | undefined,
|
|
208
|
+
tokens: Set<string>,
|
|
209
|
+
sources: TailwindStyleSource[],
|
|
210
|
+
) {
|
|
211
|
+
const userContent = config?.content
|
|
212
|
+
return {
|
|
213
|
+
...createDefaultTailwindV3Config(tokens),
|
|
214
|
+
...(config ?? {}),
|
|
215
|
+
content: [
|
|
216
|
+
...(Array.isArray(userContent) ? userContent : []),
|
|
217
|
+
...createRawContentEntries(tokens, sources),
|
|
218
|
+
],
|
|
219
|
+
} as Config
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function isDirectUtilitiesOnlyCss(css: string) {
|
|
223
|
+
return css.replace(/\s+/g, '') === '@tailwindutilities;'
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function sortCandidates(candidates: Iterable<string>) {
|
|
227
|
+
return [...candidates].sort((a, z) => {
|
|
228
|
+
if (a === z) {
|
|
229
|
+
return 0
|
|
230
|
+
}
|
|
231
|
+
return a < z ? -1 : 1
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function appendUtilityRules(
|
|
236
|
+
root: postcss.Root,
|
|
237
|
+
context: TailwindcssRuntimeContext,
|
|
238
|
+
rules: Array<[unknown, Node]>,
|
|
239
|
+
) {
|
|
240
|
+
const sortedRules = (context.offsets as unknown as TailwindV3Offsets).sort(rules)
|
|
241
|
+
for (const [sort, rule] of sortedRules) {
|
|
242
|
+
const tailwindRaw = rule.raws.tailwind as { parentLayer?: string } | undefined
|
|
243
|
+
if (sort.layer === 'utilities' || (sort.layer === 'variants' && tailwindRaw?.parentLayer === 'utilities')) {
|
|
244
|
+
root.append(rule.clone())
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function collectClassSet(context: TailwindcssRuntimeContext, notOnDemandCandidate: string) {
|
|
250
|
+
const classSet = new Set<string>()
|
|
251
|
+
for (const candidate of context.classCache.keys()) {
|
|
252
|
+
if (String(candidate) !== String(notOnDemandCandidate)) {
|
|
253
|
+
classSet.add(candidate)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return classSet
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function collectDependencyMessages(result: TailwindV3ProcessResult) {
|
|
260
|
+
const dependencies = new Set<string>()
|
|
261
|
+
for (const message of result.messages) {
|
|
262
|
+
const file = message.file
|
|
263
|
+
if (message.type === 'dependency' && typeof file === 'string') {
|
|
264
|
+
dependencies.add(file)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return [...dependencies]
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function buildStylesheetNodes(
|
|
271
|
+
rules: Array<[unknown, Node]>,
|
|
272
|
+
context: TailwindcssRuntimeContext,
|
|
273
|
+
layers: TailwindV3StyleLayer[],
|
|
274
|
+
) {
|
|
275
|
+
const sortedRules = (context.offsets as unknown as TailwindV3Offsets).sort(rules)
|
|
276
|
+
const nodes: Node[] = []
|
|
277
|
+
const layerSet = new Set<TailwindV3StyleLayer>(layers)
|
|
278
|
+
|
|
279
|
+
for (const [sort, rule] of sortedRules) {
|
|
280
|
+
const layer = sort.layer === 'defaults' ? 'base' : sort.layer
|
|
281
|
+
if (layerSet.has(layer)) {
|
|
282
|
+
nodes.push(rule.clone())
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return nodes
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function createRootFromNodes(nodes: Node[]) {
|
|
289
|
+
const root = postcss.root()
|
|
290
|
+
for (const node of nodes) {
|
|
291
|
+
root.append(node)
|
|
292
|
+
}
|
|
293
|
+
return root
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export async function generateTailwindV3Style(
|
|
297
|
+
options: TailwindV3StyleGenerateOptions = {},
|
|
298
|
+
): Promise<TailwindV3StyleGenerateResult> {
|
|
299
|
+
const tokens = await collectTailwindStyleCandidates(options)
|
|
300
|
+
const { createContext, generateRules, resolveConfig } = loadTailwindV3Modules(options)
|
|
301
|
+
const userContent = options.config?.content
|
|
302
|
+
const config = resolveConfig({
|
|
303
|
+
...createDefaultTailwindV3Config(tokens),
|
|
304
|
+
...(options.config ?? {}),
|
|
305
|
+
content: [
|
|
306
|
+
...(Array.isArray(userContent) ? userContent : []),
|
|
307
|
+
{
|
|
308
|
+
raw: [...tokens].join(' '),
|
|
309
|
+
extension: 'html',
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
} as Config)
|
|
313
|
+
const root = postcss.root()
|
|
314
|
+
const context = createContext(config, [], root)
|
|
315
|
+
const rules = generateRules(tokens, context)
|
|
316
|
+
const nodes = buildStylesheetNodes(rules, context, options.layers ?? ['base', 'components', 'utilities', 'variants'])
|
|
317
|
+
const css = createRootFromNodes(nodes).toString()
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
version: 3,
|
|
321
|
+
css,
|
|
322
|
+
tokens,
|
|
323
|
+
classSet: new Set(context.classCache.keys()),
|
|
324
|
+
sources: options.sources ?? [],
|
|
325
|
+
config,
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export async function generateTailwindV3RawStyle(
|
|
330
|
+
options: TailwindV3RawStyleGenerateOptions = {},
|
|
331
|
+
): Promise<TailwindV3RawStyleGenerateResult> {
|
|
332
|
+
const tokens = await collectTailwindStyleCandidates(options)
|
|
333
|
+
const {
|
|
334
|
+
collapseAdjacentRules,
|
|
335
|
+
collapseDuplicateDeclarations,
|
|
336
|
+
createContext,
|
|
337
|
+
generateRules,
|
|
338
|
+
notOnDemandCandidate,
|
|
339
|
+
processTailwindFeatures,
|
|
340
|
+
resolveConfig,
|
|
341
|
+
resolveDefaultsAtRules,
|
|
342
|
+
validateConfig,
|
|
343
|
+
} = loadTailwindV3Modules(options)
|
|
344
|
+
const css = options.css ?? '@tailwind utilities;'
|
|
345
|
+
const config = validateConfig(resolveConfig(createTailwindConfigWithContent(options.config, tokens, options.sources ?? [])))
|
|
346
|
+
const root = postcss.parse(css, {
|
|
347
|
+
from: undefined,
|
|
348
|
+
})
|
|
349
|
+
const result: TailwindV3ProcessResult = {
|
|
350
|
+
css: '',
|
|
351
|
+
messages: [],
|
|
352
|
+
}
|
|
353
|
+
const changedContent = createChangedContentEntries(tokens, options.sources ?? [])
|
|
354
|
+
const shouldUseDirectUtilities = options.directUtilitiesOnly === true
|
|
355
|
+
|| (options.directUtilitiesOnly !== false && isDirectUtilitiesOnlyCss(css))
|
|
356
|
+
let context: TailwindcssRuntimeContext
|
|
357
|
+
|
|
358
|
+
if (shouldUseDirectUtilities) {
|
|
359
|
+
context = createContext(config, changedContent, root)
|
|
360
|
+
generateRules(new Set(sortCandidates([notOnDemandCandidate, ...tokens])), context)
|
|
361
|
+
root.removeAll()
|
|
362
|
+
appendUtilityRules(root, context, [...context.ruleCache])
|
|
363
|
+
resolveDefaultsAtRules(context)(root, result)
|
|
364
|
+
collapseAdjacentRules(context)(root, result)
|
|
365
|
+
collapseDuplicateDeclarations(context)(root, result)
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
const setupContext = () => {
|
|
369
|
+
return (currentRoot: postcss.Root) => createContext(config, changedContent, currentRoot)
|
|
370
|
+
}
|
|
371
|
+
context = await processTailwindFeatures(setupContext)(root, result)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
version: 3,
|
|
376
|
+
css: root.toString(),
|
|
377
|
+
tokens,
|
|
378
|
+
classSet: collectClassSet(context, notOnDemandCandidate),
|
|
379
|
+
context,
|
|
380
|
+
dependencies: collectDependencyMessages(result),
|
|
381
|
+
sources: options.sources ?? [],
|
|
382
|
+
config,
|
|
383
|
+
}
|
|
384
|
+
}
|
|
@@ -10,6 +10,12 @@ export interface BareArbitraryValueResolveResult {
|
|
|
10
10
|
canonicalCandidate: string
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface BareArbitraryValueSourceCandidate {
|
|
14
|
+
rawCandidate: string
|
|
15
|
+
start: number
|
|
16
|
+
end: number
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
const DEFAULT_BARE_ARBITRARY_VALUE_UNITS = [
|
|
14
20
|
'%',
|
|
15
21
|
'px',
|
|
@@ -41,6 +47,8 @@ const DEFAULT_BARE_ARBITRARY_VALUE_UNITS = [
|
|
|
41
47
|
const NUMBER_RE = /^-?(?:\d+|\d*\.\d+)$/
|
|
42
48
|
const FUNCTION_VALUE_RE = /^[a-z_-][\w-]*\(/i
|
|
43
49
|
const HEX_ESCAPE_RE = /^[\da-f]$/i
|
|
50
|
+
const ASPECT_RATIO_RE = /^\d+\/\d+$/
|
|
51
|
+
const ESCAPED_WHITESPACE_RE = /\\[nrt]/g
|
|
44
52
|
|
|
45
53
|
function splitVariantPrefix(candidate: string) {
|
|
46
54
|
let depth = 0
|
|
@@ -221,6 +229,10 @@ function normalizeBareArbitraryValueOptions(options: boolean | BareArbitraryValu
|
|
|
221
229
|
}
|
|
222
230
|
}
|
|
223
231
|
|
|
232
|
+
export function isBareArbitraryValuesEnabled(options: boolean | BareArbitraryValueOptions | undefined) {
|
|
233
|
+
return normalizeBareArbitraryValueOptions(options) !== undefined
|
|
234
|
+
}
|
|
235
|
+
|
|
224
236
|
function normalizeEscapedValue(value: string) {
|
|
225
237
|
let result = ''
|
|
226
238
|
for (let index = 0; index < value.length; index++) {
|
|
@@ -276,13 +288,17 @@ function resolveValueWithUnit(body: string, units: string[]) {
|
|
|
276
288
|
}
|
|
277
289
|
}
|
|
278
290
|
|
|
279
|
-
function resolveArbitraryValue(body: string, units: string[]) {
|
|
291
|
+
function resolveArbitraryValue(utility: string, body: string, units: string[]) {
|
|
280
292
|
const value = normalizeEscapedValue(body)
|
|
281
293
|
const withUnit = resolveValueWithUnit(value, units)
|
|
282
294
|
if (withUnit) {
|
|
283
295
|
return withUnit
|
|
284
296
|
}
|
|
285
297
|
|
|
298
|
+
if (utility === 'aspect' && ASPECT_RATIO_RE.test(value)) {
|
|
299
|
+
return value
|
|
300
|
+
}
|
|
301
|
+
|
|
286
302
|
if (isHexColorValue(value)) {
|
|
287
303
|
return value
|
|
288
304
|
}
|
|
@@ -292,6 +308,9 @@ function resolveArbitraryValue(body: string, units: string[]) {
|
|
|
292
308
|
}
|
|
293
309
|
|
|
294
310
|
if (FUNCTION_VALUE_RE.test(value) && value.endsWith(')') && isBalancedFunctionValue(value)) {
|
|
311
|
+
if (utility === 'text' && /^var\(/i.test(value)) {
|
|
312
|
+
return `color:${value}`
|
|
313
|
+
}
|
|
295
314
|
return value
|
|
296
315
|
}
|
|
297
316
|
}
|
|
@@ -339,7 +358,7 @@ function resolveUtilityAndValue(body: string, units: string[]) {
|
|
|
339
358
|
continue
|
|
340
359
|
}
|
|
341
360
|
|
|
342
|
-
const value = resolveArbitraryValue(rawValue, units)
|
|
361
|
+
const value = resolveArbitraryValue(utility, rawValue, units)
|
|
343
362
|
if (value) {
|
|
344
363
|
return {
|
|
345
364
|
utility,
|
|
@@ -380,6 +399,112 @@ export function resolveBareArbitraryValueCandidate(
|
|
|
380
399
|
}
|
|
381
400
|
}
|
|
382
401
|
|
|
402
|
+
function isBareArbitrarySourceSplitter(char: string) {
|
|
403
|
+
return /\s/.test(char)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function isQuoteBoundary(content: string, start: number, index: number) {
|
|
407
|
+
const tokenPrefix = content.slice(start, index)
|
|
408
|
+
return tokenPrefix.length === 0 || !tokenPrefix.endsWith('-')
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function trimBareArbitrarySourceToken(token: string, start: number) {
|
|
412
|
+
let nextToken = token
|
|
413
|
+
let nextStart = start
|
|
414
|
+
while (nextToken.length > 0 && /^[<{([]$/.test(nextToken[0]!)) {
|
|
415
|
+
nextToken = nextToken.slice(1)
|
|
416
|
+
nextStart++
|
|
417
|
+
}
|
|
418
|
+
while (nextToken.length > 0 && /^[>\],;]$/.test(nextToken[nextToken.length - 1]!)) {
|
|
419
|
+
nextToken = nextToken.slice(0, -1)
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
token: nextToken,
|
|
423
|
+
start: nextStart,
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function pushBareArbitrarySourceCandidate(
|
|
428
|
+
result: BareArbitraryValueSourceCandidate[],
|
|
429
|
+
token: string,
|
|
430
|
+
start: number,
|
|
431
|
+
options: boolean | BareArbitraryValueOptions | undefined,
|
|
432
|
+
) {
|
|
433
|
+
const trimmed = trimBareArbitrarySourceToken(token, start)
|
|
434
|
+
if (!trimmed.token || trimmed.token.includes('=') || trimmed.token.includes('[') || trimmed.token.includes(']')) {
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
if (!resolveBareArbitraryValueCandidate(trimmed.token, options)) {
|
|
438
|
+
return
|
|
439
|
+
}
|
|
440
|
+
result.push({
|
|
441
|
+
rawCandidate: trimmed.token,
|
|
442
|
+
start: trimmed.start,
|
|
443
|
+
end: trimmed.start + trimmed.token.length,
|
|
444
|
+
})
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export function extractBareArbitraryValueSourceCandidatesWithPositions(
|
|
448
|
+
content: string,
|
|
449
|
+
options?: boolean | BareArbitraryValueOptions,
|
|
450
|
+
): BareArbitraryValueSourceCandidate[] {
|
|
451
|
+
if (!isBareArbitraryValuesEnabled(options)) {
|
|
452
|
+
return []
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const normalized = content.includes('\\') ? content.replace(ESCAPED_WHITESPACE_RE, ' ') : content
|
|
456
|
+
const result: BareArbitraryValueSourceCandidate[] = []
|
|
457
|
+
let depth = 0
|
|
458
|
+
let quote: string | undefined
|
|
459
|
+
let start = 0
|
|
460
|
+
|
|
461
|
+
for (let index = 0; index < normalized.length; index++) {
|
|
462
|
+
const char = normalized[index]
|
|
463
|
+
if (char === undefined) {
|
|
464
|
+
continue
|
|
465
|
+
}
|
|
466
|
+
if (char === '\\') {
|
|
467
|
+
index++
|
|
468
|
+
continue
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (quote) {
|
|
472
|
+
if (char === quote) {
|
|
473
|
+
quote = undefined
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else if ((char === '"' || char === '\'' || char === '`') && !isQuoteBoundary(normalized, start, index)) {
|
|
477
|
+
quote = char
|
|
478
|
+
}
|
|
479
|
+
else if (char === '(' || char === '{' || char === '[') {
|
|
480
|
+
depth++
|
|
481
|
+
}
|
|
482
|
+
else if (char === ')' || char === '}' || char === ']') {
|
|
483
|
+
depth = Math.max(0, depth - 1)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!isBareArbitrarySourceSplitter(char) && !((char === '"' || char === '\'' || char === '`') && depth === 0 && isQuoteBoundary(normalized, start, index))) {
|
|
487
|
+
continue
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
pushBareArbitrarySourceCandidate(result, normalized.slice(start, index), start, options)
|
|
491
|
+
start = index + 1
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
pushBareArbitrarySourceCandidate(result, normalized.slice(start), start, options)
|
|
495
|
+
return result
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export function extractBareArbitraryValueSourceCandidates(
|
|
499
|
+
content: string,
|
|
500
|
+
options?: boolean | BareArbitraryValueOptions,
|
|
501
|
+
) {
|
|
502
|
+
return [...new Set(
|
|
503
|
+
extractBareArbitraryValueSourceCandidatesWithPositions(content, options)
|
|
504
|
+
.map(candidate => candidate.rawCandidate),
|
|
505
|
+
)]
|
|
506
|
+
}
|
|
507
|
+
|
|
383
508
|
// Based on the CSS.escape algorithm, scoped to class selector escaping.
|
|
384
509
|
export function escapeCssClassName(value: string) {
|
|
385
510
|
let result = ''
|
package/src/v4/engine.ts
CHANGED
|
@@ -37,13 +37,16 @@ async function collectRawCandidates(
|
|
|
37
37
|
compiledSources: TailwindV4SourcePattern[] = [],
|
|
38
38
|
) {
|
|
39
39
|
const rawCandidates = new Set<string>()
|
|
40
|
+
const extractOptions = options?.bareArbitraryValues === undefined
|
|
41
|
+
? undefined
|
|
42
|
+
: { bareArbitraryValues: options.bareArbitraryValues }
|
|
40
43
|
|
|
41
44
|
for (const candidate of options?.candidates ?? []) {
|
|
42
45
|
rawCandidates.add(candidate)
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
for (const candidateSource of options?.sources ?? []) {
|
|
46
|
-
const candidates = await extractRawCandidatesWithPositions(candidateSource.content, candidateSource.extension)
|
|
49
|
+
const candidates = await extractRawCandidatesWithPositions(candidateSource.content, candidateSource.extension, extractOptions)
|
|
47
50
|
for (const candidate of candidates) {
|
|
48
51
|
rawCandidates.add(candidate.rawCandidate)
|
|
49
52
|
}
|
|
@@ -51,7 +54,7 @@ async function collectRawCandidates(
|
|
|
51
54
|
|
|
52
55
|
const filesystemSources = resolveScanSources(options, source, compiledRoot, compiledSources)
|
|
53
56
|
if (filesystemSources.length > 0) {
|
|
54
|
-
for (const candidate of await extractRawCandidates(filesystemSources)) {
|
|
57
|
+
for (const candidate of await extractRawCandidates(filesystemSources, extractOptions)) {
|
|
55
58
|
rawCandidates.add(candidate)
|
|
56
59
|
}
|
|
57
60
|
}
|
package/src/v4/index.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
export {
|
|
2
|
+
escapeCssClassName,
|
|
3
|
+
extractBareArbitraryValueSourceCandidates,
|
|
4
|
+
extractBareArbitraryValueSourceCandidatesWithPositions,
|
|
5
|
+
isBareArbitraryValuesEnabled,
|
|
6
|
+
resolveBareArbitraryValueCandidate,
|
|
7
|
+
} from './bare-arbitrary-values'
|
|
8
|
+
export {
|
|
9
|
+
canonicalizeBareArbitraryValueCandidates,
|
|
2
10
|
extractTailwindV4InlineSourceCandidates,
|
|
11
|
+
replaceBareArbitraryValueSelectors,
|
|
3
12
|
resolveValidTailwindV4Candidates,
|
|
4
13
|
} from './candidates'
|
|
5
14
|
export { createTailwindV4Engine } from './engine'
|
|
@@ -8,6 +17,11 @@ export {
|
|
|
8
17
|
loadTailwindV4DesignSystem,
|
|
9
18
|
loadTailwindV4NodeModule,
|
|
10
19
|
} from './node-adapter'
|
|
20
|
+
export {
|
|
21
|
+
resolveTailwindV4Source,
|
|
22
|
+
resolveTailwindV4SourceFromPatchOptions,
|
|
23
|
+
tailwindV4SourceOptionsFromPatchOptions,
|
|
24
|
+
} from './source'
|
|
11
25
|
export {
|
|
12
26
|
createTailwindV4CompiledSourceEntries,
|
|
13
27
|
createTailwindV4DefaultIgnoreSources,
|
|
@@ -30,10 +44,9 @@ export {
|
|
|
30
44
|
TAILWIND_V4_IGNORED_FILES,
|
|
31
45
|
} from './source-scan'
|
|
32
46
|
export {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} from './source'
|
|
47
|
+
collectTailwindV4StyleCandidates,
|
|
48
|
+
generateTailwindV4Style,
|
|
49
|
+
} from './style-generator'
|
|
37
50
|
export type {
|
|
38
51
|
TailwindV4CandidateSource,
|
|
39
52
|
TailwindV4CompiledSourceRoot,
|
|
@@ -45,4 +58,7 @@ export type {
|
|
|
45
58
|
TailwindV4ResolvedSource,
|
|
46
59
|
TailwindV4SourceOptions,
|
|
47
60
|
TailwindV4SourcePattern,
|
|
61
|
+
TailwindV4StyleGenerateOptions,
|
|
62
|
+
TailwindV4StyleGenerateResult,
|
|
63
|
+
TailwindV4StyleSource,
|
|
48
64
|
} from './types'
|
package/src/v4/node-adapter.ts
CHANGED
package/src/v4/source-scan.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type { TailwindV4CompiledSourceRoot, TailwindV4SourcePattern } from './ty
|
|
|
3
3
|
import { realpathSync } from 'node:fs'
|
|
4
4
|
import { stat } from 'node:fs/promises'
|
|
5
5
|
import process from 'node:process'
|
|
6
|
-
import path from 'pathe'
|
|
7
6
|
import micromatch from 'micromatch'
|
|
7
|
+
import path from 'pathe'
|
|
8
8
|
|
|
9
9
|
export const TAILWIND_V4_IGNORED_CONTENT_DIRS = [
|
|
10
10
|
'.git',
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TailwindV4SourceOptions,
|
|
3
|
+
TailwindV4StyleGenerateOptions,
|
|
4
|
+
TailwindV4StyleGenerateResult,
|
|
5
|
+
} from './types'
|
|
6
|
+
import { collectTailwindStyleCandidates } from '../style-candidates'
|
|
7
|
+
import { createTailwindV4Engine } from './engine'
|
|
8
|
+
import { resolveTailwindV4Source } from './source'
|
|
9
|
+
|
|
10
|
+
function createSourceOptions(options: TailwindV4StyleGenerateOptions): TailwindV4SourceOptions {
|
|
11
|
+
return {
|
|
12
|
+
...(options.projectRoot === undefined ? {} : { projectRoot: options.projectRoot }),
|
|
13
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
14
|
+
...(options.base === undefined ? {} : { base: options.base }),
|
|
15
|
+
...(options.baseFallbacks === undefined ? {} : { baseFallbacks: options.baseFallbacks }),
|
|
16
|
+
...(options.css === undefined ? {} : { css: options.css }),
|
|
17
|
+
...(options.cssSources === undefined ? {} : { cssSources: options.cssSources }),
|
|
18
|
+
...(options.cssEntries === undefined ? {} : { cssEntries: options.cssEntries }),
|
|
19
|
+
...(options.packageName === undefined ? {} : { packageName: options.packageName }),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function collectTailwindV4StyleCandidates(
|
|
24
|
+
options: Pick<TailwindV4StyleGenerateOptions, 'bareArbitraryValues' | 'candidates' | 'sources'>,
|
|
25
|
+
): Promise<Set<string>> {
|
|
26
|
+
return collectTailwindStyleCandidates(options)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function generateTailwindV4Style(
|
|
30
|
+
options: TailwindV4StyleGenerateOptions = {},
|
|
31
|
+
): Promise<TailwindV4StyleGenerateResult> {
|
|
32
|
+
const source = options.source ?? await resolveTailwindV4Source(createSourceOptions(options))
|
|
33
|
+
const candidates = await collectTailwindV4StyleCandidates(options)
|
|
34
|
+
const result = await createTailwindV4Engine(source).generate({
|
|
35
|
+
candidates,
|
|
36
|
+
...(options.bareArbitraryValues === undefined ? {} : { bareArbitraryValues: options.bareArbitraryValues }),
|
|
37
|
+
...(options.scanSources === undefined ? {} : { scanSources: options.scanSources }),
|
|
38
|
+
})
|
|
39
|
+
return {
|
|
40
|
+
...result,
|
|
41
|
+
tokens: result.rawCandidates,
|
|
42
|
+
source,
|
|
43
|
+
}
|
|
44
|
+
}
|