@tamagui/static 2.0.0-rc.3 → 2.0.0-rc.31

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 (141) hide show
  1. package/dist/checkDeps.cjs +164 -31
  2. package/dist/exports.cjs +3 -0
  3. package/dist/extractor/bundle.cjs +72 -35
  4. package/dist/extractor/bundleConfig.cjs +219 -35
  5. package/dist/extractor/createExtractor.cjs +170 -28
  6. package/dist/extractor/detectModuleFormat.cjs +49 -0
  7. package/dist/extractor/esbuildTsconfigPaths.cjs +3 -1
  8. package/dist/extractor/extractToClassNames.cjs +7 -5
  9. package/dist/extractor/extractToNative.cjs +7 -8
  10. package/dist/extractor/loadTamagui.cjs +1 -1
  11. package/dist/getPragmaOptions.cjs +7 -3
  12. package/dist/registerRequire.cjs +23 -14
  13. package/package.json +26 -22
  14. package/src/checkDeps.ts +305 -68
  15. package/src/exports.ts +1 -0
  16. package/src/extractor/bundle.ts +140 -37
  17. package/src/extractor/bundleConfig.ts +435 -61
  18. package/src/extractor/createExtractor.ts +261 -41
  19. package/src/extractor/detectModuleFormat.ts +42 -0
  20. package/src/extractor/esbuildTsconfigPaths.ts +6 -1
  21. package/src/extractor/extractToClassNames.ts +15 -9
  22. package/src/extractor/extractToNative.ts +32 -25
  23. package/src/extractor/loadTamagui.ts +2 -2
  24. package/src/getPragmaOptions.ts +6 -1
  25. package/src/registerRequire.ts +40 -8
  26. package/types/checkDeps.d.ts.map +1 -1
  27. package/types/exports.d.ts +1 -0
  28. package/types/exports.d.ts.map +1 -1
  29. package/types/extractor/bundle.d.ts +83 -1
  30. package/types/extractor/bundle.d.ts.map +1 -1
  31. package/types/extractor/bundleConfig.d.ts +15 -2
  32. package/types/extractor/bundleConfig.d.ts.map +1 -1
  33. package/types/extractor/createExtractor.d.ts.map +1 -1
  34. package/types/extractor/detectModuleFormat.d.ts +5 -0
  35. package/types/extractor/detectModuleFormat.d.ts.map +1 -0
  36. package/types/extractor/esbuildTsconfigPaths.d.ts +8 -0
  37. package/types/extractor/esbuildTsconfigPaths.d.ts.map +1 -1
  38. package/types/extractor/extractToClassNames.d.ts.map +1 -1
  39. package/types/extractor/extractToNative.d.ts.map +1 -1
  40. package/types/getPragmaOptions.d.ts.map +1 -1
  41. package/types/registerRequire.d.ts.map +1 -1
  42. package/dist/check-dep-versions.js +0 -389
  43. package/dist/check-dep-versions.js.map +0 -6
  44. package/dist/checkDeps.js +0 -60
  45. package/dist/checkDeps.js.map +0 -6
  46. package/dist/constants.js +0 -34
  47. package/dist/constants.js.map +0 -6
  48. package/dist/exports.js +0 -34
  49. package/dist/exports.js.map +0 -6
  50. package/dist/extractor/accessSafe.js +0 -47
  51. package/dist/extractor/accessSafe.js.map +0 -6
  52. package/dist/extractor/babelParse.js +0 -59
  53. package/dist/extractor/babelParse.js.map +0 -6
  54. package/dist/extractor/buildClassName.js +0 -72
  55. package/dist/extractor/buildClassName.js.map +0 -6
  56. package/dist/extractor/bundle.js +0 -135
  57. package/dist/extractor/bundle.js.map +0 -6
  58. package/dist/extractor/bundleConfig.js +0 -352
  59. package/dist/extractor/bundleConfig.js.map +0 -6
  60. package/dist/extractor/concatClassName.js +0 -69
  61. package/dist/extractor/concatClassName.js.map +0 -6
  62. package/dist/extractor/createEvaluator.js +0 -66
  63. package/dist/extractor/createEvaluator.js.map +0 -6
  64. package/dist/extractor/createExtractor.js +0 -1215
  65. package/dist/extractor/createExtractor.js.map +0 -6
  66. package/dist/extractor/createLogger.js +0 -32
  67. package/dist/extractor/createLogger.js.map +0 -6
  68. package/dist/extractor/ensureImportingConcat.js +0 -50
  69. package/dist/extractor/ensureImportingConcat.js.map +0 -6
  70. package/dist/extractor/errors.js +0 -22
  71. package/dist/extractor/errors.js.map +0 -6
  72. package/dist/extractor/esbuildAliasPlugin.js +0 -36
  73. package/dist/extractor/esbuildAliasPlugin.js.map +0 -6
  74. package/dist/extractor/esbuildTsconfigPaths.js +0 -79
  75. package/dist/extractor/esbuildTsconfigPaths.js.map +0 -6
  76. package/dist/extractor/evaluateAstNode.js +0 -99
  77. package/dist/extractor/evaluateAstNode.js.map +0 -6
  78. package/dist/extractor/extractHelpers.js +0 -108
  79. package/dist/extractor/extractHelpers.js.map +0 -6
  80. package/dist/extractor/extractMediaStyle.js +0 -123
  81. package/dist/extractor/extractMediaStyle.js.map +0 -6
  82. package/dist/extractor/extractToClassNames.js +0 -351
  83. package/dist/extractor/extractToClassNames.js.map +0 -6
  84. package/dist/extractor/extractToNative.js +0 -286
  85. package/dist/extractor/extractToNative.js.map +0 -6
  86. package/dist/extractor/findTopmostFunction.js +0 -32
  87. package/dist/extractor/findTopmostFunction.js.map +0 -6
  88. package/dist/extractor/generatedUid.js +0 -42
  89. package/dist/extractor/generatedUid.js.map +0 -6
  90. package/dist/extractor/getPrefixLogs.js +0 -24
  91. package/dist/extractor/getPrefixLogs.js.map +0 -6
  92. package/dist/extractor/getPropValueFromAttributes.js +0 -65
  93. package/dist/extractor/getPropValueFromAttributes.js.map +0 -6
  94. package/dist/extractor/getSourceModule.js +0 -62
  95. package/dist/extractor/getSourceModule.js.map +0 -6
  96. package/dist/extractor/getStaticBindingsForScope.js +0 -145
  97. package/dist/extractor/getStaticBindingsForScope.js.map +0 -6
  98. package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.js +0 -32
  99. package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.js.map +0 -6
  100. package/dist/extractor/hoistClassNames.js +0 -63
  101. package/dist/extractor/hoistClassNames.js.map +0 -6
  102. package/dist/extractor/literalToAst.js +0 -90
  103. package/dist/extractor/literalToAst.js.map +0 -6
  104. package/dist/extractor/loadFile.js +0 -14
  105. package/dist/extractor/loadFile.js.map +0 -6
  106. package/dist/extractor/loadTamagui.js +0 -306
  107. package/dist/extractor/loadTamagui.js.map +0 -6
  108. package/dist/extractor/logLines.js +0 -30
  109. package/dist/extractor/logLines.js.map +0 -6
  110. package/dist/extractor/normalizeTernaries.js +0 -61
  111. package/dist/extractor/normalizeTernaries.js.map +0 -6
  112. package/dist/extractor/propsToFontFamilyCache.js +0 -33
  113. package/dist/extractor/propsToFontFamilyCache.js.map +0 -6
  114. package/dist/extractor/regenerateConfig.js +0 -123
  115. package/dist/extractor/regenerateConfig.js.map +0 -6
  116. package/dist/extractor/removeUnusedHooks.js +0 -72
  117. package/dist/extractor/removeUnusedHooks.js.map +0 -6
  118. package/dist/extractor/timer.js +0 -38
  119. package/dist/extractor/timer.js.map +0 -6
  120. package/dist/extractor/validHTMLAttributes.js +0 -72
  121. package/dist/extractor/validHTMLAttributes.js.map +0 -6
  122. package/dist/extractor/watchTamaguiConfig.js +0 -57
  123. package/dist/extractor/watchTamaguiConfig.js.map +0 -6
  124. package/dist/getPragmaOptions.js +0 -46
  125. package/dist/getPragmaOptions.js.map +0 -6
  126. package/dist/helpers/memoize.js +0 -33
  127. package/dist/helpers/memoize.js.map +0 -6
  128. package/dist/helpers/requireTamaguiCore.js +0 -30
  129. package/dist/helpers/requireTamaguiCore.js.map +0 -6
  130. package/dist/index.js +0 -30
  131. package/dist/index.js.map +0 -6
  132. package/dist/registerRequire.js +0 -100
  133. package/dist/registerRequire.js.map +0 -6
  134. package/dist/server.js +0 -58
  135. package/dist/server.js.map +0 -6
  136. package/dist/setup.js +0 -1
  137. package/dist/setup.js.map +0 -6
  138. package/dist/types.js +0 -14
  139. package/dist/types.js.map +0 -6
  140. package/dist/worker.js +0 -72
  141. 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,
