@tamagui/static 2.0.0-rc.8 → 2.0.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.
Files changed (186) hide show
  1. package/dist/check-dep-versions.cjs +201 -96
  2. package/dist/checkDeps.cjs +250 -46
  3. package/dist/constants.cjs +32 -30
  4. package/dist/exports.cjs +20 -13
  5. package/dist/extractor/accessSafe.cjs +25 -23
  6. package/dist/extractor/babelParse.cjs +30 -28
  7. package/dist/extractor/buildClassName.cjs +59 -35
  8. package/dist/extractor/bundle.cjs +187 -101
  9. package/dist/extractor/bundleConfig.cjs +568 -168
  10. package/dist/extractor/concatClassName.cjs +73 -32
  11. package/dist/extractor/createEvaluator.cjs +54 -41
  12. package/dist/extractor/createExtractor.cjs +1405 -574
  13. package/dist/extractor/createLogger.cjs +30 -25
  14. package/dist/extractor/detectModuleFormat.cjs +55 -0
  15. package/dist/extractor/ensureImportingConcat.cjs +31 -25
  16. package/dist/extractor/errors.cjs +12 -10
  17. package/dist/extractor/esbuildAliasPlugin.cjs +28 -16
  18. package/dist/extractor/esbuildTsconfigPaths.cjs +60 -36
  19. package/dist/extractor/evaluateAstNode.cjs +104 -59
  20. package/dist/extractor/extractHelpers.cjs +130 -67
  21. package/dist/extractor/extractMediaStyle.cjs +110 -69
  22. package/dist/extractor/extractToClassNames.cjs +337 -229
  23. package/dist/extractor/extractToNative.cjs +248 -154
  24. package/dist/extractor/findTopmostFunction.cjs +22 -13
  25. package/dist/extractor/generatedUid.cjs +39 -28
  26. package/dist/extractor/getPrefixLogs.cjs +12 -10
  27. package/dist/extractor/getPropValueFromAttributes.cjs +52 -34
  28. package/dist/extractor/getSourceModule.cjs +73 -37
  29. package/dist/extractor/getStaticBindingsForScope.cjs +131 -68
  30. package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.cjs +20 -14
  31. package/dist/extractor/hasTopLevelAwait.cjs +62 -0
  32. package/dist/extractor/hoistClassNames.cjs +46 -32
  33. package/dist/extractor/literalToAst.cjs +67 -42
  34. package/dist/extractor/loadFile.cjs +9 -3
  35. package/dist/extractor/loadTamagui.cjs +151 -105
  36. package/dist/extractor/logLines.cjs +27 -19
  37. package/dist/extractor/normalizeTernaries.cjs +66 -44
  38. package/dist/extractor/propsToFontFamilyCache.cjs +15 -11
  39. package/dist/extractor/regenerateConfig.cjs +109 -81
  40. package/dist/extractor/removeUnusedHooks.cjs +73 -41
  41. package/dist/extractor/timer.cjs +23 -14
  42. package/dist/extractor/validHTMLAttributes.cjs +61 -59
  43. package/dist/extractor/watchTamaguiConfig.cjs +35 -23
  44. package/dist/getPragmaOptions.cjs +34 -19
  45. package/dist/helpers/memoize.cjs +24 -16
  46. package/dist/helpers/requireTamaguiCore.cjs +22 -15
  47. package/dist/index.cjs +26 -24
  48. package/dist/registerRequire.cjs +168 -65
  49. package/dist/server.cjs +35 -28
  50. package/dist/types.cjs +7 -5
  51. package/dist/worker.cjs +62 -40
  52. package/package.json +27 -24
  53. package/src/checkDeps.ts +305 -68
  54. package/src/exports.ts +2 -0
  55. package/src/extractor/babelParse.ts +1 -0
  56. package/src/extractor/bundle.ts +172 -40
  57. package/src/extractor/bundleConfig.ts +459 -65
  58. package/src/extractor/concatClassName.ts +37 -20
  59. package/src/extractor/createExtractor.ts +300 -30
  60. package/src/extractor/detectModuleFormat.ts +42 -0
  61. package/src/extractor/esbuildTsconfigPaths.ts +6 -1
  62. package/src/extractor/extractToClassNames.ts +15 -9
  63. package/src/extractor/extractToNative.ts +66 -33
  64. package/src/extractor/hasTopLevelAwait.ts +28 -0
  65. package/src/extractor/loadTamagui.ts +5 -4
  66. package/src/registerRequire.ts +102 -9
  67. package/types/checkDeps.d.ts.map +1 -1
  68. package/types/exports.d.ts +2 -0
  69. package/types/exports.d.ts.map +1 -1
  70. package/types/extractor/babelParse.d.ts.map +1 -1
  71. package/types/extractor/bundle.d.ts +83 -1
  72. package/types/extractor/bundle.d.ts.map +1 -1
  73. package/types/extractor/bundleConfig.d.ts +15 -2
  74. package/types/extractor/bundleConfig.d.ts.map +1 -1
  75. package/types/extractor/createExtractor.d.ts.map +1 -1
  76. package/types/extractor/detectModuleFormat.d.ts +5 -0
  77. package/types/extractor/detectModuleFormat.d.ts.map +1 -0
  78. package/types/extractor/esbuildTsconfigPaths.d.ts +8 -0
  79. package/types/extractor/esbuildTsconfigPaths.d.ts.map +1 -1
  80. package/types/extractor/extractToClassNames.d.ts.map +1 -1
  81. package/types/extractor/extractToNative.d.ts.map +1 -1
  82. package/types/extractor/hasTopLevelAwait.d.ts +2 -0
  83. package/types/extractor/hasTopLevelAwait.d.ts.map +1 -0
  84. package/types/extractor/loadTamagui.d.ts +1 -1
  85. package/types/extractor/loadTamagui.d.ts.map +1 -1
  86. package/types/registerRequire.d.ts.map +1 -1
  87. package/dist/check-dep-versions.js +0 -389
  88. package/dist/check-dep-versions.js.map +0 -6
  89. package/dist/checkDeps.js +0 -60
  90. package/dist/checkDeps.js.map +0 -6
  91. package/dist/constants.js +0 -34
  92. package/dist/constants.js.map +0 -6
  93. package/dist/exports.js +0 -34
  94. package/dist/exports.js.map +0 -6
  95. package/dist/extractor/accessSafe.js +0 -47
  96. package/dist/extractor/accessSafe.js.map +0 -6
  97. package/dist/extractor/babelParse.js +0 -59
  98. package/dist/extractor/babelParse.js.map +0 -6
  99. package/dist/extractor/buildClassName.js +0 -72
  100. package/dist/extractor/buildClassName.js.map +0 -6
  101. package/dist/extractor/bundle.js +0 -135
  102. package/dist/extractor/bundle.js.map +0 -6
  103. package/dist/extractor/bundleConfig.js +0 -352
  104. package/dist/extractor/bundleConfig.js.map +0 -6
  105. package/dist/extractor/concatClassName.js +0 -69
  106. package/dist/extractor/concatClassName.js.map +0 -6
  107. package/dist/extractor/createEvaluator.js +0 -66
  108. package/dist/extractor/createEvaluator.js.map +0 -6
  109. package/dist/extractor/createExtractor.js +0 -1212
  110. package/dist/extractor/createExtractor.js.map +0 -6
  111. package/dist/extractor/createLogger.js +0 -32
  112. package/dist/extractor/createLogger.js.map +0 -6
  113. package/dist/extractor/ensureImportingConcat.js +0 -50
  114. package/dist/extractor/ensureImportingConcat.js.map +0 -6
  115. package/dist/extractor/errors.js +0 -22
  116. package/dist/extractor/errors.js.map +0 -6
  117. package/dist/extractor/esbuildAliasPlugin.js +0 -36
  118. package/dist/extractor/esbuildAliasPlugin.js.map +0 -6
  119. package/dist/extractor/esbuildTsconfigPaths.js +0 -79
  120. package/dist/extractor/esbuildTsconfigPaths.js.map +0 -6
  121. package/dist/extractor/evaluateAstNode.js +0 -99
  122. package/dist/extractor/evaluateAstNode.js.map +0 -6
  123. package/dist/extractor/extractHelpers.js +0 -108
  124. package/dist/extractor/extractHelpers.js.map +0 -6
  125. package/dist/extractor/extractMediaStyle.js +0 -123
  126. package/dist/extractor/extractMediaStyle.js.map +0 -6
  127. package/dist/extractor/extractToClassNames.js +0 -351
  128. package/dist/extractor/extractToClassNames.js.map +0 -6
  129. package/dist/extractor/extractToNative.js +0 -286
  130. package/dist/extractor/extractToNative.js.map +0 -6
  131. package/dist/extractor/findTopmostFunction.js +0 -32
  132. package/dist/extractor/findTopmostFunction.js.map +0 -6
  133. package/dist/extractor/generatedUid.js +0 -42
  134. package/dist/extractor/generatedUid.js.map +0 -6
  135. package/dist/extractor/getPrefixLogs.js +0 -24
  136. package/dist/extractor/getPrefixLogs.js.map +0 -6
  137. package/dist/extractor/getPropValueFromAttributes.js +0 -65
  138. package/dist/extractor/getPropValueFromAttributes.js.map +0 -6
  139. package/dist/extractor/getSourceModule.js +0 -62
  140. package/dist/extractor/getSourceModule.js.map +0 -6
  141. package/dist/extractor/getStaticBindingsForScope.js +0 -145
  142. package/dist/extractor/getStaticBindingsForScope.js.map +0 -6
  143. package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.js +0 -32
  144. package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.js.map +0 -6
  145. package/dist/extractor/hoistClassNames.js +0 -63
  146. package/dist/extractor/hoistClassNames.js.map +0 -6
  147. package/dist/extractor/literalToAst.js +0 -90
  148. package/dist/extractor/literalToAst.js.map +0 -6
  149. package/dist/extractor/loadFile.js +0 -14
  150. package/dist/extractor/loadFile.js.map +0 -6
  151. package/dist/extractor/loadTamagui.js +0 -306
  152. package/dist/extractor/loadTamagui.js.map +0 -6
  153. package/dist/extractor/logLines.js +0 -30
  154. package/dist/extractor/logLines.js.map +0 -6
  155. package/dist/extractor/normalizeTernaries.js +0 -61
  156. package/dist/extractor/normalizeTernaries.js.map +0 -6
  157. package/dist/extractor/propsToFontFamilyCache.js +0 -33
  158. package/dist/extractor/propsToFontFamilyCache.js.map +0 -6
  159. package/dist/extractor/regenerateConfig.js +0 -123
  160. package/dist/extractor/regenerateConfig.js.map +0 -6
  161. package/dist/extractor/removeUnusedHooks.js +0 -72
  162. package/dist/extractor/removeUnusedHooks.js.map +0 -6
  163. package/dist/extractor/timer.js +0 -38
  164. package/dist/extractor/timer.js.map +0 -6
  165. package/dist/extractor/validHTMLAttributes.js +0 -72
  166. package/dist/extractor/validHTMLAttributes.js.map +0 -6
  167. package/dist/extractor/watchTamaguiConfig.js +0 -57
  168. package/dist/extractor/watchTamaguiConfig.js.map +0 -6
  169. package/dist/getPragmaOptions.js +0 -50
  170. package/dist/getPragmaOptions.js.map +0 -6
  171. package/dist/helpers/memoize.js +0 -33
  172. package/dist/helpers/memoize.js.map +0 -6
  173. package/dist/helpers/requireTamaguiCore.js +0 -30
  174. package/dist/helpers/requireTamaguiCore.js.map +0 -6
  175. package/dist/index.js +0 -30
  176. package/dist/index.js.map +0 -6
  177. package/dist/registerRequire.js +0 -100
  178. package/dist/registerRequire.js.map +0 -6
  179. package/dist/server.js +0 -58
  180. package/dist/server.js.map +0 -6
  181. package/dist/setup.js +0 -1
  182. package/dist/setup.js.map +0 -6
  183. package/dist/types.js +0 -14
  184. package/dist/types.js.map +0 -6
  185. package/dist/worker.js +0 -72
  186. package/dist/worker.js.map +0 -6
