@tamagui/static 2.0.0-rc.4 → 2.0.0-rc.40

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 (185) 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 +18 -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 +177 -101
  9. package/dist/extractor/bundleConfig.cjs +554 -167
  10. package/dist/extractor/concatClassName.cjs +41 -29
  11. package/dist/extractor/createEvaluator.cjs +54 -41
  12. package/dist/extractor/createExtractor.cjs +1400 -581
  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 +149 -104
  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 +38 -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 +167 -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 +26 -22
  53. package/src/checkDeps.ts +305 -68
  54. package/src/exports.ts +1 -0
  55. package/src/extractor/babelParse.ts +1 -0
  56. package/src/extractor/bundle.ts +140 -37
  57. package/src/extractor/bundleConfig.ts +435 -61
  58. package/src/extractor/createExtractor.ts +283 -48
  59. package/src/extractor/detectModuleFormat.ts +42 -0
  60. package/src/extractor/esbuildTsconfigPaths.ts +6 -1
  61. package/src/extractor/extractToClassNames.ts +15 -9
  62. package/src/extractor/extractToNative.ts +66 -33
  63. package/src/extractor/hasTopLevelAwait.ts +28 -0
  64. package/src/extractor/loadTamagui.ts +2 -2
  65. package/src/getPragmaOptions.ts +6 -1
  66. package/src/registerRequire.ts +88 -8
  67. package/types/checkDeps.d.ts.map +1 -1
  68. package/types/exports.d.ts +1 -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/getPragmaOptions.d.ts.map +1 -1
  85. package/types/registerRequire.d.ts.map +1 -1
  86. package/dist/check-dep-versions.js +0 -389
  87. package/dist/check-dep-versions.js.map +0 -6
  88. package/dist/checkDeps.js +0 -60
  89. package/dist/checkDeps.js.map +0 -6
  90. package/dist/constants.js +0 -34
  91. package/dist/constants.js.map +0 -6
  92. package/dist/exports.js +0 -34
  93. package/dist/exports.js.map +0 -6
  94. package/dist/extractor/accessSafe.js +0 -47
  95. package/dist/extractor/accessSafe.js.map +0 -6
  96. package/dist/extractor/babelParse.js +0 -59
  97. package/dist/extractor/babelParse.js.map +0 -6
  98. package/dist/extractor/buildClassName.js +0 -72
  99. package/dist/extractor/buildClassName.js.map +0 -6
  100. package/dist/extractor/bundle.js +0 -135
  101. package/dist/extractor/bundle.js.map +0 -6
  102. package/dist/extractor/bundleConfig.js +0 -352
  103. package/dist/extractor/bundleConfig.js.map +0 -6
  104. package/dist/extractor/concatClassName.js +0 -69
  105. package/dist/extractor/concatClassName.js.map +0 -6
  106. package/dist/extractor/createEvaluator.js +0 -66
  107. package/dist/extractor/createEvaluator.js.map +0 -6
  108. package/dist/extractor/createExtractor.js +0 -1215
  109. package/dist/extractor/createExtractor.js.map +0 -6
  110. package/dist/extractor/createLogger.js +0 -32
  111. package/dist/extractor/createLogger.js.map +0 -6
  112. package/dist/extractor/ensureImportingConcat.js +0 -50
  113. package/dist/extractor/ensureImportingConcat.js.map +0 -6
  114. package/dist/extractor/errors.js +0 -22
  115. package/dist/extractor/errors.js.map +0 -6
  116. package/dist/extractor/esbuildAliasPlugin.js +0 -36
  117. package/dist/extractor/esbuildAliasPlugin.js.map +0 -6
  118. package/dist/extractor/esbuildTsconfigPaths.js +0 -79
  119. package/dist/extractor/esbuildTsconfigPaths.js.map +0 -6
  120. package/dist/extractor/evaluateAstNode.js +0 -99
  121. package/dist/extractor/evaluateAstNode.js.map +0 -6
  122. package/dist/extractor/extractHelpers.js +0 -108
  123. package/dist/extractor/extractHelpers.js.map +0 -6
  124. package/dist/extractor/extractMediaStyle.js +0 -123
  125. package/dist/extractor/extractMediaStyle.js.map +0 -6
  126. package/dist/extractor/extractToClassNames.js +0 -351
  127. package/dist/extractor/extractToClassNames.js.map +0 -6
  128. package/dist/extractor/extractToNative.js +0 -286
  129. package/dist/extractor/extractToNative.js.map +0 -6
  130. package/dist/extractor/findTopmostFunction.js +0 -32
  131. package/dist/extractor/findTopmostFunction.js.map +0 -6
  132. package/dist/extractor/generatedUid.js +0 -42
  133. package/dist/extractor/generatedUid.js.map +0 -6
  134. package/dist/extractor/getPrefixLogs.js +0 -24
  135. package/dist/extractor/getPrefixLogs.js.map +0 -6
  136. package/dist/extractor/getPropValueFromAttributes.js +0 -65
  137. package/dist/extractor/getPropValueFromAttributes.js.map +0 -6
  138. package/dist/extractor/getSourceModule.js +0 -62
  139. package/dist/extractor/getSourceModule.js.map +0 -6
  140. package/dist/extractor/getStaticBindingsForScope.js +0 -145
  141. package/dist/extractor/getStaticBindingsForScope.js.map +0 -6
  142. package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.js +0 -32
  143. package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.js.map +0 -6
  144. package/dist/extractor/hoistClassNames.js +0 -63
  145. package/dist/extractor/hoistClassNames.js.map +0 -6
  146. package/dist/extractor/literalToAst.js +0 -90
  147. package/dist/extractor/literalToAst.js.map +0 -6
  148. package/dist/extractor/loadFile.js +0 -14
  149. package/dist/extractor/loadFile.js.map +0 -6
  150. package/dist/extractor/loadTamagui.js +0 -306
  151. package/dist/extractor/loadTamagui.js.map +0 -6
  152. package/dist/extractor/logLines.js +0 -30
  153. package/dist/extractor/logLines.js.map +0 -6
  154. package/dist/extractor/normalizeTernaries.js +0 -61
  155. package/dist/extractor/normalizeTernaries.js.map +0 -6
  156. package/dist/extractor/propsToFontFamilyCache.js +0 -33
  157. package/dist/extractor/propsToFontFamilyCache.js.map +0 -6
  158. package/dist/extractor/regenerateConfig.js +0 -123
  159. package/dist/extractor/regenerateConfig.js.map +0 -6
  160. package/dist/extractor/removeUnusedHooks.js +0 -72
  161. package/dist/extractor/removeUnusedHooks.js.map +0 -6
  162. package/dist/extractor/timer.js +0 -38
  163. package/dist/extractor/timer.js.map +0 -6
  164. package/dist/extractor/validHTMLAttributes.js +0 -72
  165. package/dist/extractor/validHTMLAttributes.js.map +0 -6
  166. package/dist/extractor/watchTamaguiConfig.js +0 -57
  167. package/dist/extractor/watchTamaguiConfig.js.map +0 -6
  168. package/dist/getPragmaOptions.js +0 -46
  169. package/dist/getPragmaOptions.js.map +0 -6
  170. package/dist/helpers/memoize.js +0 -33
  171. package/dist/helpers/memoize.js.map +0 -6
  172. package/dist/helpers/requireTamaguiCore.js +0 -30
  173. package/dist/helpers/requireTamaguiCore.js.map +0 -6
  174. package/dist/index.js +0 -30
  175. package/dist/index.js.map +0 -6
  176. package/dist/registerRequire.js +0 -100
  177. package/dist/registerRequire.js.map +0 -6
  178. package/dist/server.js +0 -58
  179. package/dist/server.js.map +0 -6
  180. package/dist/setup.js +0 -1
  181. package/dist/setup.js.map +0 -6
  182. package/dist/types.js +0 -14
  183. package/dist/types.js.map +0 -6
  184. package/dist/worker.js +0 -72
  185. package/dist/worker.js.map +0 -6