@@ -85,6 +88,29 @@ export function createExtractor(
85
88
  onPressIn: 'onMouseDown',
86
89
  onPressOut: 'onMouseUp',
87
90
  }),
91
+ ...(platform === 'native' && {
92
+ // native view props that should pass through without preventing flattening
93
+ testID: 'testID',
94
+ nativeID: 'nativeID',
95
+ accessibilityLabel: 'accessibilityLabel',
96
+ accessibilityHint: 'accessibilityHint',
97
+ accessibilityRole: 'accessibilityRole',
98
+ accessibilityState: 'accessibilityState',
99
+ accessibilityValue: 'accessibilityValue',
100
+ accessibilityActions: 'accessibilityActions',
101
+ accessibilityLabelledBy: 'accessibilityLabelledBy',
102
+ accessibilityLiveRegion: 'accessibilityLiveRegion',
103
+ accessibilityElementsHidden: 'accessibilityElementsHidden',
104
+ accessibilityViewIsModal: 'accessibilityViewIsModal',
105
+ importantForAccessibility: 'importantForAccessibility',
106
+ collapsable: 'collapsable',
107
+ needsOffscreenAlphaCompositing: 'needsOffscreenAlphaCompositing',
108
+ removeClippedSubviews: 'removeClippedSubviews',
109
+ renderToHardwareTextureAndroid: 'renderToHardwareTextureAndroid',
110
+ shouldRasterizeIOS: 'shouldRasterizeIOS',
111
+ hitSlop: 'hitSlop',
112
+ pointerEvents: 'pointerEvents',
113
+ }),
88
114
  }