@@ -45,24 +45,9 @@ export function concatClassName(_cn: Record<string, any> | null | undefined): st
45
45
  continue
46
46
  }
47
47
 
48
- const nextChar = name[splitIndex + 1]
49
- // synced to static-ui constants
50
- // MEDIA_SEP
51
- const isMediaQuery = nextChar === '_'
52
- // PSEUDO_SEP
53
- // commenting out three things to make pseudos override properly
54
- // (leave in for a bit to see if other bugs pop up later):
55
- // 1. const isPseudoQuery = nextChar === '0'
56
48
  const styleKey = name.slice(1, name.indexOf('-'))
57
- // 2. isMediaQuery || isPseudoQuery
58
- // extract just the media query name (e.g., 'lg' '_pr-_lg_260px')
59
- // by finding the underscore after the media key name
60
- const mediaStart = splitIndex + 2
61
- const mediaEnd = name.indexOf('_', mediaStart)
62
- const mediaKey =
63
- isMediaQuery && mediaEnd > mediaStart ? name.slice(mediaStart, mediaEnd) : null
64
- const uid = mediaKey ? styleKey + mediaKey : styleKey
65
- // 3. && !isPseudoQuery
49
+ const { mediaKey, pseudoKey } = getClassNameScope(name, splitIndex)
50
+ const uid = `${styleKey}${mediaKey ? `@${mediaKey}` : ''}${pseudoKey ? `:${pseudoKey}` : ''}`
66
51
 
