Skip to content

Commit 1af573b

Browse files
committed
chore: bump version
1 parent c3735db commit 1af573b

File tree

5 files changed

+186
-11
lines changed

5 files changed

+186
-11
lines changed

.changeset/relative-config-base.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'tailwindcss-patch': patch
3+
---
4+
5+
Ensure Tailwind v4 CSS entries resolve `@config` paths relative to each CSS file when no explicit base is provided.

packages/tailwindcss-patch/src/options/normalize.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,14 @@ function normalizeTailwindV4Options(
125125
v4: TailwindV4UserOptions | undefined,
126126
fallbackBase: string,
127127
): NormalizedTailwindV4Options {
128-
const base = v4?.base ? path.resolve(v4.base) : fallbackBase
128+
const configuredBase = v4?.base ? path.resolve(v4.base) : undefined
129+
const base = configuredBase ?? fallbackBase
129130
const cssEntries = Array.isArray(v4?.cssEntries)
130131
? v4!.cssEntries.filter((entry): entry is string => Boolean(entry)).map(entry => path.resolve(entry))
131132
: []
132-
const sources = v4?.sources?.length
133-
? v4.sources
133+
const hasUserDefinedSources = Boolean(v4?.sources?.length)
134+
const sources = hasUserDefinedSources
135+
? v4!.sources
134136
: [
135137
{
136138
base,
@@ -141,9 +143,11 @@ function normalizeTailwindV4Options(
141143

142144
return {
143145
base,
146+
configuredBase,
144147
css: v4?.css,
145148
cssEntries,
146149
sources,
150+
hasUserDefinedSources,
147151
}
148152
}
149153

packages/tailwindcss-patch/src/options/types.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,75 +4,140 @@ import type { ILengthUnitsPatchOptions } from '../types'
44

55
export type CacheStrategy = 'merge' | 'overwrite'
66

7+
/**
8+
* Configures how the Tailwind class cache is stored and where it lives on disk.
9+
*/
710
export interface CacheUserOptions {
11+
/** Whether caching is enabled. */
812
enabled?: boolean
13+
/** Working directory used when resolving cache paths. */
914
cwd?: string
15+
/** Directory where cache files are written. */
1016
dir?: string
17+
/**
18+
* Cache filename. Defaults to `class-cache.json` inside the derived cache folder
19+
* when omitted.
20+
*/
1121
file?: string
22+
/** Strategy used when merging new class lists with an existing cache. */
1223
strategy?: CacheStrategy
1324
}
1425

26+
/**
27+
* Controls how extracted class lists are written to disk.
28+
*/
1529
export interface OutputUserOptions {
30+
/** Whether to produce an output file. */
1631
enabled?: boolean
32+
/** Optional absolute or relative path to the output file. */
1733
file?: string
34+
/** Output format, defaults to JSON when omitted. */
1835
format?: 'json' | 'lines'
36+
/** Pretty-print spacing (truthy value enables indentation). */
1937
pretty?: number | boolean
38+
/** Whether to strip the universal selector (`*`) from the final list. */
2039
removeUniversalSelector?: boolean
2140
}
2241

42+
/**
43+
* Options controlling how Tailwind contexts are exposed during runtime patching.
44+
*/
2345
export interface ExposeContextUserOptions {
46+
/** Name of the property used to reference an exposed context. */
2447
refProperty?: string
2548
}
2649

50+
/**
51+
* Extends the built-in length-unit patch with custom defaults.
52+
*/
2753
export interface ExtendLengthUnitsUserOptions extends Partial<ILengthUnitsPatchOptions> {
54+
/** Enables or disables the length-unit patch. */
2855
enabled?: boolean
2956
}
3057

58+
/**
59+
* Feature switches that toggle optional Tailwind patch capabilities.
60+
*/
3161
export interface FeatureUserOptions {
62+
/** Whether to expose runtime Tailwind contexts (or configure how they are exposed). */
3263
exposeContext?: boolean | ExposeContextUserOptions
64+
/** Extends the length-unit patch or disables it entirely. */
3365
extendLengthUnits?: false | ExtendLengthUnitsUserOptions
3466
}
3567

68+
/**
69+
* Shared configuration used for Tailwind v2/v3 patching flows.
70+
*/
3671
export interface TailwindConfigUserOptions {
72+
/** Path to a Tailwind config file when auto-detection is insufficient. */
3773
config?: string
74+
/** Custom working directory used when resolving config-relative paths. */
3875
cwd?: string
76+
/** Optional PostCSS plugin name to use instead of the default. */
3977
postcssPlugin?: string
4078
}
4179

80+
/**
81+
* Additional configuration specific to Tailwind CSS v4 extraction.
82+
*/
4283
export interface TailwindV4UserOptions {
84+
/** Base directory used when resolving v4 content sources and configs. */
4385
base?: string
86+
/** Raw CSS passed directly to the v4 design system. */
4487
css?: string
88+
/** Set of CSS entry files that should be scanned for `@config` directives. */
4589
cssEntries?: string[]
90+
/** Overrides the content sources scanned by the oxide scanner. */
4691
sources?: SourceEntry[]
4792
}
4893

94+
/**
95+
* High-level Tailwind patch configuration shared across versions.
96+
*/
4997
export interface TailwindUserOptions extends TailwindConfigUserOptions {
5098
/**
5199
* Optional hint for picking the patch strategy.
52100
* When omitted we infer from the installed Tailwind CSS package version.
53101
*/
54102
version?: 2 | 3 | 4
103+
/** Tailwind package name if the project uses a fork. */
55104
packageName?: string
105+
/** Package resolution options forwarded to `local-pkg`. */
56106
resolve?: PackageResolvingOptions
107+
/** Overrides applied when patching Tailwind CSS v2. */
57108
v2?: TailwindConfigUserOptions
109+
/** Overrides applied when patching Tailwind CSS v3. */
58110
v3?: TailwindConfigUserOptions
111+
/** Options specific to Tailwind CSS v4 patching. */
59112
v4?: TailwindV4UserOptions
60113
}
61114

115+
/**
116+
* Root configuration consumed by the Tailwind CSS patch runner.
117+
*/
62118
export interface TailwindcssPatchOptions {
63119
/**
64120
* Base directory used when resolving Tailwind resources.
65121
* Defaults to `process.cwd()`.
66122
*/
67123
cwd?: string
124+
/** Whether to overwrite generated artifacts (e.g., caches, outputs). */
68125
overwrite?: boolean
126+
/** Tailwind-specific configuration grouped by major version. */
69127
tailwind?: TailwindUserOptions
128+
/** Feature toggles for optional helpers. */
70129
features?: FeatureUserOptions
130+
/** Optional function that filters final class names. */
71131
filter?: (className: string) => boolean
132+
/** Cache configuration or boolean to enable/disable quickly. */
72133
cache?: boolean | CacheUserOptions
134+
/** Output configuration or boolean to inherits defaults. */
73135
output?: OutputUserOptions
74136
}
75137

138+
/**
139+
* Stable shape for output configuration after normalization.
140+
*/
76141
export interface NormalizedOutputOptions {
77142
enabled: boolean
78143
file?: string
@@ -81,6 +146,9 @@ export interface NormalizedOutputOptions {
81146
removeUniversalSelector: boolean
82147
}
83148

149+
/**
150+
* Stable cache configuration used internally after defaults are applied.
151+
*/
84152
export interface NormalizedCacheOptions {
85153
enabled: boolean
86154
cwd: string
@@ -90,22 +158,30 @@ export interface NormalizedCacheOptions {
90158
strategy: CacheStrategy
91159
}
92160

161+
/** Tracks whether runtime contexts should be exposed and via which property. */
93162
export interface NormalizedExposeContextOptions {
94163
enabled: boolean
95164
refProperty: string
96165
}
97166

167+
/** Normalized representation of the extend-length-units feature flag. */
98168
export interface NormalizedExtendLengthUnitsOptions extends ILengthUnitsPatchOptions {
99169
enabled: boolean
100170
}
101171

172+
/** Normalized Tailwind v4 configuration consumed by runtime helpers. */
102173
export interface NormalizedTailwindV4Options {
103174
base: string
175+
configuredBase?: string
104176
css?: string
105177
cssEntries: string[]
106178
sources: SourceEntry[]
179+
hasUserDefinedSources: boolean
107180
}
108181

182+
/**
183+
* Tailwind configuration ready for consumption by the runtime after normalization.
184+
*/
109185
export interface NormalizedTailwindConfigOptions extends TailwindConfigUserOptions {
110186
packageName: string
111187
versionHint?: 2 | 3 | 4
@@ -115,11 +191,13 @@ export interface NormalizedTailwindConfigOptions extends TailwindConfigUserOptio
115191
v4?: NormalizedTailwindV4Options
116192
}
117193

194+
/** Grouped normalized feature flags. */
118195
export interface NormalizedFeatureOptions {
119196
exposeContext: NormalizedExposeContextOptions
120197
extendLengthUnits: NormalizedExtendLengthUnitsOptions | null
121198
}
122199

200+
/** Final normalized shape consumed throughout the patch runtime. */
123201
export interface NormalizedTailwindcssPatchOptions {
124202
projectRoot: string
125203
overwrite: boolean

packages/tailwindcss-patch/src/runtime/class-collector.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,33 @@ export async function collectClassesFromTailwindV4(
3535
return set
3636
}
3737

38-
const sources = v4Options.sources?.map((source) => {
39-
return {
40-
base: source.base ?? v4Options.base ?? process.cwd(),
38+
const toAbsolute = (value: string | undefined) => {
39+
if (!value) {
40+
return undefined
41+
}
42+
return path.isAbsolute(value) ? value : path.resolve(options.projectRoot, value)
43+
}
44+
const resolvedConfiguredBase = toAbsolute(v4Options.configuredBase)
45+
const resolvedDefaultBase = toAbsolute(v4Options.base) ?? process.cwd()
46+
const resolveSources = (base: string) => {
47+
if (!v4Options.sources?.length) {
48+
return undefined
49+
}
50+
51+
if (!v4Options.hasUserDefinedSources && !resolvedConfiguredBase) {
52+
return v4Options.sources.map(source => ({
53+
base,
54+
pattern: source.pattern,
55+
negated: source.negated,
56+
}))
57+
}
58+
59+
return v4Options.sources.map(source => ({
60+
base: source.base ?? base,
4161
pattern: source.pattern,
4262
negated: source.negated,
43-
}
44-
})
63+
}))
64+
}
4565

4666
if (v4Options.cssEntries.length > 0) {
4767
for (const entry of v4Options.cssEntries) {
@@ -50,9 +70,12 @@ export async function collectClassesFromTailwindV4(
5070
continue
5171
}
5272
const css = await fs.readFile(filePath, 'utf8')
73+
const entryDir = path.dirname(filePath)
74+
const baseForEntry = resolvedConfiguredBase ?? entryDir
75+
const sources = resolveSources(baseForEntry)
5376
const candidates = await extractValidCandidates({
5477
cwd: options.projectRoot,
55-
base: v4Options.base,
78+
base: baseForEntry,
5679
css,
5780
sources,
5881
})
@@ -64,9 +87,11 @@ export async function collectClassesFromTailwindV4(
6487
}
6588
}
6689
else {
90+
const baseForCss = resolvedConfiguredBase ?? resolvedDefaultBase
91+
const sources = resolveSources(baseForCss)
6792
const candidates = await extractValidCandidates({
6893
cwd: options.projectRoot,
69-
base: v4Options.base,
94+
base: baseForCss,
7095
css: v4Options.css,
7196
sources,
7297
})

packages/tailwindcss-patch/test/runtime.class-collector.test.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import os from 'node:os'
2+
import fs from 'fs-extra'
3+
import path from 'pathe'
14
import { describe, expect, it } from 'vitest'
2-
import { collectClassesFromContexts } from '@/runtime/class-collector'
5+
import { collectClassesFromContexts, collectClassesFromTailwindV4 } from '@/runtime/class-collector'
6+
import { normalizeOptions } from '@/options/normalize'
37

48
function createContext(classes: string[]) {
59
const map = new Map()
@@ -20,3 +24,62 @@ describe('collectClassesFromContexts', () => {
2024
expect(result.has('*')).toBe(false)
2125
})
2226
})
27+
28+
describe('collectClassesFromTailwindV4', () => {
29+
it('resolves @config relative to the CSS entry directory', async () => {
30+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'tw-v4-class-collector-'))
31+
try {
32+
const cssDir = path.join(tempDir, 'src')
33+
await fs.ensureDir(cssDir)
34+
35+
const configPath = path.join(tempDir, 'tailwind.config.js')
36+
await fs.writeFile(
37+
configPath,
38+
[
39+
'module.exports = {',
40+
' content: [],',
41+
' theme: {',
42+
' extend: {',
43+
' colors: {',
44+
" brand: '#534312',",
45+
' },',
46+
' },',
47+
' },',
48+
'};',
49+
].join('\n'),
50+
'utf8',
51+
)
52+
53+
const cssPath = path.join(cssDir, 'app.css')
54+
await fs.writeFile(
55+
cssPath,
56+
[
57+
'@config "../tailwind.config.js";',
58+
'@utility bg-[#534312] {',
59+
' background-color: #534312;',
60+
'}',
61+
].join('\n'),
62+
'utf8',
63+
)
64+
65+
const usageFile = path.join(cssDir, 'index.html')
66+
await fs.writeFile(usageFile, '<div class="bg-[#534312]"></div>', 'utf8')
67+
68+
const normalized = normalizeOptions({
69+
cwd: tempDir,
70+
tailwind: {
71+
version: 4,
72+
v4: {
73+
cssEntries: [cssPath],
74+
},
75+
},
76+
})
77+
78+
const classes = await collectClassesFromTailwindV4(normalized)
79+
expect(classes.has('bg-[#534312]')).toBe(true)
80+
}
81+
finally {
82+
await fs.remove(tempDir)
83+
}
84+
})
85+
})

0 commit comments

Comments
 (0)