89
115
 
90
116
  const componentState: TamaguiComponentState = {
@@ -113,6 +139,83 @@ export function createExtractor(
113
139
 
114
140
  let projectInfo: TamaguiProjectInfo | null = null
115
141
 
142
+ // cache of dynamically discovered styled components, keyed by absolute file path
143
+ // persists across files within the same worker/extractor instance
144
+ const dynamicComponentCache = new Map<string, LoadedComponents>()
145
+ const dynamicLoadingInProgress = new Set<string>()
146
+
147
+ // lazily loaded tsconfig compiler options for path alias resolution
148
+ let _compilerOptions: any = null
149
+ function getCompilerOptions() {
150
+ if (!_compilerOptions) {
151
+ try {
152
+ _compilerOptions = loadCompilerOptionsFromTsconfig()
153
+ } catch {
154
+ _compilerOptions = {}
155
+ }
156
+ }
157
+ return _compilerOptions
158
+ }
159
+
160
+ function resolveImportPath(fromFile: string, importPath: string): string | null {
161
+ if (importPath.startsWith('.')) {
162
+ // relative path resolution
163
+ const dir = dirname(fromFile)
164
+ const base = resolve(dir, importPath)
165
+ const extensions = ['.tsx', '.ts', '.jsx', '.js']
166
+ for (const ext of extensions) {
167
+ const full = base + ext
168
+ if (existsSync(full)) return full
169
+ }
170
+ // try index files
171
+ for (const ext of extensions) {
172
+ const full = resolve(base, `index${ext}`)
173
+ if (existsSync(full)) return full
174
+ }
175
+ return null
176
+ }
177
+
178
+ // tsconfig path alias resolution (e.g. ~/foo, @/bar)
179
+ const compilerOptions = getCompilerOptions()
180
+ if (compilerOptions.paths) {
181
+ try {
182
+ const { resolvedModule } = nodeModuleNameResolver(
183
+ importPath,
184
+ fromFile,
185
+ compilerOptions,
186
+ sys
187
+ )
188
+ if (
189
+ resolvedModule &&
190
+ !resolvedModule.resolvedFileName.endsWith('.d.ts') &&
191
+ !resolvedModule.isExternalLibraryImport
192
+ ) {
193
+ return resolvedModule.resolvedFileName
194
+ }
195
+ } catch {
196
+ // fallback - tsconfig resolution failed
197
+ }
198
+ }
199
+
200
+ return null
201
+ }
202
+
203
+ const styledCheckCache = new Map<string, boolean>()
204
+
205
+ function mightHaveStyledComponents(filePath: string): boolean {
206
+ const cached = styledCheckCache.get(filePath)
207
+ if (cached !== undefined) return cached
208
+ try {
209
+ const content = readFileSync(filePath, 'utf-8')
210
+ const result = content.includes('styled(')
211
+ styledCheckCache.set(filePath, result)
212
+ return result
213
+ } catch {
214
+ styledCheckCache.set(filePath, false)
215
+ return false
216
+ }
217
+ }
218
+
116
219
  // we load tamagui delayed because we need to set some global/env stuff before importing
117
220
  // otherwise we'd import `rnw` and cause it to evaluate react-native-web which causes errors
118
221
 
@@ -178,6 +281,12 @@ export function createExtractor(
178
281
  ...restProps
179
282
  } = options
180
283
 
284
+ // invalidate dynamic cache for this file on re-parse (HMR)
285
+ if (sourcePath && dynamicComponentCache.has(sourcePath)) {
286
+ dynamicComponentCache.delete(sourcePath)
287
+ styledCheckCache.delete(sourcePath)
288
+ }
289
+
181
290
  if (sourcePath.includes('.tamagui-dynamic-eval')) {
182
291
  return null
183
292
  }
@@ -375,12 +484,12 @@ export function createExtractor(
375
484
  logger.info(` - import via ${moduleName} ${valid}`)
376
485
  }
377
486
 
378
- if (extractStyledDefinitions) {
379
- if (valid) {
380
- if (node.specifiers.some((specifier) => specifier.local.name === 'styled')) {
381
- doesUseValidImport = true
382
- break
383
- }
487
+ if (extractStyledDefinitions && enableDynamicEvaluation) {
488
+ // check all imports for `styled`, not just valid packages
489
+ // styled( is basically guaranteed to be tamagui regardless of source
490
+ if (node.specifiers.some((specifier) => specifier.local.name === 'styled')) {
491
+ doesUseValidImport = true
492
+ // don't break - need to collect all import declarations for the styled() handler
384
493
  }
385
494
  }
386
495
 
@@ -400,7 +509,7 @@ export function createExtractor(
400
509
  }
401
510
  if (isValidComponent) {
402
511
  doesUseValidImport = true
403
- break
512
+ if (!(extractStyledDefinitions && enableDynamicEvaluation)) break
404
513
  }
405
514
  }
406
515
  }
@@ -411,6 +520,35 @@ export function createExtractor(
411
520
  )
412
521
  }
413
522
 
523
+ if (
524
+ !doesUseValidImport &&
525
+ extractStyledDefinitions &&
526
+ enableDynamicEvaluation &&
527
+ sourcePath
528
+ ) {
529
+ // check if any local import is in the dynamic cache or has styled components
530
+ for (const bodyPath of body) {
531
+ if (bodyPath.type !== 'ImportDeclaration') continue
532
+ const node = (
533
+ 'node' in bodyPath ? bodyPath.node : bodyPath
534
+ ) as t.ImportDeclaration
535
+ const moduleName = node.source.value
536
+
537
+ const resolved = resolveImportPath(sourcePath, moduleName)
538
+ if (!resolved) continue
539
+
540
+ if (dynamicComponentCache.has(resolved)) {
541
+ doesUseValidImport = true
542
+ break
543
+ }
544
+
545
+ if (mightHaveStyledComponents(resolved)) {
546
+ doesUseValidImport = true
547
+ break
548
+ }
549
+ }
550
+ }
551
+
414
552
  if (!doesUseValidImport) {
415
553
  return null
416
554
  }
@@ -484,6 +622,10 @@ export function createExtractor(
484
622
  ? path.parent.id.name
485
623
  : 'unknown'
486
624
 
625
+ if (shouldPrintDebug) {
626
+ logger.info(` [styled] Found styled(${variableName})`)
627
+ }
628
+
487
629
  const parentNode = path.node.arguments[0]
488
630
 
489
631
  if (!t.isIdentifier(parentNode)) {
@@ -496,10 +638,12 @@ export function createExtractor(
496
638
  return
497
639
  }
498
640
 
499
- let Component = getValidImportedComponent(variableName)
641
+ // look up by parent first (e.g. View in `styled(View, {...})`), then by self
642
+ let Component =
643
+ getValidImportedComponent(parentName) || getValidImportedComponent(variableName)
500
644
 
501
645
  if (!Component) {
502
- if (enableDynamicEvaluation !== true) {
646
+ if (!enableDynamicEvaluation) {
503
647
  return
504
648
  }
505
649
 
@@ -583,6 +727,7 @@ export function createExtractor(
583
727
  // for now dont parse variants, spreads, etc
584
728
  const skipped = new Set<t.ObjectProperty | t.SpreadElement | t.ObjectMethod>()
585
729
  const styles = {}
730
+ const staticDefaultProps = {}
586
731
 
587
732
  // Generate scope object at this level
588
733
  const staticNamespace = getStaticBindingsForScope(
@@ -604,6 +749,19 @@ export function createExtractor(
604
749
  const attemptEvalSafe = createSafeEvaluator(attemptEval)
605
750
 
606
751
  for (const property of definition.properties) {
752
+ if (
753
+ t.isObjectProperty(property) &&
754
+ (t.isIdentifier(property.key) || t.isStringLiteral(property.key))
755
+ ) {
756
+ const key = t.isIdentifier(property.key)
757
+ ? property.key.name
758
+ : property.key.value
759
+ const defaultPropValue = attemptEvalSafe(property.value)
760
+ if (defaultPropValue !== FAILED_EVAL) {
761
+ staticDefaultProps[key] = defaultPropValue
762
+ }
763
+ }
764
+
607
765
  if (
608
766
  !t.isObjectProperty(property) ||
609
767
  !t.isIdentifier(property.key) ||
@@ -667,23 +825,9 @@ export function createExtractor(
667
825
  )
668
826
  }
669
827
 
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
- })
828
+ // don't replace definition values with class name strings -
829
+ // the runtime needs real values for animations, context, and group styles.
830
+ // we only emit the CSS rules so they're available if the runtime uses classNames.
687
831
 
688
832
  if (out.rulesToInsert) {
689
833
  for (const key in out.rulesToInsert) {
@@ -697,6 +841,34 @@ export function createExtractor(
697
841
 
698
842
  res.styled++
699
843
 
844
+ // register so JSX handler can find this component (same-file and cross-file)
845
+ if (extractStyledDefinitions && enableDynamicEvaluation && Component) {
846
+ const dynamicStaticConfig = {
847
+ ...Component.staticConfig,
848
+ defaultProps: {
849
+ ...Component.staticConfig.defaultProps,
850
+ ...staticDefaultProps,
851
+ },
852
+ }
853
+
854
+ // add to allLoadedComponents with '' so getValidComponent matches when moduleName is ''
855
+ // (same-file styled components have '' as moduleName in JSX handler)
856
+ propsWithFileInfo.allLoadedComponents.push({
857
+ moduleName: '',
858
+ nameToInfo: { [variableName]: { staticConfig: dynamicStaticConfig } },
859
+ })
860
+
861
+ // also cache by file path so other files importing from this path can find it
862
+ if (sourcePath) {
863
+ let existing = dynamicComponentCache.get(sourcePath)
864
+ if (!existing) {
865
+ existing = { moduleName: sourcePath, nameToInfo: {} }
866
+ dynamicComponentCache.set(sourcePath, existing)
867
+ }
868
+ existing.nameToInfo[variableName] = { staticConfig: dynamicStaticConfig }
869
+ }
870
+ }
871
+
700
872
  if (shouldPrintDebug) {
701
873
  logger.info(`Extracted styled(${variableName})`)
702
874
  }
@@ -725,22 +897,77 @@ export function createExtractor(
725
897
  // validate its a proper import from tamagui (or internally inside tamagui)
726
898
  const binding = traversePath.scope.getBinding(node.name.name)
727
899
  let moduleName = ''
900
+ let dynamicComponent: { staticConfig: any } | null = null
728
901
 
729
902
  if (binding) {
730
903
  if (t.isImportDeclaration(binding.path.parent)) {
731
904
  moduleName = binding.path.parent.source.value
732
905
  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
- )
906
+ // fallback: try dynamic component cache for local imports (relative or tsconfig alias)
907
+ if (enableDynamicEvaluation && sourcePath) {
908
+ const resolved = resolveImportPath(sourcePath, moduleName)
909
+ if (resolved) {
910
+ // check cache first
911
+ const cached = dynamicComponentCache.get(resolved)
912
+ if (cached?.nameToInfo[binding.identifier.name]) {
913
+ dynamicComponent = cached.nameToInfo[binding.identifier.name]
914
+ } else if (
915
+ !dynamicLoadingInProgress.has(resolved) &&
916
+ mightHaveStyledComponents(resolved)
917
+ ) {
918
+ // proactively load the file
919
+ dynamicLoadingInProgress.add(resolved)
920
+ try {
921
+ const out = loadTamaguiSync({
922
+ forceExports: true,
923
+ components: [resolved],
924
+ })
925
+ if (out?.components) {
926
+ for (const comp of out.components) {
927
+ // merge into cache
928
+ let existing = dynamicComponentCache.get(resolved)
929
+ if (!existing) {
930
+ existing = { moduleName: resolved, nameToInfo: {} }
931
+ dynamicComponentCache.set(resolved, existing)
932
+ }
933
+ Object.assign(existing.nameToInfo, comp.nameToInfo)
934
+ // also add to allLoadedComponents so getValidComponent works
935
+ propsWithFileInfo.allLoadedComponents.push({
936
+ moduleName: resolved,
937
+ nameToInfo: comp.nameToInfo,
938
+ })
939
+ }
940
+ const cachedNow = dynamicComponentCache.get(resolved)
941
+ if (cachedNow?.nameToInfo[binding.identifier.name]) {
942
+ dynamicComponent = cachedNow.nameToInfo[binding.identifier.name]
943
+ }
944
+ }
945
+ } catch (err) {
946
+ if (shouldPrintDebug) {
947
+ logger.info(` - Failed to dynamically load ${resolved}: ${err}`)
948
+ }
949
+ } finally {
950
+ dynamicLoadingInProgress.delete(resolved)
951
+ }
952
+ }
953
+ }
954
+ }
955
+
956
+ if (!dynamicComponent) {
957
+ if (shouldPrintDebug) {
958
+ logger.info(
959
+ ` - Binding in component ${componentName} not valid import: "${binding.identifier.name}" isn't in ${moduleName}\n`
960
+ )
961
+ }
962
+ return
737
963
  }
738
- return
739
964
  }
740
965
  }
741
966
  }
742
967
 
743
- const component = getValidComponent(propsWithFileInfo, moduleName, node.name.name)
968
+ const component =
969
+ dynamicComponent ||
970
+ getValidComponent(propsWithFileInfo, moduleName, node.name.name)
744
971
  if (!component || !component.staticConfig) {
745
972
  if (shouldPrintDebug) {
746
973
  logger.info(`\n - No Tamagui conf for: ${node.name.name}\n`)
@@ -832,13 +1059,6 @@ export function createExtractor(
832
1059
  const isTextView = staticConfig.isText || false
833
1060
  const validStyles = staticConfig?.validStyles ?? {}
834
1061
 
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
1062
  // find render="a" render="main" etc dom indicators
843
1063
  let tagName = defaultProps.render ?? (isTextView ? 'span' : 'div')
844
1064
  traversePath
@@ -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
  }
@@ -22,13 +22,7 @@ const importStyleSheet = template(`
22
22
  const __ReactNativeStyleSheet = require('react-native').StyleSheet;
23
23
  `)
24
24
 
25
- const importWithStyle = template(`
26
- const __withStableStyle = require('@tamagui/core')._withStableStyle;
27
- `)
28
-
29
- const importReactUseMemo = template(`
30
- const __ReactUseMemo = require('react').useMemo;
31
- `)
25
+ const importWithStyle = template.ast(`import { _withStableStyle } from '@tamagui/core';`)
32
26
 
33
27
  const extractor = createExtractor({ platform: 'native' })
34
28
 
@@ -161,6 +155,33 @@ export function getBabelParseDefinition(options: TamaguiOptions) {
161
155
  'cursor',
162
156
  'contain',
163
157
  ]),
158
+ // native props that should pass through without preventing extraction
159
+ inlineProps: new Set([
160
+ 'testID',
161
+ 'nativeID',
162
+ 'accessibilityLabel',
163
+ 'accessibilityHint',
164
+ 'accessibilityRole',
165
+ 'accessibilityState',
166
+ 'accessibilityValue',
167
+ 'accessibilityActions',
168
+ 'accessibilityLabelledBy',
169
+ 'accessibilityLiveRegion',
170
+ 'accessibilityElementsHidden',
171
+ 'accessibilityViewIsModal',
172
+ 'importantForAccessibility',
173
+ 'onAccessibilityAction',
174
+ 'onAccessibilityEscape',
175
+ 'onAccessibilityTap',
176
+ 'onMagicTap',
177
+ 'collapsable',
178
+ 'needsOffscreenAlphaCompositing',
179
+ 'removeClippedSubviews',
180
+ 'renderToHardwareTextureAndroid',
181
+ 'shouldRasterizeIOS',
182
+ 'hitSlop',
183
+ 'pointerEvents',
184
+ ]),
164
185
  shouldPrintDebug,
165
186
  ...finalOptions,
166
187
  // disable extracting variables as no native concept of them (only theme values)
@@ -306,8 +327,7 @@ export function getBabelParseDefinition(options: TamaguiOptions) {
306
327
  hasDynamicStyle
307
328
  ) {
308
329
  if (!hasImportedViewWrapper) {
309
- root.unshiftContainer('body', importWithStyle())
310
- root.unshiftContainer('body', importReactUseMemo())
330
+ root.unshiftContainer('body', importWithStyle)
311
331
  hasImportedViewWrapper = true
312
332
  }
313
333
 
@@ -323,25 +343,12 @@ export function getBabelParseDefinition(options: TamaguiOptions) {
323
343
  t.variableDeclaration('const', [
324
344
  t.variableDeclarator(
325
345
  WrapperIdentifier,
326
- t.callExpression(t.identifier('__withStableStyle'), [
346
+ t.callExpression(t.identifier('_withStableStyle'), [
327
347
  t.identifier(name),
328
348
  t.arrowFunctionExpression(
329
349
  [t.identifier('theme'), t.identifier('_expressions')],
330
- t.blockStatement([
331
- t.returnStatement(
332
- t.callExpression(t.identifier('__ReactUseMemo'), [
333
- t.arrowFunctionExpression(
334
- [],
335
- t.blockStatement([
336
- t.returnStatement(
337
- t.arrayExpression([...hocStylesExpr.elements])
338
- ),
339
- ])
340
- ),
341
- t.identifier('_expressions'),
342
- ])
343
- ),
344
- ])
350
+ // return styles directly - no useMemo, theme changes must trigger style recalc
351
+ t.arrayExpression([...hocStylesExpr.elements])
345
352
  ),
346
353
  ])
347
354
  ),
@@ -15,7 +15,7 @@ import {
15
15
  getBundledConfig,
16
16
  getLoadedConfig,
17
17
  hasBundledConfigChanged,
18
- loadComponents,
18
+ loadComponentsSync,
19
19
  writeTamaguiCSS,
20
20
  } from './bundleConfig'
21
21
  import { getTamaguiConfigPathFromOptionsConfig } from './getTamaguiConfigPathFromOptionsConfig'
@@ -288,7 +288,7 @@ export function loadTamaguiSync({
288
288
  }
289
289
 
290
290
  // components
291
- const components = loadComponents(props, forceExports)
291
+ const components = loadComponentsSync(props, forceExports)
292
292
  if (!components) {
293
293
  throw new Error(`No components loaded`)
294
294
  }
@@ -7,8 +7,13 @@ export function getPragmaOptions({ source, path }: { source: string; path: strin
7
7
 
8
8
  let pragma = ''
9
9
  for (const line of firstLines.split('\n')) {
10
+ const trimmed = line.trim()
11
+ // only look at leading comments/empty lines, stop at first real code
12
+ if (trimmed && !trimmed.startsWith('//') && !trimmed.startsWith('/*')) {
13
+ break
14
+ }
10
15
  pragma =
11
- line
16
+ trimmed
12
17
  .match(/(\/\/|\/\*)\s?!?\s?(tamagui-ignore|debug|debug-verbose)(\n|\s|$).*/)?.[2]
13
18
  .trim() || ''
14
19
  if (pragma) {