67
52
  if (usedPrefixes.has(uid)) {
68
53
  // if (shouldDebug) console.log('debug exclude:', usedPrefixes, name)
@@ -75,8 +60,8 @@ export function concatClassName(_cn: Record<string, any> | null | undefined): st
75
60
  if (propName && propObjects) {
76
61
  if (
77
62
  propObjects.some((po) => {
78
- if (mediaKey) {
79
- const propKey = pseudoInvert[mediaKey]
63
+ if (pseudoKey) {
64
+ const propKey = pseudoInvert[pseudoKey]
80
65
  return po && po[propKey] && propName in po[propKey] && po[propKey] !== null
81
66
  }
82
67
  const res = po && propName in po && po[propName] !== null
@@ -95,11 +80,43 @@ export function concatClassName(_cn: Record<string, any> | null | undefined): st
95
80
  return final.trim()
96
81
  }
97
82
 
83
+ function getClassNameScope(name: string, splitIndex: number) {
84
+ let mediaKey = ''
85
+ let pseudoKey = ''
86
+ let valueStart = splitIndex + 1
87
+
88
+ if (name[valueStart] === '_') {
89
+ const mediaEnd = name.indexOf('_', valueStart + 1)
90
+ if (mediaEnd > valueStart + 1) {
91
+ mediaKey = name.slice(valueStart + 1, mediaEnd)
92
+ valueStart = mediaEnd + 1
93
+ }
94
+ }
95
+
96
+ if (name[valueStart] === '0') {
97
+ const pseudoStart = valueStart + 1
98
+ for (const nextPseudoKey of pseudoKeys) {
99
+ if (name.startsWith(`${nextPseudoKey}-`, pseudoStart)) {
100
+ pseudoKey = nextPseudoKey
101
+ break
102
+ }
103
+ }
104
+ }
105
+
106
+ return { mediaKey, pseudoKey }
107
+ }
108
+
98
109
  const pseudoInvert = {
99
110
  hover: 'hoverStyle',
111
+ active: 'pressStyle',
100
112
  focus: 'focusStyle',
101
- press: 'pressStyle',
113
+ 'focus-visible': 'focusVisibleStyle',
114
+ 'focus-within': 'focusWithinStyle',
102
115
  focusVisible: 'focusVisibleStyle',
103
116
  focusWithin: 'focusWithinStyle',
104
117
  disabled: 'disabledStyle',
118
+ enter: 'enterStyle',
119
+ exit: 'exitStyle',
105
120
  }
121
+
122
+ const pseudoKeys = Object.keys(pseudoInvert).sort((a, b) => b.length - a.length)
@@ -12,7 +12,9 @@ import {
12
12
  type StaticConfig,
13
13
  type TamaguiComponentState,
14
14
  } from '@tamagui/web'
15
- import { basename, relative } from 'node:path'
15
+ import { existsSync, readFileSync } from 'node:fs'
16
+ import { basename, dirname, resolve, relative } from 'node:path'
17
+ import { nodeModuleNameResolver, sys } from 'typescript'
16
18
  import type { ViewStyle } from 'react-native'
17
19
 
18
20
  import { FAILED_EVAL } from '../constants'
@@ -26,7 +28,7 @@ import type {
26
28
  TamaguiOptionsWithFileInfo,
27
29
  Ternary,
28
30
  } from '../types'
29
- import type { TamaguiProjectInfo } from './bundleConfig'
31
+ import type { LoadedComponents, TamaguiProjectInfo } from './bundleConfig'
30
32
  import { createEvaluator, createSafeEvaluator } from './createEvaluator'
31
33
  import { evaluateAstNode } from './evaluateAstNode'
32
34
  import {
@@ -49,6 +51,7 @@ import { setPropsToFontFamily } from './propsToFontFamilyCache'
49
51
  import { timer } from './timer'
50
52
  import { validHTMLAttributes } from './validHTMLAttributes'
51
53
  import { BailOptimizationError } from './errors'
54
+ import { loadCompilerOptionsFromTsconfig } from './esbuildTsconfigPaths'
52
55
 
53
56
  const UNTOUCHED_PROPS = {
54
57
  key: true,
@@ -56,6 +59,11 @@ const UNTOUCHED_PROPS = {
56
59
  className: true,
57
60
  }
58
61
 
62
+ // Platform variants that can't be resolved at compile time on native builds.
63
+ // Defined at module level (not inside the loop) to avoid repeated Set allocations during compilation.
64
+ // (requires runtime Platform.OS + Platform.isTV checks via react-native-tvos)
65
+ const nativeOnlyPlatforms = new Set(['android', 'ios', 'tv', 'androidtv', 'tvos'])
66
+
59
67
  const createTernary = (x: Ternary) => x
60
68
 
61
69
  export type Extractor = ReturnType<typeof createExtractor>
@@ -71,10 +79,6 @@ function isFullyDisabled(props: TamaguiOptions) {
71
79
  export function createExtractor(
72
80
  { logger = console, platform = 'web' }: ExtractorOptions = { logger: console }
73
81
  ) {
74
- if (!process.env.TAMAGUI_TARGET) {
75
- throw new Error('Please set process.env.TAMAGUI_TARGET to either "web" or "native"')
76
- }
77
-
78
82
  const INLINE_EXTRACTABLE = {
79
83
  ref: 'ref',
80
84
  key: 'key',
@@ -85,6 +89,29 @@ export function createExtractor(
85
89
  onPressIn: 'onMouseDown',
86
90
  onPressOut: 'onMouseUp',
87
91
  }),
92
+ ...(platform === 'native' && {
93
+ // native view props that should pass through without preventing flattening
94
+ testID: 'testID',
95
+ nativeID: 'nativeID',
96
+ accessibilityLabel: 'accessibilityLabel',
97
+ accessibilityHint: 'accessibilityHint',
98
+ accessibilityRole: 'accessibilityRole',
99
+ accessibilityState: 'accessibilityState',
100
+ accessibilityValue: 'accessibilityValue',
101
+ accessibilityActions: 'accessibilityActions',
102
+ accessibilityLabelledBy: 'accessibilityLabelledBy',
103
+ accessibilityLiveRegion: 'accessibilityLiveRegion',
104
+ accessibilityElementsHidden: 'accessibilityElementsHidden',
105
+ accessibilityViewIsModal: 'accessibilityViewIsModal',
106
+ importantForAccessibility: 'importantForAccessibility',
107
+ collapsable: 'collapsable',
108
+ needsOffscreenAlphaCompositing: 'needsOffscreenAlphaCompositing',
109
+ removeClippedSubviews: 'removeClippedSubviews',
110
+ renderToHardwareTextureAndroid: 'renderToHardwareTextureAndroid',
111
+ shouldRasterizeIOS: 'shouldRasterizeIOS',
112
+ hitSlop: 'hitSlop',
113
+ pointerEvents: 'pointerEvents',
114
+ }),
88
115
  }
89
116
 
90
117
  const componentState: TamaguiComponentState = {
@@ -99,7 +126,7 @@ export function createExtractor(
99
126
  } as const
100
127
 
101
128
  const styleProps: SplitStyleProps = {
102
- resolveValues: process.env.TAMAGUI_TARGET === 'native' ? 'value' : 'variable',
129
+ resolveValues: platform === 'native' ? 'value' : 'variable',
103
130
  noClass: false,
104
131
  isAnimated: false,
105
132
  }
@@ -107,12 +134,89 @@ export function createExtractor(
107
134
  const shouldAddDebugProp =
108
135
  // really basic disable this for next.js because it messes with ssr
109
136
  !process.env.npm_package_dependencies_next &&
110
- process.env.TAMAGUI_TARGET !== 'native' &&
137
+ platform !== 'native' &&
111
138
  process.env.IDENTIFY_TAGS !== 'false' &&
112
139
  (process.env.NODE_ENV === 'development' || process.env.IDENTIFY_TAGS)
113
140
 
114
141
  let projectInfo: TamaguiProjectInfo | null = null
115
142
 
143
+ // cache of dynamically discovered styled components, keyed by absolute file path
144
+ // persists across files within the same worker/extractor instance
145
+ const dynamicComponentCache = new Map<string, LoadedComponents>()
146
+ const dynamicLoadingInProgress = new Set<string>()
147
+
148
+ // lazily loaded tsconfig compiler options for path alias resolution
149
+ let _compilerOptions: any = null
150
+ function getCompilerOptions() {
151
+ if (!_compilerOptions) {
152
+ try {
153
+ _compilerOptions = loadCompilerOptionsFromTsconfig()
154
+ } catch {
155
+ _compilerOptions = {}
156
+ }
157
+ }
158
+ return _compilerOptions
159
+ }
160
+
161
+ function resolveImportPath(fromFile: string, importPath: string): string | null {
162
+ if (importPath.startsWith('.')) {
163
+ // relative path resolution
164
+ const dir = dirname(fromFile)
165
+ const base = resolve(dir, importPath)
166
+ const extensions = ['.tsx', '.ts', '.jsx', '.js']
167
+ for (const ext of extensions) {
168
+ const full = base + ext
169
+ if (existsSync(full)) return full
170
+ }
171
+ // try index files
172
+ for (const ext of extensions) {
173
+ const full = resolve(base, `index${ext}`)
174
+ if (existsSync(full)) return full
175
+ }
176
+ return null
177
+ }
178
+
179
+ // tsconfig path alias resolution (e.g. ~/foo, @/bar)
180
+ const compilerOptions = getCompilerOptions()
181
+ if (compilerOptions.paths) {
182
+ try {
183
+ const { resolvedModule } = nodeModuleNameResolver(
184
+ importPath,
185
+ fromFile,
186
+ compilerOptions,
187
+ sys
188
+ )
189
+ if (
190
+ resolvedModule &&
191
+ !resolvedModule.resolvedFileName.endsWith('.d.ts') &&
192
+ !resolvedModule.isExternalLibraryImport
193
+ ) {
194
+ return resolvedModule.resolvedFileName
195
+ }
196
+ } catch {
197
+ // fallback - tsconfig resolution failed
198
+ }
199
+ }
200
+
201
+ return null
202
+ }
203
+
204
+ const styledCheckCache = new Map<string, boolean>()
205
+
206
+ function mightHaveStyledComponents(filePath: string): boolean {
207
+ const cached = styledCheckCache.get(filePath)
208
+ if (cached !== undefined) return cached
209
+ try {
210
+ const content = readFileSync(filePath, 'utf-8')
211
+ const result = content.includes('styled(')
212
+ styledCheckCache.set(filePath, result)
213
+ return result
214
+ } catch {
215
+ styledCheckCache.set(filePath, false)
216
+ return false
217
+ }
218
+ }
219
+
116
220
  // we load tamagui delayed because we need to set some global/env stuff before importing
117
221
  // otherwise we'd import `rnw` and cause it to evaluate react-native-web which causes errors
118
222
 
@@ -178,6 +282,12 @@ export function createExtractor(
178
282
  ...restProps
179
283
  } = options
180
284
 
285
+ // invalidate dynamic cache for this file on re-parse (HMR)
286
+ if (sourcePath && dynamicComponentCache.has(sourcePath)) {
287
+ dynamicComponentCache.delete(sourcePath)
288
+ styledCheckCache.delete(sourcePath)
289
+ }
290
+
181
291
  if (sourcePath.includes('.tamagui-dynamic-eval')) {
182
292
  return null
183
293
  }
@@ -375,12 +485,12 @@ export function createExtractor(
375
485
  logger.info(` - import via ${moduleName} ${valid}`)
376
486
  }
377
487
 
378
- if (extractStyledDefinitions) {
379
- if (valid) {
380
- if (node.specifiers.some((specifier) => specifier.local.name === 'styled')) {
381
- doesUseValidImport = true
382
- break
383
- }
488
+ if (extractStyledDefinitions && enableDynamicEvaluation) {
489
+ // check all imports for `styled`, not just valid packages
490
+ // styled( is basically guaranteed to be tamagui regardless of source
491
+ if (node.specifiers.some((specifier) => specifier.local.name === 'styled')) {
492
+ doesUseValidImport = true
493
+ // don't break - need to collect all import declarations for the styled() handler
384
494
  }
385
495
  }
386
496
 
@@ -400,7 +510,7 @@ export function createExtractor(
400
510
  }
401
511
  if (isValidComponent) {
402
512
  doesUseValidImport = true
403
- break
513
+ if (!(extractStyledDefinitions && enableDynamicEvaluation)) break
404
514
  }
405
515
  }
406
516
  }
@@ -411,6 +521,35 @@ export function createExtractor(
411
521
  )
412
522
  }
413
523
 
524
+ if (
525
+ !doesUseValidImport &&
526
+ extractStyledDefinitions &&
527
+ enableDynamicEvaluation &&
528
+ sourcePath
529
+ ) {
530
+ // check if any local import is in the dynamic cache or has styled components
531
+ for (const bodyPath of body) {
532
+ if (bodyPath.type !== 'ImportDeclaration') continue
533
+ const node = (
534
+ 'node' in bodyPath ? bodyPath.node : bodyPath
535
+ ) as t.ImportDeclaration
536
+ const moduleName = node.source.value
537
+
538
+ const resolved = resolveImportPath(sourcePath, moduleName)
539
+ if (!resolved) continue
540
+
541
+ if (dynamicComponentCache.has(resolved)) {
542
+ doesUseValidImport = true
543
+ break
544
+ }
545
+
546
+ if (mightHaveStyledComponents(resolved)) {
547
+ doesUseValidImport = true
548
+ break
549
+ }
550
+ }
551
+ }
552
+
414
553
  if (!doesUseValidImport) {
415
554
  return null
416
555
  }
@@ -505,7 +644,7 @@ export function createExtractor(
505
644
  getValidImportedComponent(parentName) || getValidImportedComponent(variableName)
506
645
 
507
646
  if (!Component) {
508
- if (enableDynamicEvaluation !== true) {
647
+ if (!enableDynamicEvaluation) {
509
648
  return
510
649
  }
511
650
 
@@ -589,6 +728,7 @@ export function createExtractor(
589
728
  // for now dont parse variants, spreads, etc
590
729
  const skipped = new Set<t.ObjectProperty | t.SpreadElement | t.ObjectMethod>()
591
730
  const styles = {}
731
+ const staticDefaultProps = {}
592
732
 
593
733
  // Generate scope object at this level
594
734
  const staticNamespace = getStaticBindingsForScope(
@@ -610,6 +750,19 @@ export function createExtractor(
610
750
  const attemptEvalSafe = createSafeEvaluator(attemptEval)
611
751
 
612
752
  for (const property of definition.properties) {
753
+ if (
754
+ t.isObjectProperty(property) &&
755
+ (t.isIdentifier(property.key) || t.isStringLiteral(property.key))
756
+ ) {
757
+ const key = t.isIdentifier(property.key)
758
+ ? property.key.name
759
+ : property.key.value
760
+ const defaultPropValue = attemptEvalSafe(property.value)
761
+ if (defaultPropValue !== FAILED_EVAL) {
762
+ staticDefaultProps[key] = defaultPropValue
763
+ }
764
+ }
765
+
613
766
  if (
614
767
  !t.isObjectProperty(property) ||
615
768
  !t.isIdentifier(property.key) ||
@@ -689,6 +842,34 @@ export function createExtractor(
689
842
 
690
843
  res.styled++
691
844
 
845
+ // register so JSX handler can find this component (same-file and cross-file)
846
+ if (extractStyledDefinitions && enableDynamicEvaluation && Component) {
847
+ const dynamicStaticConfig = {
848
+ ...Component.staticConfig,
849
+ defaultProps: {
850
+ ...Component.staticConfig.defaultProps,
851
+ ...staticDefaultProps,
852
+ },
853
+ }
854
+
855
+ // add to allLoadedComponents with '' so getValidComponent matches when moduleName is ''
856
+ // (same-file styled components have '' as moduleName in JSX handler)
857
+ propsWithFileInfo.allLoadedComponents.push({
858
+ moduleName: '',
859
+ nameToInfo: { [variableName]: { staticConfig: dynamicStaticConfig } },
860
+ })
861
+
862
+ // also cache by file path so other files importing from this path can find it
863
+ if (sourcePath) {
864
+ let existing = dynamicComponentCache.get(sourcePath)
865
+ if (!existing) {
866
+ existing = { moduleName: sourcePath, nameToInfo: {} }
867
+ dynamicComponentCache.set(sourcePath, existing)
868
+ }
869
+ existing.nameToInfo[variableName] = { staticConfig: dynamicStaticConfig }
870
+ }
871
+ }
872
+
692
873
  if (shouldPrintDebug) {
693
874
  logger.info(`Extracted styled(${variableName})`)
694
875
  }
@@ -717,22 +898,77 @@ export function createExtractor(
717
898
  // validate its a proper import from tamagui (or internally inside tamagui)
718
899
  const binding = traversePath.scope.getBinding(node.name.name)
719
900
  let moduleName = ''
901
+ let dynamicComponent: { staticConfig: any } | null = null
720
902
 
721
903
  if (binding) {
722
904
  if (t.isImportDeclaration(binding.path.parent)) {
723
905
  moduleName = binding.path.parent.source.value
724
906
  if (!isValidImport(propsWithFileInfo, moduleName, binding.identifier.name)) {
725
- if (shouldPrintDebug) {
726
- logger.info(
727
- ` - Binding in component ${componentName} not valid import: "${binding.identifier.name}" isn't in ${moduleName}\n`
728
- )
907
+ // fallback: try dynamic component cache for local imports (relative or tsconfig alias)
908
+ if (enableDynamicEvaluation && sourcePath) {
909
+ const resolved = resolveImportPath(sourcePath, moduleName)
910
+ if (resolved) {
911
+ // check cache first
912
+ const cached = dynamicComponentCache.get(resolved)
913
+ if (cached?.nameToInfo[binding.identifier.name]) {
914
+ dynamicComponent = cached.nameToInfo[binding.identifier.name]
915
+ } else if (
916
+ !dynamicLoadingInProgress.has(resolved) &&
917
+ mightHaveStyledComponents(resolved)
918
+ ) {
919
+ // proactively load the file
920
+ dynamicLoadingInProgress.add(resolved)
921
+ try {
922
+ const out = loadTamaguiSync({
923
+ forceExports: true,
924
+ components: [resolved],
925
+ })
926
+ if (out?.components) {
927
+ for (const comp of out.components) {
928
+ // merge into cache
929
+ let existing = dynamicComponentCache.get(resolved)
930
+ if (!existing) {
931
+ existing = { moduleName: resolved, nameToInfo: {} }
932
+ dynamicComponentCache.set(resolved, existing)
933
+ }
934
+ Object.assign(existing.nameToInfo, comp.nameToInfo)
935
+ // also add to allLoadedComponents so getValidComponent works
936
+ propsWithFileInfo.allLoadedComponents.push({
937
+ moduleName: resolved,
938
+ nameToInfo: comp.nameToInfo,
939
+ })
940
+ }
941
+ const cachedNow = dynamicComponentCache.get(resolved)
942
+ if (cachedNow?.nameToInfo[binding.identifier.name]) {
943
+ dynamicComponent = cachedNow.nameToInfo[binding.identifier.name]
944
+ }
945
+ }
946
+ } catch (err) {
947
+ if (shouldPrintDebug) {
948
+ logger.info(` - Failed to dynamically load ${resolved}: ${err}`)
949
+ }
950
+ } finally {
951
+ dynamicLoadingInProgress.delete(resolved)
952
+ }
953
+ }
954
+ }
955
+ }
956
+
957
+ if (!dynamicComponent) {
958
+ if (shouldPrintDebug) {
959
+ logger.info(
960
+ ` - Binding in component ${componentName} not valid import: "${binding.identifier.name}" isn't in ${moduleName}\n`
961
+ )
962
+ }
963
+ return
729
964
  }
730
- return
731
965
  }
732
966
  }
733
967
  }
734
968
 
735
- const component = getValidComponent(propsWithFileInfo, moduleName, node.name.name)
969
+ const component =
970
+ dynamicComponent ||
971
+ getValidComponent(propsWithFileInfo, moduleName, node.name.name)
736
972
  if (!component || !component.staticConfig) {
737
973
  if (shouldPrintDebug) {
738
974
  logger.info(`\n - No Tamagui conf for: ${node.name.name}\n`)
@@ -824,13 +1060,6 @@ export function createExtractor(
824
1060
  const isTextView = staticConfig.isText || false
825
1061
  const validStyles = staticConfig?.validStyles ?? {}
826
1062
 
827
- if (process.env.NODE_ENV === 'production') {
828
- if (isTextView) {
829
- // temporarily disabled - need to fix css nesting dix
830
- return
831
- }
832
- }
833
-
834
1063
  // find render="a" render="main" etc dom indicators
835
1064
  let tagName = defaultProps.render ?? (isTextView ? 'span' : 'div')
836
1065
  traversePath
@@ -1215,6 +1444,12 @@ export function createExtractor(
1215
1444
  )
1216
1445
  // remove className - we dont use rnw styling
1217
1446
  delete out.className
1447
+ // remove style - rnw createDOMProps unconditionally emits a
1448
+ // (possibly empty) style key, but we passed it a single
1449
+ // non-style prop. Leaving it in causes Object.keys(out) to
1450
+ // iterate twice and emit the original JSXAttribute twice
1451
+ // (e.g. duplicate testID), breaking the DOM output.
1452
+ delete out.style
1218
1453
  }
1219
1454
  }
1220
1455
 
@@ -1238,6 +1473,23 @@ export function createExtractor(
1238
1473
  key === '__source' ||
1239
1474
  key === '__self'
1240
1475
  ) {
1476
+ if (
1477
+ styleValue === FAILED_EVAL &&
1478
+ key !== name &&
1479
+ t.isJSXAttribute(attr.value)
1480
+ ) {
1481
+ // createDOMProps renamed the prop (e.g. testID -> data-testid).
1482
+ // preserve the original expression value but use the new
1483
+ // attribute name. restricted to FAILED_EVAL because the
1484
+ // later `case 'attr'` rename pass only runs on
1485
+ // statically-evaluable values; for static values that pass
1486
+ // intentionally preserves some prop names (e.g. focusable
1487
+ // in v2) instead of doing the createDOMProps rename.
1488
+ return {
1489
+ type: 'attr',
1490
+ value: t.jsxAttribute(t.jsxIdentifier(key), attr.value.value),
1491
+ } as const
1492
+ }
1241
1493
  return attr
1242
1494
  }
1243
1495
  if (shouldPrintDebug) {
@@ -1284,7 +1536,7 @@ export function createExtractor(
1284
1536
  return attr
1285
1537
  }
1286
1538
 
1287
- // $platform-web, $platform-native, $platform-ios, $platform-android
1539
+ // $platform-web, $platform-native, $platform-ios, $platform-android, $platform-tv, $platform-androidtv, $platform-tvos
1288
1540
  if (name.startsWith('$platform-')) {
1289
1541
  const platformName = name.slice(10) // remove '$platform-'
1290
1542
  const isMatchingPlatform =
@@ -1306,6 +1558,20 @@ export function createExtractor(
1306
1558
  attr: path.node,
1307
1559
  }))
1308
1560
  } else {
1561
+ // On native builds, sub-platform variants (android, ios, tv, androidtv, tvos)
1562
+ // can't be resolved at compile time - leave for runtime evaluation
1563
+ if (
1564
+ platform === 'native' &&
1565
+ nativeOnlyPlatforms.has(platformName)
1566
+ ) {
1567
+ if (shouldPrintDebug) {
1568
+ logger.info(
1569
+ ` ! keeping platform-specific style for runtime evaluation: ${name}`
1570
+ )
1571
+ }
1572
+ inlined.set(name, true)
1573
+ return attr
1574
+ }
1309
1575
  // Platform doesn't match, skip these styles entirely
1310
1576
  if (shouldPrintDebug) {
1311
1577
  logger.info(` ! skipping non-matching platform style: ${name}`)
@@ -1912,6 +2178,10 @@ export function createExtractor(
1912
2178
  )
1913
2179
  // remove rnw className use ours
1914
2180
  out.className = cn
2181
+ // see note in single-prop branch above; createDOMProps
2182
+ // also emits a stray style key here that would duplicate
2183
+ // emitted JSXAttributes downstream.
2184
+ delete out.style
1915
2185
  }
1916
2186
  if (shouldPrintDebug) {
1917
2187
  logger.info([' - expanded variant', name, out].join(' '))
@@ -0,0 +1,42 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { dirname, extname, join } from 'node:path'
3
+
4
+ type ModuleFormat = 'cjs' | 'esm'
5
+
6
+ // cache per directory to avoid repeated fs reads
7
+ const formatCache = new Map<string, ModuleFormat>()
8
+
9
+ export function detectModuleFormat(filePath: string): ModuleFormat {
10
+ const ext = extname(filePath)
11
+
12
+ // definitive by extension
13
+ if (ext === '.mjs') return 'esm'
14
+ if (ext === '.cjs') return 'cjs'
15
+
16
+ // walk up to find nearest package.json with "type" field
17
+ let dir = dirname(filePath)
18
+ while (true) {
19
+ if (formatCache.has(dir)) {
20
+ return formatCache.get(dir)!
21
+ }
22
+
23
+ try {
24
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'))
25
+ const format: ModuleFormat = pkg.type === 'module' ? 'esm' : 'cjs'
26
+ formatCache.set(dir, format)
27
+ return format
28
+ } catch {
29
+ // no package.json or malformed, keep walking
30
+ }
31
+
32
+ const parent = dirname(dir)
33
+ if (parent === dir) break
34
+ dir = parent
35
+ }
36
+
37
+ return 'cjs'
38
+ }
39
+
40
+ export function clearFormatCache() {
41
+ formatCache.clear()
42
+ }
@@ -25,6 +25,11 @@ export function TsconfigPathsPlugin(): Plugin {
25
25
  name,
26
26
  setup({ onResolve }) {
27
27
  onResolve({ filter: /.*/ }, (args) => {
28
+ // skip @tamagui packages - they should be externalized, not resolved via tsconfig
29
+ if (args.path.startsWith('@tamagui/')) {
30
+ return null
31
+ }
32
+
28
33
  const paths = compilerOptions.paths || {}
29
34
  const hasMatchingPath = Object.keys(paths).some((p) =>
30
35
  new RegExp(p.replace('*', '\\w*')).test(args.path)
@@ -59,7 +64,7 @@ export function TsconfigPathsPlugin(): Plugin {
59
64
  }
60
65
  }
61
66
 
62
- function loadCompilerOptionsFromTsconfig(tsconfig?: Tsconfig | string) {
67
+ export function loadCompilerOptionsFromTsconfig(tsconfig?: Tsconfig | string) {
63
68
  if (!tsconfig) {
64
69
  const configPath =
65
70
  findConfigFile(process.cwd(), sys.fileExists, 'tsconfig.json') ||