@@ -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
  }
@@ -484,6 +623,10 @@ export function createExtractor(
484
623
  ? path.parent.id.name
485
624
  : 'unknown'
486
625
 
626
+ if (shouldPrintDebug) {
627
+ logger.info(` [styled] Found styled(${variableName})`)
628
+ }
629
+
487
630
  const parentNode = path.node.arguments[0]
488
631
 
489
632
  if (!t.isIdentifier(parentNode)) {
@@ -496,10 +639,12 @@ export function createExtractor(
496
639
  return
497
640
  }
498
641
 
499
- let Component = getValidImportedComponent(variableName)
642
+ // look up by parent first (e.g. View in `styled(View, {...})`), then by self
643
+ let Component =
644
+ getValidImportedComponent(parentName) || getValidImportedComponent(variableName)
500
645
 
501
646
  if (!Component) {
502
- if (enableDynamicEvaluation !== true) {
647
+ if (!enableDynamicEvaluation) {
503
648
  return
504
649
  }
505
650
 
@@ -583,6 +728,7 @@ export function createExtractor(
583
728
  // for now dont parse variants, spreads, etc
584
729
  const skipped = new Set<t.ObjectProperty | t.SpreadElement | t.ObjectMethod>()
585
730
  const styles = {}
731
+ const staticDefaultProps = {}
586
732
 
587
733
  // Generate scope object at this level
588
734
  const staticNamespace = getStaticBindingsForScope(
@@ -604,6 +750,19 @@ export function createExtractor(
604
750
  const attemptEvalSafe = createSafeEvaluator(attemptEval)
605
751
 
606
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
+
607
766
  if (
608
767
  !t.isObjectProperty(property) ||
609
768
  !t.isIdentifier(property.key) ||
@@ -667,23 +826,9 @@ export function createExtractor(
667
826
  )
668
827
  }
669
828
 
670
- // leave only un-parsed props...
671
- // preserve original order
672
- definition.properties = definition.properties.map((prop) => {
673
- if (
674
- skipped.has(prop) ||
675
- !t.isObjectProperty(prop) ||
676
- !t.isIdentifier(prop.key)
677
- ) {
678
- return prop
679
- }
680
- const key = prop.key.name
681
- const value = classNames[key]
682
- if (value) {
683
- return t.objectProperty(t.stringLiteral(key), t.stringLiteral(value))
684
- }
685
- return prop
686
- })
829
+ // don't replace definition values with class name strings -
830
+ // the runtime needs real values for animations, context, and group styles.
831
+ // we only emit the CSS rules so they're available if the runtime uses classNames.
687
832
 
688
833
  if (out.rulesToInsert) {
689
834
  for (const key in out.rulesToInsert) {
@@ -697,6 +842,34 @@ export function createExtractor(
697
842
 
698
843
  res.styled++
699
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
+
700
873
  if (shouldPrintDebug) {
701
874
  logger.info(`Extracted styled(${variableName})`)
702
875
  }
@@ -725,22 +898,77 @@ export function createExtractor(
725
898
  // validate its a proper import from tamagui (or internally inside tamagui)
726
899
  const binding = traversePath.scope.getBinding(node.name.name)
727
900
  let moduleName = ''
901
+ let dynamicComponent: { staticConfig: any } | null = null
728
902
 
729
903
  if (binding) {
730
904
  if (t.isImportDeclaration(binding.path.parent)) {
731
905
  moduleName = binding.path.parent.source.value
732
906
  if (!isValidImport(propsWithFileInfo, moduleName, binding.identifier.name)) {
733
- if (shouldPrintDebug) {
734
- logger.info(
735
- ` - Binding in component ${componentName} not valid import: "${binding.identifier.name}" isn't in ${moduleName}\n`
736
- )
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
737
964
  }
738
- return
739
965
  }
740
966
  }
741
967
  }
742
968
 
743
- const component = getValidComponent(propsWithFileInfo, moduleName, node.name.name)
969
+ const component =
970
+ dynamicComponent ||
971
+ getValidComponent(propsWithFileInfo, moduleName, node.name.name)
744
972
  if (!component || !component.staticConfig) {
745
973
  if (shouldPrintDebug) {
746
974
  logger.info(`\n - No Tamagui conf for: ${node.name.name}\n`)
@@ -832,13 +1060,6 @@ export function createExtractor(
832
1060
  const isTextView = staticConfig.isText || false
833
1061
  const validStyles = staticConfig?.validStyles ?? {}
834
1062
 
835
- if (process.env.NODE_ENV === 'production') {
836
- if (isTextView) {
837
- // temporarily disabled - need to fix css nesting dix
838
- return
839
- }
840
- }
841
-
842
1063
  // find render="a" render="main" etc dom indicators
843
1064
  let tagName = defaultProps.render ?? (isTextView ? 'span' : 'div')
844
1065
  traversePath
@@ -1292,7 +1513,7 @@ export function createExtractor(
1292
1513
  return attr
1293
1514
  }
1294
1515
 
1295
- // $platform-web, $platform-native, $platform-ios, $platform-android
1516
+ // $platform-web, $platform-native, $platform-ios, $platform-android, $platform-tv, $platform-androidtv, $platform-tvos
1296
1517
  if (name.startsWith('$platform-')) {
1297
1518
  const platformName = name.slice(10) // remove '$platform-'
1298
1519
  const isMatchingPlatform =
@@ -1314,6 +1535,20 @@ export function createExtractor(
1314
1535
  attr: path.node,
1315
1536
  }))
1316
1537
  } else {
1538
+ // On native builds, sub-platform variants (android, ios, tv, androidtv, tvos)
1539
+ // can't be resolved at compile time - leave for runtime evaluation
1540
+ if (
1541
+ platform === 'native' &&
1542
+ nativeOnlyPlatforms.has(platformName)
1543
+ ) {
1544
+ if (shouldPrintDebug) {
1545
+ logger.info(
1546
+ ` ! keeping platform-specific style for runtime evaluation: ${name}`
1547
+ )
1548
+ }
1549
+ inlined.set(name, true)
1550
+ return attr
1551
+ }
1317
1552
  // Platform doesn't match, skip these styles entirely
1318
1553
  if (shouldPrintDebug) {
1319
1554
  logger.info(` ! skipping non-matching platform style: ${name}`)
@@ -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') ||
@@ -107,7 +107,7 @@ export async function extractToClassNames({
107
107
  sourcePath,
108
108
  extractStyledDefinitions: true,
109
109
  onStyledDefinitionRule(identifier, rules) {
110
- const css = rules.join(';')
110
+ const css = rules.join('\n')
111
111
  if (shouldPrintDebug) {
112
112
  console.info(`adding styled() rule: .${identifier} ${css}`)
113
113
  }
@@ -343,11 +343,18 @@ export async function extractToClassNames({
343
343
  baseClassNameStr = `font_${baseFontFamily}${baseClassNameStr ? ` ${baseClassNameStr}` : ''}`
344
344
  }
345
345
 
346
- let base = staticConfig.componentName
347
- ? t.stringLiteral(
348
- `is_${staticConfig.componentName}${baseClassNameStr ? ` ${baseClassNameStr}` : ''}`
349
- )
350
- : t.stringLiteral(baseClassNameStr || '')
346
+ // add is_View or is_Text base class matching runtime behavior
347
+ const baseTypeClass = staticConfig.isText ? 'is_Text' : 'is_View'
348
+ baseClassNameStr = `${baseTypeClass}${baseClassNameStr ? ` ${baseClassNameStr}` : ''}`
349
+
350
+ // add component name class (skip 'Text' since is_Text already covers it)
351
+ const componentNameFinal = staticConfig.componentName
352
+ let base =
353
+ componentNameFinal && componentNameFinal !== 'Text'
354
+ ? t.stringLiteral(
355
+ `is_${componentNameFinal}${baseClassNameStr ? ` ${baseClassNameStr}` : ''}`
356
+ )
357
+ : t.stringLiteral(baseClassNameStr || '')
351
358
 
352
359
  attrClassName = attrClassName as t.Expression | null // actual typescript bug, flatMap doesn't update from never
353
360
 
@@ -616,10 +623,9 @@ function hoistClassName(path: NodePath<t.JSXElement>, str: string) {
616
623
  function cleanupClassName(inStr: string) {
617
624
  const out = new Set<string>()
618
625
  for (const part of inStr.split(' ')) {
619
- if (part === ' ') continue
626
+ if (!part || part === ' ') continue
620
627
  if (part === 'font_') continue
621
628
  out.add(part)
622
629
  }
623
- // always a space after for joining
624
- return [...out].join(' ') + ' '
630
+ return [...out].join(' ')
625
631
  }