@tamagui/tailwind 0.0.0-bootstrap.0 → 3.0.0-beta.643.1
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/cjs/candidate.cjs +385 -0
- package/dist/cjs/candidate.native.cjs +431 -0
- package/dist/cjs/candidate.native.js +433 -0
- package/dist/cjs/candidate.native.js.map +1 -0
- package/dist/cjs/frontend.cjs +80 -0
- package/dist/cjs/frontend.native.cjs +95 -0
- package/dist/cjs/frontend.native.js +97 -0
- package/dist/cjs/frontend.native.js.map +1 -0
- package/dist/cjs/index.cjs +39 -0
- package/dist/cjs/index.native.cjs +41 -0
- package/dist/cjs/index.native.js +43 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/styled.cjs +30 -0
- package/dist/cjs/styled.native.cjs +32 -0
- package/dist/cjs/styled.native.js +34 -0
- package/dist/cjs/styled.native.js.map +1 -0
- package/dist/cjs/types.cjs +17 -0
- package/dist/cjs/types.native.cjs +19 -0
- package/dist/cjs/types.native.js +21 -0
- package/dist/cjs/types.native.js.map +1 -0
- package/dist/cjs/vite/state.cjs +249 -0
- package/dist/cjs/vite/state.native.cjs +331 -0
- package/dist/cjs/vite/state.native.js +333 -0
- package/dist/cjs/vite/state.native.js.map +1 -0
- package/dist/cjs/vite.cjs +148 -0
- package/dist/cjs/vite.native.cjs +188 -0
- package/dist/cjs/vite.native.js +190 -0
- package/dist/cjs/vite.native.js.map +1 -0
- package/dist/esm/candidate.mjs +360 -0
- package/dist/esm/candidate.mjs.map +1 -0
- package/dist/esm/candidate.native.js +404 -0
- package/dist/esm/candidate.native.js.map +1 -0
- package/dist/esm/frontend.mjs +57 -0
- package/dist/esm/frontend.mjs.map +1 -0
- package/dist/esm/frontend.native.js +70 -0
- package/dist/esm/frontend.native.js.map +1 -0
- package/dist/esm/index.mjs +11 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +11 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/styled.mjs +10 -0
- package/dist/esm/styled.mjs.map +1 -0
- package/dist/esm/styled.native.js +10 -0
- package/dist/esm/styled.native.js.map +1 -0
- package/dist/esm/types.mjs +0 -0
- package/dist/esm/types.native.js +0 -0
- package/dist/esm/vite/state.mjs +215 -0
- package/dist/esm/vite/state.mjs.map +1 -0
- package/dist/esm/vite/state.native.js +295 -0
- package/dist/esm/vite/state.native.js.map +1 -0
- package/dist/esm/vite.mjs +116 -0
- package/dist/esm/vite.mjs.map +1 -0
- package/dist/esm/vite.native.js +154 -0
- package/dist/esm/vite.native.js.map +1 -0
- package/package.json +88 -5
- package/src/candidate.ts +527 -0
- package/src/frontend.ts +116 -0
- package/src/index.tsx +24 -0
- package/src/styled.tsx +52 -0
- package/src/types.ts +226 -0
- package/src/vite/state.ts +274 -0
- package/src/vite.ts +204 -0
- package/types/candidate.d.ts +23 -0
- package/types/candidate.d.ts.map +1 -0
- package/types/frontend.d.ts +20 -0
- package/types/frontend.d.ts.map +1 -0
- package/types/index.d.ts +8 -0
- package/types/index.d.ts.map +1 -0
- package/types/styled.d.ts +12 -0
- package/types/styled.d.ts.map +1 -0
- package/types/types.d.ts +158 -0
- package/types/types.d.ts.map +1 -0
- package/types/vite/state.d.ts +28 -0
- package/types/vite/state.d.ts.map +1 -0
- package/types/vite.d.ts +20 -0
- package/types/vite.d.ts.map +1 -0
- package/README.md +0 -1
package/src/styled.tsx
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createFrontendStyled } from '@tamagui/core/internal-runtime'
|
|
2
|
+
import { tailwindStyleFrontend } from './frontend'
|
|
3
|
+
import type {
|
|
4
|
+
GetTailwindNonStyleProps,
|
|
5
|
+
GetTailwindRef,
|
|
6
|
+
GetTailwindVariantProps,
|
|
7
|
+
TailwindComponent,
|
|
8
|
+
TailwindStyledOptions,
|
|
9
|
+
TailwindVariantDefinitions,
|
|
10
|
+
TailwindVariantProps,
|
|
11
|
+
} from './types'
|
|
12
|
+
|
|
13
|
+
const styledWithFrontend = createFrontendStyled(tailwindStyleFrontend)
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Class-first `styled()`. The base is a class string, variant values are class
|
|
17
|
+
* strings, and the resulting component accepts `className` plus its variants —
|
|
18
|
+
* never Tamagui inline style props.
|
|
19
|
+
*
|
|
20
|
+
* The class base and variant values are parsed once per (component, config) pair
|
|
21
|
+
* by the frontend's `normalizeStaticConfig`, so authoring cost is not per render.
|
|
22
|
+
*/
|
|
23
|
+
export function styled<
|
|
24
|
+
Parent extends TailwindComponent<any, any, any>,
|
|
25
|
+
Variants extends TailwindVariantDefinitions = {},
|
|
26
|
+
>(
|
|
27
|
+
Component: Parent,
|
|
28
|
+
baseClassName: string,
|
|
29
|
+
options?: TailwindStyledOptions<Variants>
|
|
30
|
+
): TailwindComponent<
|
|
31
|
+
GetTailwindRef<Parent>,
|
|
32
|
+
GetTailwindNonStyleProps<Parent>,
|
|
33
|
+
GetTailwindVariantProps<Parent> & TailwindVariantProps<Variants>
|
|
34
|
+
>
|
|
35
|
+
export function styled<
|
|
36
|
+
Parent extends TailwindComponent<any, any, any>,
|
|
37
|
+
Variants extends TailwindVariantDefinitions = {},
|
|
38
|
+
>(
|
|
39
|
+
Component: Parent,
|
|
40
|
+
options?: TailwindStyledOptions<Variants>
|
|
41
|
+
): TailwindComponent<
|
|
42
|
+
GetTailwindRef<Parent>,
|
|
43
|
+
GetTailwindNonStyleProps<Parent>,
|
|
44
|
+
GetTailwindVariantProps<Parent> & TailwindVariantProps<Variants>
|
|
45
|
+
>
|
|
46
|
+
export function styled(
|
|
47
|
+
Component: any,
|
|
48
|
+
optionsOrBaseClassName?: any,
|
|
49
|
+
maybeOptions?: any
|
|
50
|
+
): any {
|
|
51
|
+
return styledWithFrontend(Component, optionsOrBaseClassName, maybeOptions)
|
|
52
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { FrontendStaticConfig } from '@tamagui/core/internal-runtime'
|
|
2
|
+
import type {
|
|
3
|
+
AriaAttributes,
|
|
4
|
+
CSSProperties,
|
|
5
|
+
FunctionComponent,
|
|
6
|
+
HTMLAttributes,
|
|
7
|
+
ReactElement,
|
|
8
|
+
ReactNode,
|
|
9
|
+
Ref as ReactRef,
|
|
10
|
+
} from 'react'
|
|
11
|
+
import type {
|
|
12
|
+
PressableProps,
|
|
13
|
+
StyleProp,
|
|
14
|
+
Text as ReactNativeText,
|
|
15
|
+
TextProps as ReactNativeTextProps,
|
|
16
|
+
TextStyle,
|
|
17
|
+
View as ReactNativeView,
|
|
18
|
+
ViewProps as ReactNativeViewProps,
|
|
19
|
+
ViewStyle,
|
|
20
|
+
} from 'react-native'
|
|
21
|
+
|
|
22
|
+
type WebEventProps = {
|
|
23
|
+
onMouseEnter?: HTMLAttributes<HTMLDivElement>['onMouseEnter']
|
|
24
|
+
onMouseLeave?: HTMLAttributes<HTMLDivElement>['onMouseLeave']
|
|
25
|
+
onMouseDown?: HTMLAttributes<HTMLDivElement>['onMouseDown']
|
|
26
|
+
onMouseUp?: HTMLAttributes<HTMLDivElement>['onMouseUp']
|
|
27
|
+
onMouseMove?: HTMLAttributes<HTMLDivElement>['onMouseMove']
|
|
28
|
+
onMouseOver?: HTMLAttributes<HTMLDivElement>['onMouseOver']
|
|
29
|
+
onMouseOut?: HTMLAttributes<HTMLDivElement>['onMouseOut']
|
|
30
|
+
onFocus?: HTMLAttributes<HTMLDivElement>['onFocus']
|
|
31
|
+
onBlur?: HTMLAttributes<HTMLDivElement>['onBlur']
|
|
32
|
+
onClick?: HTMLAttributes<HTMLDivElement>['onClick']
|
|
33
|
+
onDoubleClick?: HTMLAttributes<HTMLDivElement>['onDoubleClick']
|
|
34
|
+
onContextMenu?: HTMLAttributes<HTMLDivElement>['onContextMenu']
|
|
35
|
+
onWheel?: HTMLAttributes<HTMLDivElement>['onWheel']
|
|
36
|
+
onKeyDown?: HTMLAttributes<HTMLDivElement>['onKeyDown']
|
|
37
|
+
onKeyUp?: HTMLAttributes<HTMLDivElement>['onKeyUp']
|
|
38
|
+
onChange?: HTMLAttributes<HTMLDivElement>['onChange']
|
|
39
|
+
onInput?: HTMLAttributes<HTMLDivElement>['onInput']
|
|
40
|
+
onBeforeInput?: HTMLAttributes<HTMLDivElement>['onBeforeInput']
|
|
41
|
+
onScroll?: HTMLAttributes<HTMLDivElement>['onScroll']
|
|
42
|
+
onCopy?: HTMLAttributes<HTMLDivElement>['onCopy']
|
|
43
|
+
onCut?: HTMLAttributes<HTMLDivElement>['onCut']
|
|
44
|
+
onPaste?: HTMLAttributes<HTMLDivElement>['onPaste']
|
|
45
|
+
onDrag?: HTMLAttributes<HTMLDivElement>['onDrag']
|
|
46
|
+
onDragStart?: HTMLAttributes<HTMLDivElement>['onDragStart']
|
|
47
|
+
onDragEnd?: HTMLAttributes<HTMLDivElement>['onDragEnd']
|
|
48
|
+
onDragEnter?: HTMLAttributes<HTMLDivElement>['onDragEnter']
|
|
49
|
+
onDragLeave?: HTMLAttributes<HTMLDivElement>['onDragLeave']
|
|
50
|
+
onDragOver?: HTMLAttributes<HTMLDivElement>['onDragOver']
|
|
51
|
+
onDrop?: HTMLAttributes<HTMLDivElement>['onDrop']
|
|
52
|
+
onPointerDown?: HTMLAttributes<HTMLDivElement>['onPointerDown']
|
|
53
|
+
onPointerMove?: HTMLAttributes<HTMLDivElement>['onPointerMove']
|
|
54
|
+
onPointerUp?: HTMLAttributes<HTMLDivElement>['onPointerUp']
|
|
55
|
+
onPointerCancel?: HTMLAttributes<HTMLDivElement>['onPointerCancel']
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type TailwindComponentState = {
|
|
59
|
+
unmounted: boolean | 'should-enter'
|
|
60
|
+
disabled?: boolean
|
|
61
|
+
hover?: boolean
|
|
62
|
+
press?: boolean
|
|
63
|
+
pressIn?: boolean
|
|
64
|
+
focus?: boolean
|
|
65
|
+
focusVisible?: boolean
|
|
66
|
+
focusWithin?: boolean
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type TailwindCommonProps = AriaAttributes &
|
|
70
|
+
WebEventProps & {
|
|
71
|
+
target?: string
|
|
72
|
+
htmlFor?: string
|
|
73
|
+
asChild?: boolean | 'except-style' | 'except-style-web' | 'web'
|
|
74
|
+
dangerouslySetInnerHTML?: { __html: string }
|
|
75
|
+
children?: ReactNode
|
|
76
|
+
debug?: boolean | 'break' | 'verbose' | 'visualize' | 'profile'
|
|
77
|
+
disabled?: boolean
|
|
78
|
+
themeShallow?: boolean
|
|
79
|
+
id?: string
|
|
80
|
+
render?:
|
|
81
|
+
| keyof HTMLElementTagNameMap
|
|
82
|
+
| (string & {})
|
|
83
|
+
| ReactElement
|
|
84
|
+
| ((
|
|
85
|
+
props: Record<string, any> & { ref?: ReactRef<any> },
|
|
86
|
+
state: TailwindComponentState
|
|
87
|
+
) => ReactElement)
|
|
88
|
+
theme?: string | null
|
|
89
|
+
group?: string | boolean
|
|
90
|
+
container?: boolean
|
|
91
|
+
untilMeasured?: 'hide' | 'show'
|
|
92
|
+
name?: string
|
|
93
|
+
tabIndex?: string | number
|
|
94
|
+
role?: HTMLAttributes<HTMLDivElement>['role']
|
|
95
|
+
disableOptimization?: boolean
|
|
96
|
+
forceStyle?: 'hover' | 'press' | 'focus' | 'focusVisible' | 'focusWithin'
|
|
97
|
+
disableClassName?: boolean
|
|
98
|
+
hitSlop?:
|
|
99
|
+
| number
|
|
100
|
+
| { top?: number; left?: number; bottom?: number; right?: number }
|
|
101
|
+
| null
|
|
102
|
+
animatedBy?: string | null
|
|
103
|
+
onPress?: PressableProps['onPress']
|
|
104
|
+
onLongPress?: PressableProps['onLongPress']
|
|
105
|
+
onPressIn?: PressableProps['onPress']
|
|
106
|
+
onPressOut?: PressableProps['onPress']
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type LooseCombinedObjects<A extends object, B extends object> = A | B | (A & B)
|
|
110
|
+
|
|
111
|
+
export type TailwindViewNonStyleProps = Omit<
|
|
112
|
+
ReactNativeViewProps,
|
|
113
|
+
'children' | 'display' | 'hitSlop' | 'pointerEvents' | 'style' | keyof WebEventProps
|
|
114
|
+
> &
|
|
115
|
+
TailwindCommonProps & {
|
|
116
|
+
style?: StyleProp<LooseCombinedObjects<CSSProperties, ViewStyle>>
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type TailwindTextNonStyleProps = Omit<
|
|
120
|
+
ReactNativeTextProps,
|
|
121
|
+
'children' | 'style' | keyof WebEventProps
|
|
122
|
+
> &
|
|
123
|
+
TailwindCommonProps & {
|
|
124
|
+
style?: StyleProp<LooseCombinedObjects<CSSProperties, TextStyle>>
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type MeasureOnSuccessCallback = (
|
|
128
|
+
x: number,
|
|
129
|
+
y: number,
|
|
130
|
+
width: number,
|
|
131
|
+
height: number,
|
|
132
|
+
pageX: number,
|
|
133
|
+
pageY: number
|
|
134
|
+
) => void
|
|
135
|
+
|
|
136
|
+
export interface TailwindElementMethods {
|
|
137
|
+
measure(callback: MeasureOnSuccessCallback): void
|
|
138
|
+
measureInWindow(
|
|
139
|
+
callback: (x: number, y: number, width: number, height: number) => void
|
|
140
|
+
): void
|
|
141
|
+
measureLayout(
|
|
142
|
+
relativeToNativeNode: ReactNativeView | HTMLElement,
|
|
143
|
+
onSuccess: (left: number, top: number, width: number, height: number) => void,
|
|
144
|
+
onFail?: () => void
|
|
145
|
+
): void
|
|
146
|
+
focus(): void
|
|
147
|
+
blur(): void
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type TailwindViewElement = (HTMLElement & TailwindElementMethods) | ReactNativeView
|
|
151
|
+
export type TailwindTextElement = (HTMLElement & TailwindElementMethods) | ReactNativeText
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The complete styling surface of a `@tamagui/tailwind` component.
|
|
155
|
+
*
|
|
156
|
+
* Everything else a component accepts is ordinary behavior: children, refs,
|
|
157
|
+
* accessibility, events, ids, and the raw platform `style` escape hatch, all
|
|
158
|
+
* inherited from the shared non-style prop types. Tamagui inline style props
|
|
159
|
+
* (`padding`, `bg`, shorthands, state/media clauses, theme style props) are
|
|
160
|
+
* deliberately absent — that authoring syntax belongs to `@tamagui/core`.
|
|
161
|
+
*/
|
|
162
|
+
export type TailwindStyleProps = {
|
|
163
|
+
className?: string
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** `true`/`false` matchers become a boolean prop; every other matcher is its own literal. */
|
|
167
|
+
type VariantAcceptedValue<Key> = Key extends 'true' | 'false' ? boolean : Key
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Finite variant inference: one optional prop per variant, accepting exactly the
|
|
171
|
+
* matchers that variant declares. It never reaches core's style prop graph.
|
|
172
|
+
*/
|
|
173
|
+
export type TailwindVariantProps<Variants> = Variants extends object
|
|
174
|
+
? {
|
|
175
|
+
[Key in keyof Variants]?: VariantAcceptedValue<keyof Variants[Key]>
|
|
176
|
+
}
|
|
177
|
+
: {}
|
|
178
|
+
|
|
179
|
+
/** Variant values are class strings — the same authoring syntax as `className`. */
|
|
180
|
+
export type TailwindVariantDefinitions = {
|
|
181
|
+
[variantName: string]: { [matcher: string]: string }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export type TailwindCompoundVariant<Variants> = TailwindVariantProps<Variants> & {
|
|
185
|
+
style: string
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export type TailwindStyledOptions<Variants extends TailwindVariantDefinitions> = {
|
|
189
|
+
displayName?: string
|
|
190
|
+
variants?: Variants
|
|
191
|
+
defaultVariants?: TailwindVariantProps<Variants>
|
|
192
|
+
compoundVariants?: readonly TailwindCompoundVariant<Variants>[]
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface TailwindComponent<
|
|
196
|
+
Ref = any,
|
|
197
|
+
NonStyleProps = {},
|
|
198
|
+
VariantProps = {},
|
|
199
|
+
> extends FunctionComponent<
|
|
200
|
+
NonStyleProps & TailwindStyleProps & VariantProps & { ref?: ReactRef<Ref> }
|
|
201
|
+
> {
|
|
202
|
+
staticConfig: FrontendStaticConfig
|
|
203
|
+
/** phantom carrier so `styled()` can recover the parts of a parent component */
|
|
204
|
+
__tailwind?: { ref: Ref; props: NonStyleProps; variants: VariantProps }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export type GetTailwindRef<Component> =
|
|
208
|
+
Component extends TailwindComponent<infer Ref, any, any> ? Ref : any
|
|
209
|
+
|
|
210
|
+
export type GetTailwindNonStyleProps<Component> =
|
|
211
|
+
Component extends TailwindComponent<any, infer Props, any> ? Props : {}
|
|
212
|
+
|
|
213
|
+
export type GetTailwindVariantProps<Component> =
|
|
214
|
+
Component extends TailwindComponent<any, any, infer Variants> ? Variants : {}
|
|
215
|
+
|
|
216
|
+
export type TailwindView = TailwindComponent<
|
|
217
|
+
TailwindViewElement,
|
|
218
|
+
TailwindViewNonStyleProps
|
|
219
|
+
>
|
|
220
|
+
export type TailwindText = TailwindComponent<
|
|
221
|
+
TailwindTextElement,
|
|
222
|
+
TailwindTextNonStyleProps
|
|
223
|
+
>
|
|
224
|
+
|
|
225
|
+
export type TailwindViewProps = TailwindViewNonStyleProps & TailwindStyleProps
|
|
226
|
+
export type TailwindTextProps = TailwindTextNonStyleProps & TailwindStyleProps
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { compile } from '@tailwindcss/node'
|
|
2
|
+
import { Scanner } from '@tailwindcss/oxide'
|
|
3
|
+
import {
|
|
4
|
+
classifyCandidate,
|
|
5
|
+
createGrammarConfigView,
|
|
6
|
+
type GrammarSourceConfig,
|
|
7
|
+
} from '@tamagui/style-grammar/runtime'
|
|
8
|
+
import { readFile, realpath } from 'node:fs/promises'
|
|
9
|
+
import { createRequire } from 'node:module'
|
|
10
|
+
import path from 'node:path'
|
|
11
|
+
|
|
12
|
+
export const TAILWIND_VERSION = '4.3.0'
|
|
13
|
+
export const TAILWIND_VIRTUAL_ID = 'virtual:tamagui-tailwind.css'
|
|
14
|
+
export const TAILWIND_RESOLVED_ID = `\0${TAILWIND_VIRTUAL_ID}`
|
|
15
|
+
|
|
16
|
+
export function wrapWithTamaguiLayer(css: string): string {
|
|
17
|
+
return `@layer tamagui {\n${css}\n}`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isTamaguiCoreResetCSS(id: string): boolean {
|
|
21
|
+
const normalizedId = id.split('?', 1)[0].replace(/\\/g, '/')
|
|
22
|
+
return (
|
|
23
|
+
normalizedId.endsWith('/@tamagui/core/reset.css') ||
|
|
24
|
+
normalizedId.endsWith('/code/core/core/reset.css')
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const TAILWIND_INPUT = `@layer tamagui, theme, utilities;
|
|
29
|
+
@import "tailwindcss/theme.css" layer(theme);
|
|
30
|
+
@import "tailwindcss/utilities.css" layer(utilities);`
|
|
31
|
+
|
|
32
|
+
type TailwindCompiler = Awaited<ReturnType<typeof compile>>
|
|
33
|
+
|
|
34
|
+
export type TailwindScannerState = ReturnType<typeof createTailwindScannerState>
|
|
35
|
+
|
|
36
|
+
export type TailwindWatchEvent = 'create' | 'update' | 'delete'
|
|
37
|
+
|
|
38
|
+
function extensionForFile(id: string): string {
|
|
39
|
+
return path.extname(id.split('?', 1)[0]).slice(1) || 'html'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function assertHostTailwind(root: string): Promise<void> {
|
|
43
|
+
const requireFromRoot = createRequire(path.join(root, 'package.json'))
|
|
44
|
+
let packagePath: string
|
|
45
|
+
try {
|
|
46
|
+
packagePath = requireFromRoot.resolve('tailwindcss/package.json')
|
|
47
|
+
} catch {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`[tamagui] @tamagui/tailwind/vite requires tailwindcss@${TAILWIND_VERSION} in the app. Install it with \`bun add -D tailwindcss@${TAILWIND_VERSION}\` (resolved from ${root}).`
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const packageJSON = JSON.parse(await readFile(packagePath, 'utf8')) as {
|
|
54
|
+
version?: string
|
|
55
|
+
}
|
|
56
|
+
if (packageJSON.version !== TAILWIND_VERSION) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`[tamagui] @tamagui/tailwind/vite requires tailwindcss@${TAILWIND_VERSION}, but ${packageJSON.version || 'an unknown version'} was resolved from ${packagePath}. Align the app dependency before starting Vite.`
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The official Tailwind scanner and compiler, owned by `@tamagui/tailwind/vite`.
|
|
65
|
+
*
|
|
66
|
+
* It compiles only the candidates Tamagui's own grammar does not claim: claimed
|
|
67
|
+
* candidates already became Tamagui atoms through the shared renderer, so emitting
|
|
68
|
+
* them again would duplicate the rules and hand the cascade to whichever landed
|
|
69
|
+
* last. There is no style-mode gate here — the state exists because the app
|
|
70
|
+
* imported the Tailwind plugin.
|
|
71
|
+
*/
|
|
72
|
+
export function createTailwindScannerState() {
|
|
73
|
+
let root = ''
|
|
74
|
+
let generation = -1
|
|
75
|
+
let enabled = false
|
|
76
|
+
let scanner: Scanner | null = null
|
|
77
|
+
let compiler: TailwindCompiler | null = null
|
|
78
|
+
let grammarConfig = createGrammarConfigView({})
|
|
79
|
+
let candidatesByFile = new Map<string, Set<string>>()
|
|
80
|
+
let compiledCandidates = new Set<string>()
|
|
81
|
+
let addDependency: (file: string) => void = () => {}
|
|
82
|
+
let css = ''
|
|
83
|
+
|
|
84
|
+
function clear() {
|
|
85
|
+
root = ''
|
|
86
|
+
generation = -1
|
|
87
|
+
enabled = false
|
|
88
|
+
scanner = null
|
|
89
|
+
compiler = null
|
|
90
|
+
grammarConfig = createGrammarConfigView({})
|
|
91
|
+
candidatesByFile = new Map()
|
|
92
|
+
compiledCandidates = new Set()
|
|
93
|
+
addDependency = () => {}
|
|
94
|
+
css = ''
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function configure(
|
|
98
|
+
nextRoot: string,
|
|
99
|
+
nextGeneration: number,
|
|
100
|
+
config: GrammarSourceConfig | null | undefined,
|
|
101
|
+
onDependency: (file: string) => void,
|
|
102
|
+
onSourceGlob: (glob: string) => void = () => {}
|
|
103
|
+
): Promise<boolean> {
|
|
104
|
+
// a null config means Tamagui itself is disabled for this build, and without a
|
|
105
|
+
// config there is no grammar view to decide which candidates Tamagui owns
|
|
106
|
+
if (!config) {
|
|
107
|
+
clear()
|
|
108
|
+
generation = nextGeneration
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
compiler &&
|
|
113
|
+
scanner &&
|
|
114
|
+
enabled &&
|
|
115
|
+
generation === nextGeneration &&
|
|
116
|
+
root === nextRoot
|
|
117
|
+
) {
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await assertHostTailwind(nextRoot)
|
|
122
|
+
root = nextRoot
|
|
123
|
+
generation = nextGeneration
|
|
124
|
+
enabled = true
|
|
125
|
+
grammarConfig = createGrammarConfigView(config)
|
|
126
|
+
addDependency = onDependency
|
|
127
|
+
scanner = new Scanner({
|
|
128
|
+
sources: [
|
|
129
|
+
{ base: root, pattern: '**/*', negated: false },
|
|
130
|
+
{ base: root, pattern: 'node_modules/**', negated: true },
|
|
131
|
+
{ base: root, pattern: '.git/**', negated: true },
|
|
132
|
+
{ base: root, pattern: '.vite/**', negated: true },
|
|
133
|
+
{ base: root, pattern: 'dist/**', negated: true },
|
|
134
|
+
{ base: root, pattern: 'build/**', negated: true },
|
|
135
|
+
{ base: root, pattern: 'coverage/**', negated: true },
|
|
136
|
+
],
|
|
137
|
+
})
|
|
138
|
+
compiler = await createCompiler()
|
|
139
|
+
scanner.scan()
|
|
140
|
+
registerScannerSources(scanner, onDependency, onSourceGlob)
|
|
141
|
+
candidatesByFile = new Map()
|
|
142
|
+
await Promise.all(
|
|
143
|
+
scanner.files.map(async (file) => {
|
|
144
|
+
try {
|
|
145
|
+
const source = await readFile(file, 'utf8')
|
|
146
|
+
candidatesByFile.set(
|
|
147
|
+
await normalizeSourcePath(file),
|
|
148
|
+
candidatesForSource(scanner!, file, source)
|
|
149
|
+
)
|
|
150
|
+
} catch {
|
|
151
|
+
// A source may disappear between oxide's inventory and this read.
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
)
|
|
155
|
+
await rebuild()
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function createCompiler() {
|
|
160
|
+
return compile(TAILWIND_INPUT, {
|
|
161
|
+
base: root,
|
|
162
|
+
onDependency: addDependency,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function rebuild(): Promise<boolean> {
|
|
167
|
+
if (!compiler) return false
|
|
168
|
+
const allCandidates = new Set<string>()
|
|
169
|
+
for (const candidates of candidatesByFile.values()) {
|
|
170
|
+
for (const candidate of candidates) allCandidates.add(candidate)
|
|
171
|
+
}
|
|
172
|
+
const passthrough = [...allCandidates]
|
|
173
|
+
.filter(
|
|
174
|
+
(candidate) => classifyCandidate(candidate, grammarConfig).kind === 'passthrough'
|
|
175
|
+
)
|
|
176
|
+
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
|
|
177
|
+
const nextCandidates = new Set(passthrough)
|
|
178
|
+
const removedCandidate = [...compiledCandidates].some(
|
|
179
|
+
(candidate) => !nextCandidates.has(candidate)
|
|
180
|
+
)
|
|
181
|
+
if (removedCandidate) {
|
|
182
|
+
compiler = await createCompiler()
|
|
183
|
+
}
|
|
184
|
+
const nextCSS = compiler.build(passthrough)
|
|
185
|
+
compiledCandidates = nextCandidates
|
|
186
|
+
if (nextCSS === css) return false
|
|
187
|
+
css = nextCSS
|
|
188
|
+
return true
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function scanSource(id: string, source: string): Promise<boolean> {
|
|
192
|
+
if (!enabled || !scanner) return false
|
|
193
|
+
candidatesByFile.set(
|
|
194
|
+
await normalizeSourcePath(id),
|
|
195
|
+
candidatesForSource(scanner, id, source)
|
|
196
|
+
)
|
|
197
|
+
return rebuild()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function removeSource(id: string): Promise<boolean> {
|
|
201
|
+
if (!candidatesByFile.delete(await normalizeSourcePath(id))) return false
|
|
202
|
+
return rebuild()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
clear,
|
|
207
|
+
configure,
|
|
208
|
+
removeSource,
|
|
209
|
+
scanSource,
|
|
210
|
+
get enabled() {
|
|
211
|
+
return enabled
|
|
212
|
+
},
|
|
213
|
+
get css() {
|
|
214
|
+
return css
|
|
215
|
+
},
|
|
216
|
+
get candidateCount() {
|
|
217
|
+
return compiledCandidates.size
|
|
218
|
+
},
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function updateTailwindForWatchChange(
|
|
223
|
+
state: TailwindScannerState,
|
|
224
|
+
id: string,
|
|
225
|
+
event: TailwindWatchEvent,
|
|
226
|
+
configure: () => Promise<boolean>
|
|
227
|
+
): Promise<boolean> {
|
|
228
|
+
if (!(await configure())) return false
|
|
229
|
+
if (event === 'delete') {
|
|
230
|
+
return state.removeSource(id)
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
return state.scanSource(id, await readFile(id, 'utf8'))
|
|
234
|
+
} catch {
|
|
235
|
+
return state.removeSource(id)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function registerScannerSources(
|
|
240
|
+
scanner: Scanner,
|
|
241
|
+
onDependency: (file: string) => void,
|
|
242
|
+
onSourceGlob: (glob: string) => void
|
|
243
|
+
) {
|
|
244
|
+
for (const file of scanner.files) {
|
|
245
|
+
onDependency(file)
|
|
246
|
+
}
|
|
247
|
+
for (const { base, pattern } of scanner.globs) {
|
|
248
|
+
onSourceGlob(path.join(base, pattern))
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function normalizeSourcePath(id: string): Promise<string> {
|
|
253
|
+
const cleanId = path.resolve(id.split('?', 1)[0])
|
|
254
|
+
try {
|
|
255
|
+
return await realpath(cleanId)
|
|
256
|
+
} catch {
|
|
257
|
+
try {
|
|
258
|
+
return path.join(await realpath(path.dirname(cleanId)), path.basename(cleanId))
|
|
259
|
+
} catch {
|
|
260
|
+
return cleanId
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function candidatesForSource(scanner: Scanner, id: string, source: string): Set<string> {
|
|
266
|
+
return new Set(
|
|
267
|
+
scanner
|
|
268
|
+
.getCandidatesWithPositions({
|
|
269
|
+
content: source,
|
|
270
|
+
extension: extensionForFile(id),
|
|
271
|
+
})
|
|
272
|
+
.map(({ candidate }) => candidate)
|
|
273
|
+
)
|
|
274
|
+
}
|