@tamagui/static 1.0.1-beta.21 → 1.0.1-beta.210

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 (260) hide show
  1. package/dist/{cjs/constants.js → constants.js} +9 -4
  2. package/dist/constants.js.map +7 -0
  3. package/dist/{cjs/extractor → extractor}/accessSafe.js +14 -4
  4. package/dist/{esm/extractor → extractor}/accessSafe.js.map +2 -2
  5. package/dist/{cjs/extractor → extractor}/babelParse.js +20 -17
  6. package/dist/extractor/babelParse.js.map +7 -0
  7. package/dist/{cjs/extractor → extractor}/buildClassName.js +40 -11
  8. package/dist/extractor/buildClassName.js.map +7 -0
  9. package/dist/extractor/bundle.js +117 -0
  10. package/dist/extractor/bundle.js.map +7 -0
  11. package/dist/{cjs/extractor → extractor}/createEvaluator.js +19 -14
  12. package/dist/extractor/createEvaluator.js.map +7 -0
  13. package/dist/extractor/createExtractor.js +1645 -0
  14. package/dist/extractor/createExtractor.js.map +7 -0
  15. package/dist/{cjs/extractor → extractor}/ensureImportingConcat.js +18 -8
  16. package/dist/extractor/ensureImportingConcat.js.map +7 -0
  17. package/dist/{cjs/extractor → extractor}/evaluateAstNode.js +14 -6
  18. package/dist/{esm/extractor → extractor}/evaluateAstNode.js.map +2 -2
  19. package/dist/{cjs/extractor → extractor}/extractHelpers.js +91 -27
  20. package/dist/extractor/extractHelpers.js.map +7 -0
  21. package/dist/{cjs/extractor → extractor}/extractMediaStyle.js +49 -50
  22. package/dist/extractor/extractMediaStyle.js.map +7 -0
  23. package/dist/{cjs/extractor → extractor}/extractToClassNames.js +165 -109
  24. package/dist/extractor/extractToClassNames.js.map +7 -0
  25. package/dist/{cjs/extractor → extractor}/findTopmostFunction.js +2 -3
  26. package/dist/{esm/extractor → extractor}/findTopmostFunction.js.map +2 -2
  27. package/dist/{cjs/extractor → extractor}/generatedUid.js +9 -4
  28. package/dist/{cjs/extractor → extractor}/generatedUid.js.map +3 -3
  29. package/dist/{cjs/extractor → extractor}/getPrefixLogs.js +1 -2
  30. package/dist/extractor/getPrefixLogs.js.map +7 -0
  31. package/dist/{cjs/extractor → extractor}/getPropValueFromAttributes.js +24 -15
  32. package/dist/extractor/getPropValueFromAttributes.js.map +7 -0
  33. package/dist/{cjs/extractor → extractor}/getSourceModule.js +5 -3
  34. package/dist/{esm/extractor → extractor}/getSourceModule.js.map +2 -2
  35. package/dist/{cjs/extractor → extractor}/getStaticBindingsForScope.js +88 -38
  36. package/dist/extractor/getStaticBindingsForScope.js.map +7 -0
  37. package/dist/{cjs/extractor → extractor}/hoistClassNames.js +5 -4
  38. package/dist/{esm/extractor → extractor}/hoistClassNames.js.map +3 -3
  39. package/dist/{cjs/extractor → extractor}/literalToAst.js +26 -34
  40. package/dist/{cjs/extractor → extractor}/literalToAst.js.map +2 -2
  41. package/dist/extractor/loadFile.js +21 -0
  42. package/dist/extractor/loadFile.js.map +7 -0
  43. package/dist/extractor/loadTamagui.js +421 -0
  44. package/dist/extractor/loadTamagui.js.map +7 -0
  45. package/dist/{cjs/extractor → extractor}/logLines.js +3 -3
  46. package/dist/{cjs/extractor → extractor}/logLines.js.map +2 -2
  47. package/dist/{cjs/extractor → extractor}/normalizeTernaries.js +13 -36
  48. package/dist/extractor/normalizeTernaries.js.map +7 -0
  49. package/dist/{cjs/extractor → extractor}/removeUnusedHooks.js +7 -5
  50. package/dist/{cjs/extractor → extractor}/removeUnusedHooks.js.map +2 -2
  51. package/dist/{cjs/extractor → extractor}/timer.js +11 -4
  52. package/dist/extractor/timer.js.map +7 -0
  53. package/dist/{cjs/extractor → extractor}/validHTMLAttributes.js +52 -0
  54. package/dist/extractor/validHTMLAttributes.js.map +7 -0
  55. package/dist/getPragmaOptions.js +68 -0
  56. package/dist/getPragmaOptions.js.map +7 -0
  57. package/dist/helpers/memoize.js +46 -0
  58. package/dist/helpers/memoize.js.map +7 -0
  59. package/dist/{cjs/index.js → index.js} +10 -7
  60. package/dist/index.js.map +7 -0
  61. package/dist/require.js +90 -0
  62. package/dist/require.js.map +7 -0
  63. package/dist/tamagui-node/tamagui.config.js +6781 -0
  64. package/dist/{jsx/types.js → tamagui-node/tamaguicore-components.config.js} +0 -0
  65. package/dist/tamagui-node/tamaguitestdesignsystem-components.config.js +31 -0
  66. package/dist/{cjs/types.js → types.js} +1 -0
  67. package/dist/types.js.map +7 -0
  68. package/dist/webpackPlugin.js +35 -0
  69. package/dist/webpackPlugin.js.map +7 -0
  70. package/package.json +47 -35
  71. package/src/constants.ts +14 -0
  72. package/src/extractor/accessSafe.ts +18 -0
  73. package/src/extractor/babelParse.ts +28 -0
  74. package/src/extractor/buildClassName.ts +77 -0
  75. package/src/extractor/bundle.ts +110 -0
  76. package/src/extractor/createEvaluator.ts +73 -0
  77. package/src/extractor/createExtractor.ts +2133 -0
  78. package/src/extractor/ensureImportingConcat.ts +32 -0
  79. package/src/extractor/evaluateAstNode.ts +121 -0
  80. package/src/extractor/extractHelpers.ts +201 -0
  81. package/src/extractor/extractMediaStyle.ts +199 -0
  82. package/src/extractor/extractToClassNames.ts +450 -0
  83. package/src/extractor/findTopmostFunction.ts +22 -0
  84. package/src/extractor/generatedUid.ts +43 -0
  85. package/src/extractor/getPrefixLogs.ts +6 -0
  86. package/src/extractor/getPropValueFromAttributes.ts +92 -0
  87. package/src/extractor/getSourceModule.ts +101 -0
  88. package/src/extractor/getStaticBindingsForScope.ts +227 -0
  89. package/src/extractor/hoistClassNames.ts +45 -0
  90. package/src/extractor/literalToAst.ts +84 -0
  91. package/src/extractor/loadFile.ts +17 -0
  92. package/src/extractor/loadTamagui.ts +478 -0
  93. package/src/extractor/logLines.ts +16 -0
  94. package/src/extractor/normalizeTernaries.ts +63 -0
  95. package/src/extractor/removeUnusedHooks.ts +76 -0
  96. package/src/extractor/timer.ts +29 -0
  97. package/src/extractor/validHTMLAttributes.ts +99 -0
  98. package/src/getPragmaOptions.ts +53 -0
  99. package/src/helpers/memoize.ts +21 -0
  100. package/src/index.ts +11 -0
  101. package/src/require.ts +107 -0
  102. package/src/types.ts +98 -0
  103. package/src/webpackPlugin.ts +9 -0
  104. package/tamagui.tsconfig.json +5 -0
  105. package/types/constants.d.ts +1 -1
  106. package/types/extractor/babelParse.d.ts +2 -1
  107. package/types/extractor/buildClassName.d.ts +5 -2
  108. package/types/extractor/bundle.d.ts +13 -0
  109. package/types/extractor/createEvaluator.d.ts +4 -4
  110. package/types/extractor/createExtractor.d.ts +26 -11
  111. package/types/extractor/extractHelpers.d.ts +16 -4
  112. package/types/extractor/extractMediaStyle.d.ts +3 -3
  113. package/types/extractor/extractToClassNames.d.ts +5 -9
  114. package/types/extractor/getPrefixLogs.d.ts +1 -1
  115. package/types/extractor/getPropValueFromAttributes.d.ts +16 -0
  116. package/types/extractor/getStaticBindingsForScope.d.ts +2 -1
  117. package/types/extractor/loadFile.d.ts +1 -0
  118. package/types/extractor/loadTamagui.d.ts +22 -6
  119. package/types/extractor/normalizeTernaries.d.ts +1 -1
  120. package/types/extractor/validHTMLAttributes.d.ts +49 -0
  121. package/types/getPragmaOptions.d.ts +9 -0
  122. package/types/helpers/memoize.d.ts +8 -0
  123. package/types/index.d.ts +9 -7
  124. package/types/require.d.ts +4 -0
  125. package/types/types.d.ts +32 -32
  126. package/types/webpackPlugin.d.ts +4 -0
  127. package/dist/cjs/constants.js.map +0 -7
  128. package/dist/cjs/extractor/accessSafe.js.map +0 -7
  129. package/dist/cjs/extractor/babelParse.js.map +0 -7
  130. package/dist/cjs/extractor/buildClassName.js.map +0 -7
  131. package/dist/cjs/extractor/createEvaluator.js.map +0 -7
  132. package/dist/cjs/extractor/createExtractor.js +0 -1221
  133. package/dist/cjs/extractor/createExtractor.js.map +0 -7
  134. package/dist/cjs/extractor/ensureImportingConcat.js.map +0 -7
  135. package/dist/cjs/extractor/evaluateAstNode.js.map +0 -7
  136. package/dist/cjs/extractor/extractHelpers.js.map +0 -7
  137. package/dist/cjs/extractor/extractMediaStyle.js.map +0 -7
  138. package/dist/cjs/extractor/extractToClassNames.js.map +0 -7
  139. package/dist/cjs/extractor/findTopmostFunction.js.map +0 -7
  140. package/dist/cjs/extractor/getPrefixLogs.js.map +0 -7
  141. package/dist/cjs/extractor/getPropValueFromAttributes.js.map +0 -7
  142. package/dist/cjs/extractor/getSourceModule.js.map +0 -7
  143. package/dist/cjs/extractor/getStaticBindingsForScope.js.map +0 -7
  144. package/dist/cjs/extractor/hoistClassNames.js.map +0 -7
  145. package/dist/cjs/extractor/loadTamagui.js +0 -106
  146. package/dist/cjs/extractor/loadTamagui.js.map +0 -7
  147. package/dist/cjs/extractor/normalizeTernaries.js.map +0 -7
  148. package/dist/cjs/extractor/timer.js.map +0 -7
  149. package/dist/cjs/extractor/validHTMLAttributes.js.map +0 -7
  150. package/dist/cjs/index.js.map +0 -7
  151. package/dist/cjs/patchReactNativeWeb.js +0 -167
  152. package/dist/cjs/patchReactNativeWeb.js.map +0 -7
  153. package/dist/cjs/types.js.map +0 -7
  154. package/dist/esm/constants.js +0 -14
  155. package/dist/esm/constants.js.map +0 -7
  156. package/dist/esm/extractor/accessSafe.js +0 -11
  157. package/dist/esm/extractor/babelParse.js +0 -30
  158. package/dist/esm/extractor/babelParse.js.map +0 -7
  159. package/dist/esm/extractor/buildClassName.js +0 -40
  160. package/dist/esm/extractor/buildClassName.js.map +0 -7
  161. package/dist/esm/extractor/createEvaluator.js +0 -52
  162. package/dist/esm/extractor/createEvaluator.js.map +0 -7
  163. package/dist/esm/extractor/createExtractor.js +0 -1169
  164. package/dist/esm/extractor/createExtractor.js.map +0 -7
  165. package/dist/esm/extractor/ensureImportingConcat.js +0 -24
  166. package/dist/esm/extractor/ensureImportingConcat.js.map +0 -7
  167. package/dist/esm/extractor/evaluateAstNode.js +0 -94
  168. package/dist/esm/extractor/extractHelpers.js +0 -103
  169. package/dist/esm/extractor/extractHelpers.js.map +0 -7
  170. package/dist/esm/extractor/extractMediaStyle.js +0 -152
  171. package/dist/esm/extractor/extractMediaStyle.js.map +0 -7
  172. package/dist/esm/extractor/extractToClassNames.js +0 -293
  173. package/dist/esm/extractor/extractToClassNames.js.map +0 -7
  174. package/dist/esm/extractor/findTopmostFunction.js +0 -23
  175. package/dist/esm/extractor/generatedUid.js +0 -28
  176. package/dist/esm/extractor/generatedUid.js.map +0 -7
  177. package/dist/esm/extractor/getPrefixLogs.js +0 -12
  178. package/dist/esm/extractor/getPrefixLogs.js.map +0 -7
  179. package/dist/esm/extractor/getPropValueFromAttributes.js +0 -49
  180. package/dist/esm/extractor/getPropValueFromAttributes.js.map +0 -7
  181. package/dist/esm/extractor/getSourceModule.js +0 -69
  182. package/dist/esm/extractor/getStaticBindingsForScope.js +0 -137
  183. package/dist/esm/extractor/getStaticBindingsForScope.js.map +0 -7
  184. package/dist/esm/extractor/hoistClassNames.js +0 -44
  185. package/dist/esm/extractor/literalToAst.js +0 -80
  186. package/dist/esm/extractor/literalToAst.js.map +0 -7
  187. package/dist/esm/extractor/loadTamagui.js +0 -84
  188. package/dist/esm/extractor/loadTamagui.js.map +0 -7
  189. package/dist/esm/extractor/logLines.js +0 -22
  190. package/dist/esm/extractor/logLines.js.map +0 -7
  191. package/dist/esm/extractor/normalizeTernaries.js +0 -53
  192. package/dist/esm/extractor/normalizeTernaries.js.map +0 -7
  193. package/dist/esm/extractor/removeUnusedHooks.js +0 -80
  194. package/dist/esm/extractor/removeUnusedHooks.js.map +0 -7
  195. package/dist/esm/extractor/timer.js +0 -24
  196. package/dist/esm/extractor/timer.js.map +0 -7
  197. package/dist/esm/extractor/validHTMLAttributes.js +0 -52
  198. package/dist/esm/extractor/validHTMLAttributes.js.map +0 -7
  199. package/dist/esm/index.js +0 -14
  200. package/dist/esm/index.js.map +0 -7
  201. package/dist/esm/patchReactNativeWeb.js +0 -142
  202. package/dist/esm/patchReactNativeWeb.js.map +0 -7
  203. package/dist/esm/types.js +0 -1
  204. package/dist/esm/types.js.map +0 -7
  205. package/dist/jsx/constants.js +0 -13
  206. package/dist/jsx/extractor/accessSafe.js +0 -10
  207. package/dist/jsx/extractor/babelParse.js +0 -29
  208. package/dist/jsx/extractor/buildClassName.js +0 -39
  209. package/dist/jsx/extractor/createEvaluator.js +0 -51
  210. package/dist/jsx/extractor/createExtractor.js +0 -1168
  211. package/dist/jsx/extractor/ensureImportingConcat.js +0 -23
  212. package/dist/jsx/extractor/evaluateAstNode.js +0 -93
  213. package/dist/jsx/extractor/extractHelpers.js +0 -102
  214. package/dist/jsx/extractor/extractMediaStyle.js +0 -151
  215. package/dist/jsx/extractor/extractToClassNames.js +0 -292
  216. package/dist/jsx/extractor/findTopmostFunction.js +0 -22
  217. package/dist/jsx/extractor/generatedUid.js +0 -27
  218. package/dist/jsx/extractor/getPrefixLogs.js +0 -11
  219. package/dist/jsx/extractor/getPropValueFromAttributes.js +0 -48
  220. package/dist/jsx/extractor/getSourceModule.js +0 -68
  221. package/dist/jsx/extractor/getStaticBindingsForScope.js +0 -136
  222. package/dist/jsx/extractor/hoistClassNames.js +0 -43
  223. package/dist/jsx/extractor/literalToAst.js +0 -79
  224. package/dist/jsx/extractor/loadTamagui.js +0 -83
  225. package/dist/jsx/extractor/logLines.js +0 -21
  226. package/dist/jsx/extractor/normalizeTernaries.js +0 -52
  227. package/dist/jsx/extractor/removeUnusedHooks.js +0 -79
  228. package/dist/jsx/extractor/timer.js +0 -23
  229. package/dist/jsx/extractor/validHTMLAttributes.js +0 -51
  230. package/dist/jsx/index.js +0 -13
  231. package/dist/jsx/patchReactNativeWeb.js +0 -141
  232. package/types/constants.d.ts.map +0 -1
  233. package/types/extractor/accessSafe.d.ts.map +0 -1
  234. package/types/extractor/babelParse.d.ts.map +0 -1
  235. package/types/extractor/buildClassName.d.ts.map +0 -1
  236. package/types/extractor/createEvaluator.d.ts.map +0 -1
  237. package/types/extractor/createExtractor.d.ts.map +0 -1
  238. package/types/extractor/ensureImportingConcat.d.ts.map +0 -1
  239. package/types/extractor/evaluateAstNode.d.ts.map +0 -1
  240. package/types/extractor/extractHelpers.d.ts.map +0 -1
  241. package/types/extractor/extractMediaStyle.d.ts.map +0 -1
  242. package/types/extractor/extractToClassNames.d.ts.map +0 -1
  243. package/types/extractor/findTopmostFunction.d.ts.map +0 -1
  244. package/types/extractor/generatedUid.d.ts.map +0 -1
  245. package/types/extractor/getPrefixLogs.d.ts.map +0 -1
  246. package/types/extractor/getPropValueFromAttributes.d.ts.map +0 -1
  247. package/types/extractor/getSourceModule.d.ts.map +0 -1
  248. package/types/extractor/getStaticBindingsForScope.d.ts.map +0 -1
  249. package/types/extractor/hoistClassNames.d.ts.map +0 -1
  250. package/types/extractor/literalToAst.d.ts.map +0 -1
  251. package/types/extractor/loadTamagui.d.ts.map +0 -1
  252. package/types/extractor/logLines.d.ts.map +0 -1
  253. package/types/extractor/normalizeTernaries.d.ts.map +0 -1
  254. package/types/extractor/removeUnusedHooks.d.ts.map +0 -1
  255. package/types/extractor/timer.d.ts.map +0 -1
  256. package/types/extractor/validHTMLAttributes.d.ts.map +0 -1
  257. package/types/index.d.ts.map +0 -1
  258. package/types/patchReactNativeWeb.d.ts +0 -2
  259. package/types/patchReactNativeWeb.d.ts.map +0 -1
  260. package/types/types.d.ts.map +0 -1
@@ -0,0 +1,2133 @@
1
+ /* eslint-disable no-console */
2
+ import { basename, relative } from 'path'
3
+
4
+ import traverse, { NodePath, TraverseOptions } from '@babel/traverse'
5
+ import * as t from '@babel/types'
6
+ import {
7
+ PseudoStyles,
8
+ StaticConfigParsed,
9
+ expandStyles,
10
+ getSplitStyles,
11
+ mediaQueryConfig,
12
+ proxyThemeVariables,
13
+ pseudoDescriptors,
14
+ } from '@tamagui/core-node'
15
+ import type { ViewStyle } from 'react-native'
16
+ import { createDOMProps } from 'react-native-web-internals'
17
+
18
+ import { FAILED_EVAL } from '../constants.js'
19
+ import type {
20
+ ExtractedAttr,
21
+ ExtractedAttrAttr,
22
+ ExtractedAttrStyle,
23
+ ExtractorOptions,
24
+ ExtractorParseProps,
25
+ TamaguiOptions,
26
+ TamaguiOptionsWithFileInfo,
27
+ Ternary,
28
+ } from '../types.js'
29
+ import { createEvaluator, createSafeEvaluator } from './createEvaluator.js'
30
+ import { evaluateAstNode } from './evaluateAstNode.js'
31
+ import {
32
+ attrStr,
33
+ findComponentName,
34
+ getValidComponent,
35
+ getValidImport,
36
+ isPresent,
37
+ isValidImport,
38
+ objToStr,
39
+ } from './extractHelpers.js'
40
+ import { findTopmostFunction } from './findTopmostFunction.js'
41
+ import { getPrefixLogs } from './getPrefixLogs.js'
42
+ import { cleanupBeforeExit, getStaticBindingsForScope } from './getStaticBindingsForScope.js'
43
+ import { literalToAst } from './literalToAst.js'
44
+ import { TamaguiProjectInfo, loadTamagui, loadTamaguiSync } from './loadTamagui.js'
45
+ import { logLines } from './logLines.js'
46
+ import { normalizeTernaries } from './normalizeTernaries.js'
47
+ import { removeUnusedHooks } from './removeUnusedHooks.js'
48
+ import { timer } from './timer.js'
49
+ import { validHTMLAttributes } from './validHTMLAttributes.js'
50
+
51
+ const UNTOUCHED_PROPS = {
52
+ key: true,
53
+ style: true,
54
+ className: true,
55
+ }
56
+
57
+ const INLINE_EXTRACTABLE = {
58
+ ref: 'ref',
59
+ key: 'key',
60
+ ...(process.env.TAMAGUI_TARGET === 'web' && {
61
+ onPress: 'onClick',
62
+ onHoverIn: 'onMouseEnter',
63
+ onHoverOut: 'onMouseLeave',
64
+ onPressIn: 'onMouseDown',
65
+ onPressOut: 'onMouseUp',
66
+ }),
67
+ }
68
+
69
+ const validHooks = {
70
+ useMedia: true,
71
+ useTheme: true,
72
+ }
73
+
74
+ const isAttr = (x: ExtractedAttr): x is ExtractedAttrAttr => x.type === 'attr'
75
+ const createTernary = (x: Ternary) => x
76
+
77
+ export type Extractor = ReturnType<typeof createExtractor>
78
+
79
+ type FileOrPath = NodePath<t.Program> | t.File
80
+
81
+ let hasLoggedBaseInfo = false
82
+
83
+ export function createExtractor({ logger = console }: ExtractorOptions = { logger: console }) {
84
+ if (!process.env.TAMAGUI_TARGET) {
85
+ console.log('⚠️ Please set process.env.TAMAGUI_TARGET to either "web" or "native"')
86
+ process.exit(1)
87
+ }
88
+
89
+ const shouldAddDebugProp =
90
+ // really basic disable this for next.js because it messes with ssr
91
+ !process.env.npm_package_dependencies_next &&
92
+ process.env.TAMAGUI_TARGET !== 'native' &&
93
+ process.env.IDENTIFY_TAGS !== 'false' &&
94
+ (process.env.NODE_ENV === 'development' || process.env.DEBUG || process.env.IDENTIFY_TAGS)
95
+
96
+ let projectInfo: TamaguiProjectInfo | null = null
97
+
98
+ // we load tamagui delayed because we need to set some global/env stuff before importing
99
+ // otherwise we'd import `rnw` and cause it to evaluate react-native-web which causes errors
100
+
101
+ function loadSync(props: TamaguiOptions) {
102
+ return (projectInfo ||= loadTamaguiSync({
103
+ config: props.config || 'tamagui.config.ts',
104
+ components: props.components || ['tamagui'],
105
+ }))
106
+ }
107
+
108
+ async function load(props: TamaguiOptions) {
109
+ return (projectInfo ||= await loadTamagui({
110
+ config: props.config || 'tamagui.config.ts',
111
+ components: props.components || ['tamagui'],
112
+ }))
113
+ }
114
+
115
+ return {
116
+ options: {
117
+ logger,
118
+ },
119
+ cleanupBeforeExit,
120
+ loadTamagui: load,
121
+ loadTamaguiSync: loadSync,
122
+ getTamagui() {
123
+ return projectInfo?.tamaguiConfig
124
+ },
125
+ parseSync: (f: FileOrPath, props: ExtractorParseProps) => {
126
+ const projectInfo = loadSync(props)
127
+ return parseWithConfig(projectInfo, f, props)
128
+ },
129
+ parse: async (f: FileOrPath, props: ExtractorParseProps) => {
130
+ const projectInfo = await load(props)
131
+ return parseWithConfig(projectInfo, f, props)
132
+ },
133
+ }
134
+
135
+ function isValidStyleKey(name: string, staticConfig: StaticConfigParsed) {
136
+ if (!projectInfo) {
137
+ throw new Error(`Tamagui extractor not loaded yet`)
138
+ }
139
+ return !!(
140
+ !!staticConfig.validStyles?.[name] ||
141
+ !!pseudoDescriptors[name] ||
142
+ // dont disable variants or else you lose many things flattening
143
+ staticConfig.variants?.[name] ||
144
+ projectInfo?.tamaguiConfig.shorthands[name] ||
145
+ (name[0] === '$' ? !!mediaQueryConfig[name.slice(1)] : false)
146
+ )
147
+ }
148
+
149
+ function parseWithConfig(
150
+ { components, tamaguiConfig }: TamaguiProjectInfo,
151
+ fileOrPath: FileOrPath,
152
+ options: ExtractorParseProps
153
+ ) {
154
+ const {
155
+ config = 'tamagui.config.ts',
156
+ importsWhitelist = ['constants.js'],
157
+ evaluateVars = true,
158
+ sourcePath = '',
159
+ onExtractTag,
160
+ onStyleRule,
161
+ getFlattenedNode,
162
+ disable,
163
+ disableExtraction,
164
+ disableExtractInlineMedia,
165
+ disableExtractVariables,
166
+ disableDebugAttr,
167
+ disableExtractFoundComponents,
168
+ includeExtensions = ['.tsx', '.jsx'],
169
+ extractStyledDefinitions = false,
170
+ prefixLogs,
171
+ excludeProps,
172
+ target,
173
+ ...restProps
174
+ } = options
175
+
176
+ let shouldPrintDebug = options.shouldPrintDebug || false
177
+
178
+ if (disable === true || (Array.isArray(disable) && disable.includes(sourcePath))) {
179
+ return null
180
+ }
181
+ if (sourcePath === '') {
182
+ throw new Error(`Must provide a source file name`)
183
+ }
184
+ if (!components) {
185
+ throw new Error(`Must provide components`)
186
+ }
187
+ if (includeExtensions && !includeExtensions.some((ext) => sourcePath.endsWith(ext))) {
188
+ if (shouldPrintDebug) {
189
+ logger.info(
190
+ `Ignoring file due to includeExtensions: ${sourcePath}, includeExtensions: ${includeExtensions.join(
191
+ ', '
192
+ )}`
193
+ )
194
+ }
195
+ return null
196
+ }
197
+
198
+ /**
199
+ * Step 1: Determine if importing any statically extractable components
200
+ */
201
+
202
+ const isTargetingHTML = target === 'html'
203
+ const ogDebug = shouldPrintDebug
204
+ const tm = timer()
205
+ const propsWithFileInfo: TamaguiOptionsWithFileInfo = {
206
+ ...options,
207
+ sourcePath,
208
+ allLoadedComponents: [...components],
209
+ }
210
+
211
+ if (!hasLoggedBaseInfo) {
212
+ hasLoggedBaseInfo = true
213
+ if (shouldPrintDebug) {
214
+ logger.info(
215
+ [
216
+ 'loaded components:',
217
+ propsWithFileInfo.allLoadedComponents
218
+ .map((comp) => Object.keys(comp.nameToInfo).join(', '))
219
+ .join(', '),
220
+ ].join(' ')
221
+ )
222
+ }
223
+ if (process.env.DEBUG?.startsWith('tamagui')) {
224
+ const next = [...propsWithFileInfo.allLoadedComponents].map((info) => {
225
+ const nameToInfo = { ...info.nameToInfo }
226
+ for (const key in nameToInfo) {
227
+ delete nameToInfo[key].staticConfig.validStyles
228
+ }
229
+ return { ...info, nameToInfo }
230
+ })
231
+ logger.info(['loaded:', JSON.stringify(next, null, 2)].join('\n'))
232
+ }
233
+ }
234
+
235
+ tm.mark('load-tamagui', !!shouldPrintDebug)
236
+
237
+ const firstThemeName = Object.keys(tamaguiConfig.themes)[0]
238
+ const firstTheme = tamaguiConfig.themes[firstThemeName]
239
+
240
+ if (!firstTheme || typeof firstTheme !== 'object') {
241
+ console.error(`Missing theme, an error occurred when importing your config`)
242
+ console.log(`Got config:`, tamaguiConfig)
243
+ console.log(`Looking for theme:`, firstThemeName)
244
+ process.exit(0)
245
+ }
246
+
247
+ const proxiedTheme = proxyThemeVariables(firstTheme)
248
+ type AccessListener = (key: string) => void
249
+ const themeAccessListeners = new Set<AccessListener>()
250
+ const defaultTheme = new Proxy(proxiedTheme, {
251
+ get(target, key) {
252
+ if (Reflect.has(target, key)) {
253
+ themeAccessListeners.forEach((cb) => cb(String(key)))
254
+ }
255
+ return Reflect.get(target, key)
256
+ },
257
+ })
258
+
259
+ // @ts-ignore
260
+ const body = fileOrPath.type === 'Program' ? fileOrPath.get('body') : fileOrPath.program.body
261
+
262
+ if (Object.keys(components).length === 0) {
263
+ console.warn(`Warning: Tamagui didn't find any valid components (DEBUG=tamagui for more)`)
264
+ if (process.env.DEBUG === 'tamagui') {
265
+ console.log(`components`, Object.keys(components), components)
266
+ }
267
+ }
268
+
269
+ if (shouldPrintDebug === 'verbose') {
270
+ logger.info(
271
+ `allLoadedComponent modules ${propsWithFileInfo.allLoadedComponents
272
+ .map((k) => k.moduleName)
273
+ .join(', ')}`
274
+ )
275
+ }
276
+
277
+ let doesUseValidImport = false
278
+ let hasImportedTheme = false
279
+
280
+ const importDeclarations: t.ImportDeclaration[] = []
281
+
282
+ for (const bodyPath of body) {
283
+ if (bodyPath.type !== 'ImportDeclaration') continue
284
+ const node = ('node' in bodyPath ? bodyPath.node : bodyPath) as t.ImportDeclaration
285
+ const moduleName = node.source.value
286
+
287
+ // if importing valid module
288
+ const valid = isValidImport(propsWithFileInfo, moduleName)
289
+
290
+ if (valid) {
291
+ importDeclarations.push(node)
292
+ }
293
+
294
+ if (extractStyledDefinitions) {
295
+ if (valid) {
296
+ if (node.specifiers.some((specifier) => specifier.local.name === 'styled')) {
297
+ doesUseValidImport = true
298
+ break
299
+ }
300
+ }
301
+ }
302
+
303
+ if (valid) {
304
+ const names = node.specifiers.map((specifier) => specifier.local.name)
305
+ const isValidComponent = names.some((name) =>
306
+ Boolean(isValidImport(propsWithFileInfo, moduleName, name) || validHooks[name])
307
+ )
308
+ if (shouldPrintDebug === 'verbose') {
309
+ logger.info(
310
+ `import ${names.join(', ')} from ${moduleName} isValidComponent ${isValidComponent}`
311
+ )
312
+ }
313
+ if (isValidComponent) {
314
+ doesUseValidImport = true
315
+ break
316
+ }
317
+ }
318
+ }
319
+
320
+ if (shouldPrintDebug) {
321
+ logger.info(`file: ${sourcePath} ${JSON.stringify({ doesUseValidImport, hasImportedTheme })}`)
322
+ }
323
+
324
+ if (!doesUseValidImport) {
325
+ return null
326
+ }
327
+
328
+ function getValidImportedComponent(componentName: string) {
329
+ const importDeclaration = importDeclarations.find((dec) =>
330
+ dec.specifiers.some((spec) => spec.local.name === componentName)
331
+ )
332
+ if (!importDeclaration) {
333
+ return null
334
+ }
335
+ return getValidImport(propsWithFileInfo, importDeclaration.source.value, componentName)
336
+ }
337
+
338
+ tm.mark('import-check', !!shouldPrintDebug)
339
+
340
+ let couldntParse = false
341
+ const modifiedComponents = new Set<NodePath<any>>()
342
+
343
+ // only keeping a cache around per-file, reset it if it changes
344
+ const bindingCache: Record<string, string | null> = {}
345
+
346
+ const callTraverse = (a: TraverseOptions<any>) => {
347
+ return fileOrPath.type === 'File' ? traverse(fileOrPath, a) : fileOrPath.traverse(a)
348
+ }
349
+
350
+ const shouldDisableExtraction =
351
+ disableExtraction === true ||
352
+ (Array.isArray(disableExtraction) && disableExtraction.includes(sourcePath))
353
+
354
+ /**
355
+ * Step 2: Statically extract from JSX < /> nodes
356
+ */
357
+ let programPath: NodePath<t.Program> | null = null
358
+
359
+ const res = {
360
+ styled: 0,
361
+ flattened: 0,
362
+ optimized: 0,
363
+ modified: 0,
364
+ found: 0,
365
+ }
366
+
367
+ callTraverse({
368
+ // @ts-ignore
369
+ Program: {
370
+ enter(path) {
371
+ programPath = path
372
+ },
373
+ },
374
+
375
+ // styled() calls
376
+ CallExpression(path) {
377
+ if (disable || shouldDisableExtraction || extractStyledDefinitions === false) {
378
+ return
379
+ }
380
+
381
+ if (!t.isIdentifier(path.node.callee) || path.node.callee.name !== 'styled') {
382
+ return
383
+ }
384
+
385
+ const name =
386
+ t.isVariableDeclarator(path.parent) && t.isIdentifier(path.parent.id)
387
+ ? path.parent.id.name
388
+ : 'unknown'
389
+
390
+ const definition = path.node.arguments[1]
391
+
392
+ if (!name || !definition || !t.isObjectExpression(definition)) {
393
+ return
394
+ }
395
+
396
+ let Component = getValidImportedComponent(name)
397
+
398
+ if (!Component) {
399
+ if (disableExtractFoundComponents === true) {
400
+ return
401
+ }
402
+ if (
403
+ Array.isArray(disableExtractFoundComponents) &&
404
+ disableExtractFoundComponents.includes(name)
405
+ ) {
406
+ return
407
+ }
408
+
409
+ try {
410
+ if (shouldPrintDebug) {
411
+ logger.info(`Unknown component ${name}, attempting dynamic load: ${sourcePath}`)
412
+ }
413
+
414
+ const out = loadTamaguiSync({
415
+ forceExports: true,
416
+ components: [sourcePath],
417
+ })
418
+
419
+ if (!out?.components) {
420
+ if (shouldPrintDebug) {
421
+ logger.info(`Couldn't load, got ${out}`)
422
+ }
423
+ return
424
+ }
425
+
426
+ propsWithFileInfo.allLoadedComponents = [
427
+ ...propsWithFileInfo.allLoadedComponents,
428
+ ...out.components,
429
+ ]
430
+
431
+ Component = out.components.flatMap((x) => x.nameToInfo[name] ?? [])[0]
432
+
433
+ if (shouldPrintDebug === 'verbose') {
434
+ logger.info([`Tamagui Loaded`, JSON.stringify(out.components), !!Component].join(' '))
435
+ }
436
+ } catch (err: any) {
437
+ if (shouldPrintDebug) {
438
+ logger.info(
439
+ `${getPrefixLogs(
440
+ options
441
+ )} skip optimize styled(${name}), unable to pre-process (DEBUG=tamagui for more)`
442
+ )
443
+ }
444
+ if (process.env.DEBUG === 'tamagui') {
445
+ logger.info(
446
+ ` Disable this with "disableExtractFoundComponents" in your build-time configuration. \n\n ${err.message} ${err.stack}`
447
+ )
448
+ }
449
+ }
450
+ }
451
+
452
+ if (!Component) {
453
+ return
454
+ }
455
+
456
+ const componentSkipProps = new Set([
457
+ ...(Component.staticConfig.inlineWhenUnflattened || []),
458
+ ...(Component.staticConfig.inlineProps || []),
459
+ ...(Component.staticConfig.deoptProps || []),
460
+ // for now skip variants, will return to them
461
+ 'variants',
462
+ 'defaultVariants',
463
+ // skip fontFamily its basically a "variant", important for theme use to be value always
464
+ 'fontFamily',
465
+ 'name',
466
+ ])
467
+
468
+ // for now dont parse variants, spreads, etc
469
+ const skipped: (t.ObjectProperty | t.SpreadElement | t.ObjectMethod)[] = []
470
+ const styles = {}
471
+
472
+ // Generate scope object at this level
473
+ const staticNamespace = getStaticBindingsForScope(
474
+ path.scope,
475
+ importsWhitelist,
476
+ sourcePath,
477
+ bindingCache,
478
+ shouldPrintDebug
479
+ )
480
+
481
+ const attemptEval = !evaluateVars
482
+ ? evaluateAstNode
483
+ : createEvaluator({
484
+ props: propsWithFileInfo,
485
+ staticNamespace,
486
+ sourcePath,
487
+ shouldPrintDebug,
488
+ })
489
+ const attemptEvalSafe = createSafeEvaluator(attemptEval)
490
+
491
+ for (const property of definition.properties) {
492
+ if (
493
+ !t.isObjectProperty(property) ||
494
+ !t.isIdentifier(property.key) ||
495
+ !isValidStyleKey(property.key.name, Component.staticConfig) ||
496
+ // skip variants
497
+ Component.staticConfig.variants?.[property.key.name] ||
498
+ componentSkipProps.has(property.key.name)
499
+ ) {
500
+ skipped.push(property)
501
+ continue
502
+ }
503
+ // attempt eval
504
+ const out = attemptEvalSafe(property.value)
505
+ if (out === FAILED_EVAL) {
506
+ skipped.push(property)
507
+ } else {
508
+ styles[property.key.name] = out
509
+ }
510
+ }
511
+
512
+ const out = getSplitStyles(styles, Component.staticConfig, defaultTheme, {
513
+ focus: false,
514
+ hover: false,
515
+ mounted: false,
516
+ press: false,
517
+ pressIn: false,
518
+ resolveVariablesAs: 'variable',
519
+ noClassNames: false,
520
+ })
521
+
522
+ const classNames = {
523
+ ...out.classNames,
524
+ }
525
+
526
+ if (shouldPrintDebug) {
527
+ // prettier-ignore
528
+ logger.info([`Extracted styled(${name})\n`, JSON.stringify(styles, null, 2), '\n rulesToInsert:', out.rulesToInsert.flatMap((rule) => rule.rules).join('\n')].join(' '))
529
+ }
530
+
531
+ // leave only un-parsed props...
532
+ definition.properties = skipped
533
+
534
+ // ... + key: className
535
+ for (const cn in classNames) {
536
+ if (componentSkipProps.has(cn)) {
537
+ continue
538
+ }
539
+ const val = classNames[cn]
540
+ definition.properties.push(t.objectProperty(t.stringLiteral(cn), t.stringLiteral(val)))
541
+ }
542
+
543
+ if (out.rulesToInsert) {
544
+ for (const { identifier, rules } of out.rulesToInsert) {
545
+ onStyleRule?.(identifier, rules)
546
+ }
547
+ }
548
+
549
+ res.styled++
550
+
551
+ if (shouldPrintDebug) {
552
+ logger.info(`Extracted styled(${name})`)
553
+ }
554
+ },
555
+
556
+ JSXElement(traversePath) {
557
+ if (shouldDisableExtraction) {
558
+ return
559
+ }
560
+
561
+ tm.mark('jsx-element', !!shouldPrintDebug)
562
+
563
+ const node = traversePath.node.openingElement
564
+ const ogAttributes = node.attributes.map((attr) => ({ ...attr }))
565
+ const componentName = findComponentName(traversePath.scope)
566
+ const closingElement = traversePath.node.closingElement
567
+
568
+ // skip non-identifier opening elements (member expressions, etc.)
569
+ if (t.isJSXMemberExpression(closingElement?.name) || !t.isJSXIdentifier(node.name)) {
570
+ return
571
+ }
572
+
573
+ // validate its a proper import from tamagui (or internally inside tamagui)
574
+ const binding = traversePath.scope.getBinding(node.name.name)
575
+ let modulePath = ''
576
+
577
+ if (binding) {
578
+ if (!t.isImportDeclaration(binding.path.parent)) {
579
+ if (shouldPrintDebug) {
580
+ logger.info(` - Binding not import declaration, skip`)
581
+ }
582
+ return
583
+ }
584
+ modulePath = binding.path.parent.source.value
585
+ if (!isValidImport(propsWithFileInfo, modulePath, binding.identifier.name)) {
586
+ if (shouldPrintDebug) {
587
+ logger.info(` - Binding not internal import or from components ${modulePath}`)
588
+ }
589
+ return
590
+ }
591
+ }
592
+
593
+ const component = getValidComponent(propsWithFileInfo, modulePath, node.name.name)
594
+ if (!component || !component.staticConfig) {
595
+ if (shouldPrintDebug) {
596
+ logger.info(` - No Tamagui conf on this: ${node.name.name}`)
597
+ }
598
+ return
599
+ }
600
+
601
+ const originalNodeName = node.name.name
602
+
603
+ // found a valid tag
604
+ res.found++
605
+
606
+ const filePath = `./${relative(process.cwd(), sourcePath)}`
607
+ const lineNumbers = node.loc
608
+ ? node.loc.start.line +
609
+ (node.loc.start.line !== node.loc.end.line ? `-${node.loc.end.line}` : '')
610
+ : ''
611
+
612
+ const codePosition = `${filePath}:${lineNumbers}`
613
+
614
+ // debug just one
615
+ const debugPropValue = node.attributes
616
+ .filter(
617
+ (n) => t.isJSXAttribute(n) && t.isJSXIdentifier(n.name) && n.name.name === 'debug'
618
+ )
619
+ // @ts-ignore
620
+ .map((n: t.JSXAttribute) => {
621
+ if (n.value === null) return true
622
+ if (t.isStringLiteral(n.value)) return n.value.value as 'verbose'
623
+ return false
624
+ })[0] as boolean | 'verbose' | undefined
625
+
626
+ if (debugPropValue) {
627
+ shouldPrintDebug = debugPropValue
628
+ }
629
+
630
+ if (shouldPrintDebug) {
631
+ logger.info('\n')
632
+ logger.info(
633
+ `\x1b[33m%s\x1b[0m ` + `${componentName} | ${codePosition} -------------------`
634
+ )
635
+ // prettier-ignore
636
+ logger.info(['\x1b[1m', '\x1b[32m', `<${originalNodeName} />`, disableDebugAttr ? '' : '🐛'].join(' '))
637
+ }
638
+
639
+ // add data-* debug attributes
640
+ if (shouldAddDebugProp && !disableDebugAttr) {
641
+ res.modified++
642
+ node.attributes.unshift(
643
+ t.jsxAttribute(t.jsxIdentifier('data-is'), t.stringLiteral(node.name.name))
644
+ )
645
+ if (componentName) {
646
+ node.attributes.unshift(
647
+ t.jsxAttribute(t.jsxIdentifier('data-in'), t.stringLiteral(componentName))
648
+ )
649
+ }
650
+
651
+ node.attributes.unshift(
652
+ t.jsxAttribute(
653
+ t.jsxIdentifier('data-at'),
654
+ t.stringLiteral(`${basename(filePath)}:${lineNumbers}`)
655
+ )
656
+ )
657
+ }
658
+
659
+ // disable as it gets messy
660
+ // const shouldLog = !hasLogged
661
+ // if (shouldLog) {
662
+ // logger.info(` 1️⃣ Inline optimized 2️⃣ Inline flattened 3️⃣ styled() extracted`)
663
+ // const prefix = ' |'
664
+ // // prettier-ignore
665
+ // logger.info([prefixLogs || prefix, ' total · 1️⃣ · 2️⃣ · 3️⃣'].join(' '))
666
+ // hasLogged = true
667
+ // }
668
+
669
+ if (disableExtraction) {
670
+ return
671
+ }
672
+
673
+ try {
674
+ const { staticConfig } = component
675
+ const variants = staticConfig.variants || {}
676
+ const isTextView = staticConfig.isText || false
677
+ const validStyles = staticConfig?.validStyles ?? {}
678
+
679
+ // find tag="a" tag="main" etc dom indicators
680
+ let tagName = staticConfig.defaultProps.tag ?? (isTextView ? 'span' : 'div')
681
+ traversePath
682
+ .get('openingElement')
683
+ .get('attributes')
684
+ .forEach((path) => {
685
+ const attr = path.node
686
+ if (t.isJSXSpreadAttribute(attr)) return
687
+ if (attr.name.name !== 'tag') return
688
+ const val = attr.value
689
+ if (!t.isStringLiteral(val)) return
690
+ tagName = val.value
691
+ })
692
+
693
+ const flatNode = getFlattenedNode?.({ isTextView, tag: tagName })
694
+
695
+ const inlineProps = new Set([
696
+ ...(restProps.inlineProps || []),
697
+ ...(staticConfig.inlineProps || []),
698
+ ])
699
+
700
+ const deoptProps = new Set([
701
+ // always de-opt animation
702
+ 'animation',
703
+ ...(restProps.deoptProps || []),
704
+ ...(staticConfig.deoptProps || []),
705
+ ])
706
+
707
+ const inlineWhenUnflattened = new Set([...(staticConfig.inlineWhenUnflattened || [])])
708
+
709
+ // Generate scope object at this level
710
+ const staticNamespace = getStaticBindingsForScope(
711
+ traversePath.scope,
712
+ importsWhitelist,
713
+ sourcePath,
714
+ bindingCache,
715
+ shouldPrintDebug
716
+ )
717
+
718
+ const attemptEval = !evaluateVars
719
+ ? evaluateAstNode
720
+ : createEvaluator({
721
+ props: propsWithFileInfo,
722
+ staticNamespace,
723
+ sourcePath,
724
+ traversePath,
725
+ shouldPrintDebug,
726
+ })
727
+ const attemptEvalSafe = createSafeEvaluator(attemptEval)
728
+
729
+ if (shouldPrintDebug) {
730
+ logger.info(` staticNamespace ${Object.keys(staticNamespace).join(', ')}`)
731
+ }
732
+
733
+ //
734
+ // SPREADS SETUP
735
+ //
736
+
737
+ // TODO restore
738
+ // const hasDeopt = (obj: Object) => {
739
+ // return Object.keys(obj).some(isDeoptedProp)
740
+ // }
741
+
742
+ // flatten any easily evaluatable spreads
743
+ const flattenedAttrs: (t.JSXAttribute | t.JSXSpreadAttribute)[] = []
744
+ traversePath
745
+ .get('openingElement')
746
+ .get('attributes')
747
+ .forEach((path) => {
748
+ const attr = path.node
749
+ if (!t.isJSXSpreadAttribute(attr)) {
750
+ flattenedAttrs.push(attr)
751
+ return
752
+ }
753
+ let arg: any
754
+ try {
755
+ arg = attemptEval(attr.argument)
756
+ } catch (e: any) {
757
+ if (shouldPrintDebug) {
758
+ logger.info([' couldnt parse spread', e.message].join(' '))
759
+ }
760
+ flattenedAttrs.push(attr)
761
+ return
762
+ }
763
+ if (arg !== undefined) {
764
+ try {
765
+ if (typeof arg !== 'object' || arg == null) {
766
+ if (shouldPrintDebug) {
767
+ logger.info([' non object or null arg', arg].join(' '))
768
+ }
769
+ flattenedAttrs.push(attr)
770
+ } else {
771
+ for (const k in arg) {
772
+ const value = arg[k]
773
+ // this is a null prop:
774
+ if (!value && typeof value === 'object') {
775
+ logger.error(['Unhandled null prop', k, value, arg].join(' '))
776
+ continue
777
+ }
778
+ flattenedAttrs.push(
779
+ t.jsxAttribute(
780
+ t.jsxIdentifier(k),
781
+ t.jsxExpressionContainer(literalToAst(value))
782
+ )
783
+ )
784
+ }
785
+ }
786
+ } catch (err) {
787
+ logger.warn(`cant parse spread, caught err ${err}`)
788
+ couldntParse = true
789
+ }
790
+ }
791
+ })
792
+
793
+ if (couldntParse) {
794
+ return
795
+ }
796
+
797
+ tm.mark('jsx-element-flattened', !!shouldPrintDebug)
798
+
799
+ // set flattened
800
+ node.attributes = flattenedAttrs
801
+
802
+ let attrs: ExtractedAttr[] = []
803
+ let shouldDeopt = false
804
+ const inlined = new Map<string, any>()
805
+ const variantValues = new Map<string, any>()
806
+ let hasSetOptimized = false
807
+ const inlineWhenUnflattenedOGVals = {}
808
+
809
+ // RUN first pass
810
+
811
+ // normalize all conditionals so we can evaluate away easier later
812
+ // at the same time lets normalize shorthand media queries into spreads:
813
+ // that way we can parse them with the same logic later on
814
+ //
815
+ // {...media.sm && { color: x ? 'red' : 'blue' }}
816
+ // => {...media.sm && x && { color: 'red' }}
817
+ // => {...media.sm && !x && { color: 'blue' }}
818
+ //
819
+ // $sm={{ color: 'red' }}
820
+ // => {...media.sm && { color: 'red' }}
821
+ //
822
+ // $sm={{ color: x ? 'red' : 'blue' }}
823
+ // => {...media.sm && x && { color: 'red' }}
824
+ // => {...media.sm && !x && { color: 'blue' }}
825
+
826
+ attrs = traversePath
827
+ .get('openingElement')
828
+ .get('attributes')
829
+ .flatMap((path) => {
830
+ try {
831
+ const res = evaluateAttribute(path)
832
+ tm.mark('jsx-element-evaluate-attr', !!shouldPrintDebug)
833
+ if (!res) {
834
+ path.remove()
835
+ }
836
+ return res
837
+ } catch (err: any) {
838
+ if (shouldPrintDebug) {
839
+ logger.info(
840
+ [
841
+ 'Recoverable error extracting attribute',
842
+ err.message,
843
+ shouldPrintDebug === 'verbose' ? err.stack : '',
844
+ ].join(' ')
845
+ )
846
+ if (shouldPrintDebug === 'verbose') {
847
+ logger.info(`node ${path.node?.type}`)
848
+ }
849
+ }
850
+ // dont flatten if we run into error
851
+ inlined.set(`${Math.random()}`, 'spread')
852
+ return {
853
+ type: 'attr',
854
+ value: path.node,
855
+ } as const
856
+ }
857
+ })
858
+ .flat(4)
859
+ .filter(isPresent)
860
+
861
+ if (shouldPrintDebug) {
862
+ logger.info(
863
+ [' - attrs (before):\n', logLines(attrs.map(attrStr).join(', '))].join(' ')
864
+ )
865
+ }
866
+
867
+ // START function evaluateAttribute
868
+ function evaluateAttribute(
869
+ path: NodePath<t.JSXAttribute | t.JSXSpreadAttribute>
870
+ ): ExtractedAttr | ExtractedAttr[] | null {
871
+ const attribute = path.node
872
+ const attr: ExtractedAttr = { type: 'attr', value: attribute }
873
+ // ...spreads
874
+ if (t.isJSXSpreadAttribute(attribute)) {
875
+ const arg = attribute.argument
876
+ const conditional = t.isConditionalExpression(arg)
877
+ ? // <YStack {...isSmall ? { color: 'red } : { color: 'blue }}
878
+ ([arg.test, arg.consequent, arg.alternate] as const)
879
+ : t.isLogicalExpression(arg) && arg.operator === '&&'
880
+ ? // <YStack {...isSmall && { color: 'red }}
881
+ ([arg.left, arg.right, null] as const)
882
+ : null
883
+
884
+ if (conditional) {
885
+ const [test, alt, cons] = conditional
886
+ if (!test) throw new Error(`no test`)
887
+ if ([alt, cons].some((side) => side && !isStaticObject(side))) {
888
+ if (shouldPrintDebug) {
889
+ logger.info(`not extractable ${alt} ${cons}`)
890
+ }
891
+ return attr
892
+ }
893
+ // split into individual ternaries per object property
894
+ return [
895
+ ...(createTernariesFromObjectProperties(test, alt) || []),
896
+ ...((cons &&
897
+ createTernariesFromObjectProperties(t.unaryExpression('!', test), cons)) ||
898
+ []),
899
+ ].map((ternary) => ({
900
+ type: 'ternary',
901
+ value: ternary,
902
+ }))
903
+ }
904
+ }
905
+ // END ...spreads
906
+
907
+ // directly keep these
908
+ // couldn't evaluate spread, undefined name, or name is not string
909
+ if (
910
+ t.isJSXSpreadAttribute(attribute) ||
911
+ !attribute.name ||
912
+ typeof attribute.name.name !== 'string'
913
+ ) {
914
+ if (shouldPrintDebug) {
915
+ logger.info(' ! inlining, spread attr')
916
+ }
917
+ inlined.set(`${Math.random()}`, 'spread')
918
+ return attr
919
+ }
920
+
921
+ const name = attribute.name.name
922
+
923
+ if (excludeProps?.has(name)) {
924
+ if (shouldPrintDebug) {
925
+ logger.info([' excluding prop', name].join(' '))
926
+ }
927
+ return null
928
+ }
929
+
930
+ if (inlineProps.has(name)) {
931
+ inlined.set(name, name)
932
+ if (shouldPrintDebug) {
933
+ logger.info([' ! inlining, inline prop', name].join(' '))
934
+ }
935
+ return attr
936
+ }
937
+
938
+ // can still optimize the object... see hoverStyle on native
939
+ if (deoptProps.has(name)) {
940
+ shouldDeopt = true
941
+ inlined.set(name, name)
942
+ if (shouldPrintDebug) {
943
+ logger.info([' ! inlining, deopted prop', name].join(' '))
944
+ }
945
+ return attr
946
+ }
947
+
948
+ // pass className, key, and style props through untouched
949
+ if (UNTOUCHED_PROPS[name]) {
950
+ return attr
951
+ }
952
+
953
+ if (INLINE_EXTRACTABLE[name]) {
954
+ inlined.set(name, INLINE_EXTRACTABLE[name])
955
+ return attr
956
+ }
957
+
958
+ if (name.startsWith('data-')) {
959
+ return attr
960
+ }
961
+
962
+ // shorthand media queries
963
+ if (name[0] === '$' && t.isJSXExpressionContainer(attribute?.value)) {
964
+ // allow disabling this extraction
965
+ if (disableExtractInlineMedia) {
966
+ return attr
967
+ }
968
+
969
+ const shortname = name.slice(1)
970
+ if (mediaQueryConfig[shortname]) {
971
+ const expression = attribute.value.expression
972
+ if (!t.isJSXEmptyExpression(expression)) {
973
+ const ternaries = createTernariesFromObjectProperties(
974
+ t.stringLiteral(shortname),
975
+ expression,
976
+ {
977
+ inlineMediaQuery: shortname,
978
+ }
979
+ )
980
+ if (ternaries) {
981
+ return ternaries.map((value) => ({
982
+ type: 'ternary',
983
+ value,
984
+ }))
985
+ }
986
+ }
987
+ }
988
+ }
989
+
990
+ const [value, valuePath] = (() => {
991
+ if (t.isJSXExpressionContainer(attribute?.value)) {
992
+ return [attribute.value.expression!, path.get('value')!] as const
993
+ } else {
994
+ return [attribute.value!, path.get('value')!] as const
995
+ }
996
+ })()
997
+
998
+ const remove = () => {
999
+ Array.isArray(valuePath) ? valuePath.map((p) => p.remove()) : valuePath.remove()
1000
+ }
1001
+
1002
+ if (name === 'ref') {
1003
+ if (shouldPrintDebug) {
1004
+ logger.info([' ! inlining, ref', name].join(' '))
1005
+ }
1006
+ inlined.set('ref', 'ref')
1007
+ return attr
1008
+ }
1009
+
1010
+ if (name === 'tag') {
1011
+ return {
1012
+ type: 'attr',
1013
+ value: path.node,
1014
+ }
1015
+ }
1016
+
1017
+ // native shouldn't extract variables
1018
+ if (disableExtractVariables === true) {
1019
+ if (value) {
1020
+ if (value.type === 'StringLiteral' && value.value[0] === '$') {
1021
+ if (shouldPrintDebug) {
1022
+ logger.info(
1023
+ [` ! inlining, native disable extract: ${name} =`, value.value].join(' ')
1024
+ )
1025
+ }
1026
+ inlined.set(name, true)
1027
+ return attr
1028
+ }
1029
+ }
1030
+ }
1031
+
1032
+ if (name === 'theme') {
1033
+ inlined.set('theme', attr.value)
1034
+ return attr
1035
+ }
1036
+
1037
+ // if value can be evaluated, extract it and filter it out
1038
+ const styleValue = attemptEvalSafe(value)
1039
+
1040
+ // never flatten if a prop isn't a valid static attribute
1041
+ // only post prop-mapping
1042
+ if (!variants[name] && !isValidStyleKey(name, staticConfig)) {
1043
+ let keys = [name]
1044
+ let out: any = null
1045
+
1046
+ // for now passing empty props {}, a bit odd, need to at least document
1047
+ // for now we don't expose custom components so just noting behavior
1048
+ out = staticConfig.propMapper(
1049
+ name,
1050
+ styleValue,
1051
+ defaultTheme,
1052
+ staticConfig.defaultProps,
1053
+ { resolveVariablesAs: 'auto' },
1054
+ undefined,
1055
+ undefined,
1056
+ shouldPrintDebug
1057
+ )
1058
+
1059
+ if (out) {
1060
+ if (!Array.isArray(out)) {
1061
+ logger.warn(`Error expected array but got`, out)
1062
+ couldntParse = true
1063
+ shouldDeopt = true
1064
+ } else {
1065
+ out = Object.fromEntries(out)
1066
+ keys = Object.keys(out)
1067
+ }
1068
+ }
1069
+ if (out) {
1070
+ if (isTargetingHTML) {
1071
+ // translate to DOM-compat
1072
+ out = createDOMProps(isTextView ? 'span' : 'div', out)
1073
+ // remove className - we dont use rnw styling
1074
+ delete out.className
1075
+ }
1076
+
1077
+ keys = Object.keys(out)
1078
+ }
1079
+
1080
+ let didInline = false
1081
+ const attributes = keys.map((key) => {
1082
+ const val = out[key]
1083
+ if (isValidStyleKey(key, staticConfig)) {
1084
+ return {
1085
+ type: 'style',
1086
+ value: { [key]: styleValue },
1087
+ name: key,
1088
+ attr: path.node,
1089
+ } as const
1090
+ }
1091
+ if (
1092
+ validHTMLAttributes[key] ||
1093
+ key.startsWith('aria-') ||
1094
+ key.startsWith('data-') ||
1095
+ // this is debug stuff added by vite / new jsx transform
1096
+ key === '__source' ||
1097
+ key === '__self'
1098
+ ) {
1099
+ return attr
1100
+ }
1101
+ if (shouldPrintDebug) {
1102
+ logger.info(' ! inlining, non-static ' + key)
1103
+ }
1104
+ didInline = true
1105
+ inlined.set(key, val)
1106
+ return val
1107
+ })
1108
+
1109
+ // weird logic whats going on here
1110
+ if (didInline) {
1111
+ if (shouldPrintDebug) {
1112
+ logger.info(` bailing flattening due to attributes ${attributes}`)
1113
+ }
1114
+ // bail
1115
+ return attr
1116
+ }
1117
+
1118
+ // return evaluated attributes
1119
+ return attributes
1120
+ }
1121
+
1122
+ // FAILED = dynamic or ternary, keep going
1123
+ if (styleValue !== FAILED_EVAL) {
1124
+ if (inlineWhenUnflattened.has(name)) {
1125
+ // preserve original value for restoration
1126
+ inlineWhenUnflattenedOGVals[name] = { styleValue, attr }
1127
+ }
1128
+
1129
+ if (isValidStyleKey(name, staticConfig)) {
1130
+ if (shouldPrintDebug) {
1131
+ logger.info(` style: ${name} = ${styleValue}`)
1132
+ }
1133
+ if (!(name in staticConfig.defaultProps)) {
1134
+ if (!hasSetOptimized) {
1135
+ res.optimized++
1136
+ hasSetOptimized = true
1137
+ }
1138
+ }
1139
+ return {
1140
+ type: 'style',
1141
+ value: { [name]: styleValue },
1142
+ name,
1143
+ attr: path.node,
1144
+ }
1145
+ } else {
1146
+ if (variants[name]) {
1147
+ variantValues.set(name, styleValue)
1148
+ }
1149
+ inlined.set(name, true)
1150
+ return attr
1151
+ }
1152
+ }
1153
+
1154
+ // ternaries!
1155
+
1156
+ // binary ternary, we can eventually make this smarter but step 1
1157
+ // basically for the common use case of:
1158
+ // opacity={(conditional ? 0 : 1) * scale}
1159
+ if (t.isBinaryExpression(value)) {
1160
+ if (shouldPrintDebug) {
1161
+ logger.info(` binary expression ${name} = ${value}`)
1162
+ }
1163
+ const { operator, left, right } = value
1164
+ // if one side is a ternary, and the other side is evaluatable, we can maybe extract
1165
+ const lVal = attemptEvalSafe(left)
1166
+ const rVal = attemptEvalSafe(right)
1167
+ if (shouldPrintDebug) {
1168
+ logger.info(` evalBinaryExpression lVal ${String(lVal)}, rVal ${String(rVal)}`)
1169
+ }
1170
+ if (lVal !== FAILED_EVAL && t.isConditionalExpression(right)) {
1171
+ const ternary = addBinaryConditional(operator, left, right)
1172
+ if (ternary) return ternary
1173
+ }
1174
+ if (rVal !== FAILED_EVAL && t.isConditionalExpression(left)) {
1175
+ const ternary = addBinaryConditional(operator, right, left)
1176
+ if (ternary) return ternary
1177
+ }
1178
+ if (shouldPrintDebug) {
1179
+ logger.info(` evalBinaryExpression cant extract`)
1180
+ }
1181
+ inlined.set(name, true)
1182
+ return attr
1183
+ }
1184
+
1185
+ const staticConditional = getStaticConditional(value)
1186
+ if (staticConditional) {
1187
+ if (shouldPrintDebug === 'verbose') {
1188
+ logger.info(` static conditional ${name} ${value}`)
1189
+ }
1190
+ return { type: 'ternary', value: staticConditional }
1191
+ }
1192
+
1193
+ const staticLogical = getStaticLogical(value)
1194
+ if (staticLogical) {
1195
+ if (shouldPrintDebug === 'verbose') {
1196
+ logger.info(` static ternary ${name} = ${value}`)
1197
+ }
1198
+ return { type: 'ternary', value: staticLogical }
1199
+ }
1200
+
1201
+ // if we've made it this far, the prop stays inline
1202
+ inlined.set(name, true)
1203
+ if (shouldPrintDebug) {
1204
+ logger.info(` ! inline no match ${name} ${value}`)
1205
+ }
1206
+
1207
+ //
1208
+ // RETURN ATTR
1209
+ //
1210
+ return attr
1211
+
1212
+ // attr helpers:
1213
+ function addBinaryConditional(
1214
+ operator: any,
1215
+ staticExpr: any,
1216
+ cond: t.ConditionalExpression
1217
+ ): ExtractedAttr | null {
1218
+ if (getStaticConditional(cond)) {
1219
+ const alt = attemptEval(t.binaryExpression(operator, staticExpr, cond.alternate))
1220
+ const cons = attemptEval(t.binaryExpression(operator, staticExpr, cond.consequent))
1221
+ if (shouldPrintDebug) {
1222
+ logger.info([' binaryConditional', cond.test, cons, alt].join(' '))
1223
+ }
1224
+ return {
1225
+ type: 'ternary',
1226
+ value: {
1227
+ test: cond.test,
1228
+ remove,
1229
+ alternate: { [name]: alt },
1230
+ consequent: { [name]: cons },
1231
+ },
1232
+ }
1233
+ }
1234
+ return null
1235
+ }
1236
+
1237
+ function getStaticConditional(value: t.Node): Ternary | null {
1238
+ if (t.isConditionalExpression(value)) {
1239
+ try {
1240
+ const aVal = attemptEval(value.alternate)
1241
+ const cVal = attemptEval(value.consequent)
1242
+ if (shouldPrintDebug) {
1243
+ const type = value.test.type
1244
+ logger.info([' static ternary', type, cVal, aVal].join(' '))
1245
+ }
1246
+ return {
1247
+ test: value.test,
1248
+ remove,
1249
+ consequent: { [name]: cVal },
1250
+ alternate: { [name]: aVal },
1251
+ }
1252
+ } catch (err: any) {
1253
+ if (shouldPrintDebug) {
1254
+ logger.info([' cant eval ternary', err.message].join(' '))
1255
+ }
1256
+ }
1257
+ }
1258
+ return null
1259
+ }
1260
+
1261
+ function getStaticLogical(value: t.Node): Ternary | null {
1262
+ if (t.isLogicalExpression(value)) {
1263
+ if (value.operator === '&&') {
1264
+ try {
1265
+ const val = attemptEval(value.right)
1266
+ if (shouldPrintDebug) {
1267
+ logger.info([' staticLogical', value.left, name, val].join(' '))
1268
+ }
1269
+ return {
1270
+ test: value.left,
1271
+ remove,
1272
+ consequent: { [name]: val },
1273
+ alternate: null,
1274
+ }
1275
+ } catch (err) {
1276
+ if (shouldPrintDebug) {
1277
+ logger.info([' cant static eval logical', err].join(' '))
1278
+ }
1279
+ }
1280
+ }
1281
+ }
1282
+ return null
1283
+ }
1284
+ } // END function evaluateAttribute
1285
+
1286
+ function isStaticObject(obj: t.Node): obj is t.ObjectExpression {
1287
+ return (
1288
+ t.isObjectExpression(obj) &&
1289
+ obj.properties.every((prop) => {
1290
+ if (!t.isObjectProperty(prop)) {
1291
+ logger.info(['not object prop', prop].join(' '))
1292
+ return false
1293
+ }
1294
+ const propName = prop.key['name']
1295
+ if (!isValidStyleKey(propName, staticConfig) && propName !== 'tag') {
1296
+ if (shouldPrintDebug) {
1297
+ logger.info([' not a valid style prop!', propName].join(' '))
1298
+ }
1299
+ return false
1300
+ }
1301
+ return true
1302
+ })
1303
+ )
1304
+ }
1305
+
1306
+ // side = {
1307
+ // color: 'red',
1308
+ // background: x ? 'red' : 'green',
1309
+ // $gtSm: { color: 'green' }
1310
+ // }
1311
+ // => Ternary<test, { color: 'red' }, null>
1312
+ // => Ternary<test && x, { background: 'red' }, null>
1313
+ // => Ternary<test && !x, { background: 'green' }, null>
1314
+ // => Ternary<test && '$gtSm', { color: 'green' }, null>
1315
+ function createTernariesFromObjectProperties(
1316
+ test: t.Expression,
1317
+ side: t.Expression | null,
1318
+ ternaryPartial: Partial<Ternary> = {}
1319
+ ): null | Ternary[] {
1320
+ if (!side) {
1321
+ return null
1322
+ }
1323
+ if (!isStaticObject(side)) {
1324
+ throw new Error('not extractable')
1325
+ }
1326
+ return side.properties.flatMap((property) => {
1327
+ if (!t.isObjectProperty(property)) {
1328
+ throw new Error('expected object property')
1329
+ }
1330
+ // handle media queries inside spread/conditional objects
1331
+ if (t.isIdentifier(property.key)) {
1332
+ const key = property.key.name
1333
+ const mediaQueryKey = key.slice(1)
1334
+ const isMediaQuery = key[0] === '$' && mediaQueryConfig[mediaQueryKey]
1335
+ if (isMediaQuery) {
1336
+ if (t.isExpression(property.value)) {
1337
+ const ternaries = createTernariesFromObjectProperties(
1338
+ t.stringLiteral(mediaQueryKey),
1339
+ property.value,
1340
+ {
1341
+ inlineMediaQuery: mediaQueryKey,
1342
+ }
1343
+ )
1344
+ if (ternaries) {
1345
+ return ternaries.map((value) => ({
1346
+ ...ternaryPartial,
1347
+ ...value,
1348
+ // ensure media query test stays on left side (see getMediaQueryTernary)
1349
+ test: t.logicalExpression('&&', value.test, test),
1350
+ }))
1351
+ } else {
1352
+ logger.info(['⚠️ no ternaries?', property].join(' '))
1353
+ }
1354
+ } else {
1355
+ logger.info(['⚠️ not expression', property].join(' '))
1356
+ }
1357
+ }
1358
+ }
1359
+ // this could be a recurse here if we want to get fancy
1360
+ if (t.isConditionalExpression(property.value)) {
1361
+ // merge up into the parent conditional, split into two
1362
+ const [truthy, falsy] = [
1363
+ t.objectExpression([t.objectProperty(property.key, property.value.consequent)]),
1364
+ t.objectExpression([t.objectProperty(property.key, property.value.alternate)]),
1365
+ ].map((x) => attemptEval(x))
1366
+ return [
1367
+ createTernary({
1368
+ remove() {},
1369
+ ...ternaryPartial,
1370
+ test: t.logicalExpression('&&', test, property.value.test),
1371
+ consequent: truthy,
1372
+ alternate: null,
1373
+ }),
1374
+ createTernary({
1375
+ ...ternaryPartial,
1376
+ test: t.logicalExpression(
1377
+ '&&',
1378
+ test,
1379
+ t.unaryExpression('!', property.value.test)
1380
+ ),
1381
+ consequent: falsy,
1382
+ alternate: null,
1383
+ remove() {},
1384
+ }),
1385
+ ]
1386
+ }
1387
+ const obj = t.objectExpression([t.objectProperty(property.key, property.value)])
1388
+ const consequent = attemptEval(obj)
1389
+ return createTernary({
1390
+ remove() {},
1391
+ ...ternaryPartial,
1392
+ test,
1393
+ consequent,
1394
+ alternate: null,
1395
+ })
1396
+ })
1397
+ }
1398
+
1399
+ // now update to new values
1400
+ node.attributes = attrs.filter(isAttr).map((x) => x.value)
1401
+
1402
+ if (couldntParse || shouldDeopt) {
1403
+ if (shouldPrintDebug) {
1404
+ logger.info([` avoid optimizing:`, { couldntParse, shouldDeopt }].join(' '))
1405
+ }
1406
+ node.attributes = ogAttributes
1407
+ return
1408
+ }
1409
+
1410
+ // before deopt, can still optimize
1411
+ const parentFn = findTopmostFunction(traversePath)
1412
+ if (parentFn) {
1413
+ modifiedComponents.add(parentFn)
1414
+ }
1415
+
1416
+ // combine ternaries
1417
+ let ternaries: Ternary[] = []
1418
+ attrs = attrs
1419
+ .reduce<(ExtractedAttr | ExtractedAttr[])[]>((out, cur) => {
1420
+ const next = attrs[attrs.indexOf(cur) + 1]
1421
+ if (cur.type === 'ternary') {
1422
+ ternaries.push(cur.value)
1423
+ }
1424
+ if ((!next || next.type !== 'ternary') && ternaries.length) {
1425
+ // finish, process
1426
+ const normalized = normalizeTernaries(ternaries).map(
1427
+ ({ alternate, consequent, ...rest }) => {
1428
+ return {
1429
+ type: 'ternary' as const,
1430
+ value: {
1431
+ ...rest,
1432
+ alternate: alternate || null,
1433
+ consequent: consequent || null,
1434
+ },
1435
+ }
1436
+ }
1437
+ )
1438
+ try {
1439
+ return [...out, ...normalized]
1440
+ } finally {
1441
+ if (shouldPrintDebug) {
1442
+ logger.info(
1443
+ ` normalizeTernaries (${ternaries.length} => ${normalized.length})`
1444
+ )
1445
+ }
1446
+ ternaries = []
1447
+ }
1448
+ }
1449
+ if (cur.type === 'ternary') {
1450
+ return out
1451
+ }
1452
+ out.push(cur)
1453
+ return out
1454
+ }, [])
1455
+ .flat()
1456
+
1457
+ // flatten logic!
1458
+ // fairly simple check to see if all children are text
1459
+ const hasSpread = node.attributes.some((x) => t.isJSXSpreadAttribute(x))
1460
+
1461
+ const hasOnlyStringChildren =
1462
+ !hasSpread &&
1463
+ (node.selfClosing ||
1464
+ (traversePath.node.children &&
1465
+ traversePath.node.children.every((x) => x.type === 'JSXText')))
1466
+
1467
+ const themeVal = inlined.get('theme')
1468
+
1469
+ // on native we can't flatten when theme prop is set
1470
+ if (target !== 'native') {
1471
+ inlined.delete('theme')
1472
+ }
1473
+
1474
+ for (const [key] of [...inlined]) {
1475
+ const isStaticObjectVariant = staticConfig.variants?.[key] && variantValues.has(key)
1476
+ if (INLINE_EXTRACTABLE[key] || isStaticObjectVariant) {
1477
+ inlined.delete(key)
1478
+ }
1479
+ }
1480
+
1481
+ const canFlattenProps = inlined.size === 0
1482
+
1483
+ let shouldFlatten = Boolean(
1484
+ flatNode &&
1485
+ !shouldDeopt &&
1486
+ canFlattenProps &&
1487
+ !hasSpread &&
1488
+ staticConfig.neverFlatten !== true &&
1489
+ (staticConfig.neverFlatten === 'jsx' ? hasOnlyStringChildren : true)
1490
+ )
1491
+
1492
+ const shouldWrapTheme = shouldFlatten && themeVal
1493
+ const usedThemeKeys = new Set<string>()
1494
+
1495
+ if (disableExtractVariables) {
1496
+ // if it accesses any theme values during evaluation
1497
+ themeAccessListeners.add((key) => {
1498
+ shouldFlatten = false
1499
+ usedThemeKeys.add(key)
1500
+ if (shouldPrintDebug === 'verbose') {
1501
+ logger.info([' ! accessing theme key, avoid flatten', key].join(' '))
1502
+ }
1503
+ })
1504
+ }
1505
+
1506
+ if (shouldPrintDebug) {
1507
+ try {
1508
+ // prettier-ignore
1509
+ logger.info([' - flatten?', objToStr({ hasSpread, shouldDeopt, shouldFlatten, canFlattenProps, shouldWrapTheme, hasOnlyStringChildren }), 'inlined', [...inlined]].join(' '))
1510
+ } catch {
1511
+ // ok
1512
+ }
1513
+ }
1514
+
1515
+ // wrap theme around children on flatten
1516
+ // TODO move this to bottom and re-check shouldFlatten
1517
+ // account for shouldFlatten could change w the above block "if (disableExtractVariables)"
1518
+ if (shouldFlatten && shouldWrapTheme) {
1519
+ if (!programPath) {
1520
+ console.warn(
1521
+ `No program path found, avoiding importing flattening / importing theme in ${sourcePath}`
1522
+ )
1523
+ } else {
1524
+ if (shouldPrintDebug) {
1525
+ logger.info([' - wrapping theme', themeVal].join(' '))
1526
+ }
1527
+
1528
+ // remove theme attribute from flattened node
1529
+ attrs = attrs.filter((x) =>
1530
+ x.type === 'attr' && t.isJSXAttribute(x.value) && x.value.name.name === 'theme'
1531
+ ? false
1532
+ : true
1533
+ )
1534
+
1535
+ // add import
1536
+ if (!hasImportedTheme) {
1537
+ hasImportedTheme = true
1538
+ programPath.node.body.push(
1539
+ t.importDeclaration(
1540
+ [t.importSpecifier(t.identifier('_TamaguiTheme'), t.identifier('Theme'))],
1541
+ t.stringLiteral('@tamagui/core')
1542
+ )
1543
+ )
1544
+ }
1545
+
1546
+ traversePath.replaceWith(
1547
+ t.jsxElement(
1548
+ t.jsxOpeningElement(t.jsxIdentifier('_TamaguiTheme'), [
1549
+ t.jsxAttribute(t.jsxIdentifier('name'), themeVal.value),
1550
+ ]),
1551
+ t.jsxClosingElement(t.jsxIdentifier('_TamaguiTheme')),
1552
+ [traversePath.node]
1553
+ )
1554
+ )
1555
+ }
1556
+ }
1557
+
1558
+ // only if we flatten, ensure the default styles are there
1559
+ if (shouldFlatten) {
1560
+ const defaultStyleAttrs = Object.keys(staticConfig.defaultProps).flatMap((key) => {
1561
+ if (!isValidStyleKey(key, staticConfig)) {
1562
+ return []
1563
+ }
1564
+ const value = staticConfig.defaultProps[key]
1565
+ const name = tamaguiConfig.shorthands[key] || key
1566
+ if (value === undefined) {
1567
+ logger.warn(`⚠️ Error evaluating default style for component, prop ${key} ${value}`)
1568
+ shouldDeopt = true
1569
+ return
1570
+ }
1571
+ const attr: ExtractedAttrStyle = {
1572
+ type: 'style',
1573
+ name,
1574
+ value: { [name]: value },
1575
+ }
1576
+ return attr
1577
+ }) as ExtractedAttr[]
1578
+
1579
+ if (defaultStyleAttrs.length) {
1580
+ attrs = [...defaultStyleAttrs, ...attrs]
1581
+ }
1582
+ }
1583
+
1584
+ if (shouldDeopt) {
1585
+ if (shouldPrintDebug) {
1586
+ logger.info(`Deopting`)
1587
+ }
1588
+ node.attributes = ogAttributes
1589
+ return
1590
+ }
1591
+
1592
+ // insert overrides - this inserts null props for things that are set in classNames
1593
+ // only when not flattening, so the downstream component can skip applying those styles
1594
+ const ensureOverridden = {}
1595
+ if (!shouldFlatten) {
1596
+ for (const cur of attrs) {
1597
+ if (cur.type === 'style') {
1598
+ // TODO need to loop over initial props not just style props
1599
+ for (const key in cur.value) {
1600
+ const shouldEnsureOverridden = !!staticConfig.ensureOverriddenProp?.[key]
1601
+ const isSetInAttrsAlready = attrs.some(
1602
+ (x) =>
1603
+ x.type === 'attr' &&
1604
+ x.value.type === 'JSXAttribute' &&
1605
+ x.value.name.name === key
1606
+ )
1607
+
1608
+ if (!isSetInAttrsAlready) {
1609
+ const isVariant = !!staticConfig.variants?.[cur.name || '']
1610
+ if (isVariant || shouldEnsureOverridden) {
1611
+ ensureOverridden[key] = true
1612
+ }
1613
+ }
1614
+ }
1615
+ }
1616
+ }
1617
+ }
1618
+
1619
+ if (shouldPrintDebug) {
1620
+ logger.info(
1621
+ [' - attrs (flattened): \n', logLines(attrs.map(attrStr).join(', '))].join(' ')
1622
+ )
1623
+ logger.info(
1624
+ [' - ensureOverriden:', Object.keys(ensureOverridden).join(', ')].join(' ')
1625
+ )
1626
+ }
1627
+
1628
+ const state = {
1629
+ noClassNames: false,
1630
+ focus: false,
1631
+ hover: false,
1632
+ mounted: true, // TODO match logic in createComponent
1633
+ press: false,
1634
+ pressIn: false,
1635
+ }
1636
+
1637
+ // evaluates all static attributes into a simple object
1638
+ let foundStaticProps = {}
1639
+ for (const key in attrs) {
1640
+ const cur = attrs[key]
1641
+ if (cur.type === 'style') {
1642
+ // remove variants because they are processed later, and can lead to invalid values here
1643
+ // see <Spacer flex /> where flex looks like a valid style, but is a variant
1644
+ foundStaticProps = {
1645
+ ...foundStaticProps,
1646
+ ...expandStylesWithoutVariants(cur.value),
1647
+ }
1648
+ continue
1649
+ }
1650
+ if (cur.type === 'attr') {
1651
+ if (t.isJSXSpreadAttribute(cur.value)) {
1652
+ continue
1653
+ }
1654
+ if (!t.isJSXIdentifier(cur.value.name)) {
1655
+ continue
1656
+ }
1657
+ const key = cur.value.name.name
1658
+ // undefined = boolean true
1659
+ const value = attemptEvalSafe(cur.value.value || t.booleanLiteral(true))
1660
+ if (value !== FAILED_EVAL) {
1661
+ foundStaticProps = {
1662
+ ...foundStaticProps,
1663
+ [key]: value,
1664
+ }
1665
+ }
1666
+ }
1667
+ }
1668
+
1669
+ // must preserve exact order
1670
+ const completeProps = {}
1671
+ for (const key in staticConfig.defaultProps) {
1672
+ if (!(key in foundStaticProps)) {
1673
+ completeProps[key] = staticConfig.defaultProps[key]
1674
+ }
1675
+ }
1676
+ for (const key in foundStaticProps) {
1677
+ completeProps[key] = foundStaticProps[key]
1678
+ }
1679
+
1680
+ // expand shorthands, de-opt variables
1681
+ attrs = attrs.reduce<ExtractedAttr[]>((acc, cur) => {
1682
+ if (!cur) return acc
1683
+ if (cur.type === 'attr' && !t.isJSXSpreadAttribute(cur.value)) {
1684
+ if (shouldFlatten) {
1685
+ const name = cur.value.name.name
1686
+ if (typeof name === 'string') {
1687
+ if (name === 'tag') {
1688
+ // remove tag=""
1689
+ return acc
1690
+ }
1691
+
1692
+ // if flattening, expand variants
1693
+ if (variants[name] && variantValues.has(name)) {
1694
+ let out = Object.fromEntries(
1695
+ staticConfig.propMapper(
1696
+ name,
1697
+ variantValues.get(name),
1698
+ defaultTheme,
1699
+ completeProps,
1700
+ { ...state, resolveVariablesAs: 'auto' },
1701
+ undefined,
1702
+ undefined,
1703
+ shouldPrintDebug
1704
+ ) || []
1705
+ )
1706
+ if (out && isTargetingHTML) {
1707
+ const cn = out.className
1708
+ // translate to DOM-compat
1709
+ out = createDOMProps(isTextView ? 'span' : 'div', out)
1710
+ // remove rnw className use ours
1711
+ out.className = cn
1712
+ }
1713
+ if (shouldPrintDebug) {
1714
+ logger.info([' - expanded variant', name, out].join(' '))
1715
+ }
1716
+ for (const key in out) {
1717
+ const value = out[key]
1718
+ if (isValidStyleKey(key, staticConfig)) {
1719
+ acc.push({
1720
+ type: 'style',
1721
+ value: { [key]: value },
1722
+ name: key,
1723
+ attr: cur.value,
1724
+ } as const)
1725
+ } else {
1726
+ acc.push({
1727
+ type: 'attr',
1728
+ value: t.jsxAttribute(
1729
+ t.jsxIdentifier(key),
1730
+ t.jsxExpressionContainer(
1731
+ typeof value === 'string'
1732
+ ? t.stringLiteral(value)
1733
+ : literalToAst(value)
1734
+ )
1735
+ ),
1736
+ })
1737
+ }
1738
+ }
1739
+ }
1740
+ }
1741
+ }
1742
+ }
1743
+
1744
+ if (cur.type !== 'style') {
1745
+ acc.push(cur)
1746
+ return acc
1747
+ }
1748
+
1749
+ let key = Object.keys(cur.value)[0]
1750
+ const value = cur.value[key]
1751
+ const fullKey = tamaguiConfig.shorthands[key]
1752
+ // expand shorthands
1753
+ if (fullKey) {
1754
+ cur.value = { [fullKey]: value }
1755
+ key = fullKey
1756
+ }
1757
+
1758
+ // finally we have all styles + expansions, lets see if we need to skip
1759
+ // any and keep them as attrs
1760
+ if (disableExtractVariables) {
1761
+ if (value[0] === '$' && (usedThemeKeys.has(key) || usedThemeKeys.has(fullKey))) {
1762
+ if (shouldPrintDebug) {
1763
+ logger.info([` keeping variable inline: ${key} =`, value].join(' '))
1764
+ }
1765
+ acc.push({
1766
+ type: 'attr',
1767
+ value: t.jsxAttribute(
1768
+ t.jsxIdentifier(key),
1769
+ t.jsxExpressionContainer(t.stringLiteral(value))
1770
+ ),
1771
+ })
1772
+ return acc
1773
+ }
1774
+ }
1775
+
1776
+ acc.push(cur)
1777
+ return acc
1778
+ }, [])
1779
+
1780
+ tm.mark('jsx-element-expanded', !!shouldPrintDebug)
1781
+ if (shouldPrintDebug) {
1782
+ logger.info(
1783
+ [' - attrs (expanded): \n', logLines(attrs.map(attrStr).join(', '))].join(' ')
1784
+ )
1785
+ }
1786
+
1787
+ // merge styles, leave undefined values
1788
+ let prev: ExtractedAttr | null = null
1789
+
1790
+ function splitVariants(style: any) {
1791
+ const variants = {}
1792
+ const styles = {}
1793
+ for (const key in style) {
1794
+ if (staticConfig.variants?.[key]) {
1795
+ variants[key] = style[key]
1796
+ } else {
1797
+ styles[key] = style[key]
1798
+ }
1799
+ }
1800
+ return {
1801
+ variants,
1802
+ styles,
1803
+ }
1804
+ }
1805
+
1806
+ function expandStylesWithoutVariants(style: any) {
1807
+ const { variants, styles } = splitVariants(style)
1808
+ return {
1809
+ ...expandStyles(styles),
1810
+ ...variants,
1811
+ }
1812
+ }
1813
+
1814
+ function mergeStyles(prev: ViewStyle & PseudoStyles, nextIn: ViewStyle & PseudoStyles) {
1815
+ const next = expandStylesWithoutVariants(nextIn)
1816
+ for (const key in next) {
1817
+ // merge pseudos
1818
+ if (pseudoDescriptors[key]) {
1819
+ prev[key] = prev[key] || {}
1820
+ if (shouldPrintDebug) {
1821
+ if (!next[key] || !prev[key]) {
1822
+ logger.info(['warn: missing', key, prev, next].join(' '))
1823
+ }
1824
+ }
1825
+ Object.assign(prev[key], next[key])
1826
+ } else {
1827
+ prev[key] = next[key]
1828
+ }
1829
+ }
1830
+ }
1831
+
1832
+ attrs = attrs.reduce<ExtractedAttr[]>((acc, cur) => {
1833
+ if (cur.type === 'style') {
1834
+ const key = Object.keys(cur.value)[0]
1835
+ const value = cur.value[key]
1836
+
1837
+ const shouldKeepOriginalAttr =
1838
+ // !isStyleAndAttr[key] &&
1839
+ !shouldFlatten &&
1840
+ // de-opt transform styles so it merges properly if not flattened
1841
+ // we handle this later on
1842
+ // (stylePropsTransform[key] ||
1843
+ // de-opt if non-style
1844
+ !validStyles[key] &&
1845
+ !pseudoDescriptors[key] &&
1846
+ !key.startsWith('data-')
1847
+
1848
+ if (shouldKeepOriginalAttr) {
1849
+ if (shouldPrintDebug) {
1850
+ logger.info([' - keeping as non-style', key].join(' '))
1851
+ }
1852
+ prev = cur
1853
+ acc.push({
1854
+ type: 'attr',
1855
+ value: t.jsxAttribute(
1856
+ t.jsxIdentifier(key),
1857
+ t.jsxExpressionContainer(
1858
+ typeof value === 'string' ? t.stringLiteral(value) : literalToAst(value)
1859
+ )
1860
+ ),
1861
+ })
1862
+ acc.push(cur)
1863
+ return acc
1864
+ }
1865
+
1866
+ if (ensureOverridden[key]) {
1867
+ acc.push({
1868
+ type: 'attr',
1869
+ value:
1870
+ cur.attr ||
1871
+ t.jsxAttribute(t.jsxIdentifier(key), t.jsxExpressionContainer(t.nullLiteral())),
1872
+ })
1873
+ }
1874
+
1875
+ if (prev?.type === 'style') {
1876
+ mergeStyles(prev.value, cur.value)
1877
+ return acc
1878
+ }
1879
+ }
1880
+
1881
+ prev = cur
1882
+ acc.push(cur)
1883
+ return acc
1884
+ }, [])
1885
+
1886
+ if (shouldPrintDebug) {
1887
+ logger.info(
1888
+ [' - attrs (combined 🔀): \n', logLines(attrs.map(attrStr).join(', '))].join(' ')
1889
+ )
1890
+ logger.info(
1891
+ [' - defaultProps: \n', logLines(objToStr(staticConfig.defaultProps))].join(' ')
1892
+ )
1893
+ // prettier-ignore
1894
+ logger.info([' - foundStaticProps: \n', logLines(objToStr(foundStaticProps))].join(' '))
1895
+ logger.info([' - completeProps: \n', logLines(objToStr(completeProps))].join(' '))
1896
+ }
1897
+
1898
+ // post process
1899
+ const getStyles = (props: Object | null, debugName = '') => {
1900
+ if (!props || !Object.keys(props).length) {
1901
+ if (shouldPrintDebug) logger.info([' getStyles() no props'].join(' '))
1902
+ return {}
1903
+ }
1904
+ if (excludeProps && !!excludeProps.size) {
1905
+ for (const key in props) {
1906
+ if (excludeProps.has(key)) {
1907
+ if (shouldPrintDebug) logger.info([' delete excluded', key].join(' '))
1908
+ delete props[key]
1909
+ }
1910
+ }
1911
+ }
1912
+ try {
1913
+ const out = getSplitStyles(
1914
+ props,
1915
+ staticConfig,
1916
+ defaultTheme,
1917
+ {
1918
+ ...state,
1919
+ fallbackProps: completeProps,
1920
+ },
1921
+ undefined,
1922
+ undefined,
1923
+ undefined,
1924
+ debugPropValue
1925
+ )
1926
+
1927
+ const outStyle = {
1928
+ ...out.style,
1929
+ ...out.pseudos,
1930
+ }
1931
+ // omitInvalidStyles(outStyle)
1932
+ // if (shouldPrintDebug) {
1933
+ // // prettier-ignore
1934
+ // logger.info(` getStyles ${debugName} (props):\n`, logLines(objToStr(props)))
1935
+ // // prettier-ignore
1936
+ // logger.info(` getStyles ${debugName} (out.viewProps):\n`, logLines(objToStr(out.viewProps)))
1937
+ // // prettier-ignore
1938
+ // logger.info(` getStyles ${debugName} (out.style):\n`, logLines(objToStr(outStyle || {}), true))
1939
+ // }
1940
+ return outStyle
1941
+ } catch (err: any) {
1942
+ logger.info(['error', err.message, err.stack].join(' '))
1943
+ return {}
1944
+ }
1945
+ }
1946
+
1947
+ // used to ensure we pass the entire prop bundle to getStyles
1948
+ const completeStyles = getStyles(completeProps, 'completeStyles')
1949
+
1950
+ if (!completeStyles) {
1951
+ throw new Error(`Impossible, no styles`)
1952
+ }
1953
+
1954
+ const isNativeNotFlat = !shouldFlatten && target === 'native'
1955
+ if (isNativeNotFlat) {
1956
+ if (shouldPrintDebug) {
1957
+ logger.info(`Disabled flattening except for simple cases on native for now`)
1958
+ }
1959
+ node.attributes = ogAttributes
1960
+ return null
1961
+ }
1962
+
1963
+ let getStyleError: any = null
1964
+
1965
+ // fix up ternaries, combine final style values
1966
+ for (const attr of attrs) {
1967
+ try {
1968
+ switch (attr.type) {
1969
+ case 'ternary': {
1970
+ const a = getStyles(attr.value.alternate, 'ternary.alternate')
1971
+ const c = getStyles(attr.value.consequent, 'ternary.consequent')
1972
+ if (a) attr.value.alternate = a
1973
+ if (c) attr.value.consequent = c
1974
+ if (shouldPrintDebug) logger.info([' => tern ', attrStr(attr)].join(' '))
1975
+ continue
1976
+ }
1977
+ case 'style': {
1978
+ // expand variants and such
1979
+ const styles = getStyles(attr.value, 'style')
1980
+ if (styles) {
1981
+ attr.value = styles
1982
+ }
1983
+ // prettier-ignore
1984
+ if (shouldPrintDebug) logger.info([' * styles (in)', logLines(objToStr(attr.value))].join(' '))
1985
+ // prettier-ignore
1986
+ if (shouldPrintDebug) logger.info([' * styles (out)', logLines(objToStr(styles))].join(' '))
1987
+ continue
1988
+ }
1989
+ }
1990
+ } catch (err) {
1991
+ // any error de-opt
1992
+ getStyleError = err
1993
+ }
1994
+ }
1995
+
1996
+ if (shouldPrintDebug) {
1997
+ // prettier-ignore
1998
+ logger.info([' - attrs (ternaries/combined):\n', logLines(attrs.map(attrStr).join(', '))].join(' '))
1999
+ }
2000
+
2001
+ tm.mark('jsx-element-styles', !!shouldPrintDebug)
2002
+
2003
+ if (getStyleError) {
2004
+ logger.info([' ⚠️ postprocessing error, deopt', getStyleError].join(' '))
2005
+ node.attributes = ogAttributes
2006
+ return null
2007
+ }
2008
+
2009
+ // final lazy extra loop:
2010
+ const existingStyleKeys = new Set()
2011
+ for (let i = attrs.length - 1; i >= 0; i--) {
2012
+ const attr = attrs[i]
2013
+
2014
+ // if flattening map inline props to proper flattened names
2015
+ if (shouldFlatten) {
2016
+ if (attr.type === 'attr') {
2017
+ if (t.isJSXAttribute(attr.value)) {
2018
+ if (t.isJSXIdentifier(attr.value.name)) {
2019
+ const name = attr.value.name.name
2020
+ if (INLINE_EXTRACTABLE[name]) {
2021
+ // map to HTML only name
2022
+ attr.value.name.name = INLINE_EXTRACTABLE[name]
2023
+ }
2024
+ }
2025
+ }
2026
+ }
2027
+ }
2028
+
2029
+ // remove duplicate styles
2030
+ // so if you have:
2031
+ // style({ color: 'red' }), ...someProps, style({ color: 'green' })
2032
+ // this will mutate:
2033
+ // style({}), ...someProps, style({ color: 'green' })
2034
+ if (attr.type === 'style') {
2035
+ for (const key in attr.value) {
2036
+ if (existingStyleKeys.has(key)) {
2037
+ if (shouldPrintDebug) {
2038
+ logger.info([` >> delete existing ${key}`].join(' '))
2039
+ }
2040
+ delete attr.value[key]
2041
+ } else {
2042
+ existingStyleKeys.add(key)
2043
+ }
2044
+ }
2045
+ }
2046
+ }
2047
+
2048
+ // inlineWhenUnflattened
2049
+ if (!shouldFlatten) {
2050
+ if (inlineWhenUnflattened.size) {
2051
+ for (const [index, attr] of attrs.entries()) {
2052
+ if (attr.type === 'style') {
2053
+ for (const key in attr.value) {
2054
+ if (!inlineWhenUnflattened.has(key)) continue
2055
+ const val = inlineWhenUnflattenedOGVals[key]
2056
+ if (val) {
2057
+ // delete the style
2058
+ delete attr.value[key]
2059
+
2060
+ // and insert it before
2061
+ attrs.splice(index - 1, 0, val.attr)
2062
+ } else {
2063
+ // just delete it, it was added during expansion but should be left inline
2064
+ delete attr.value[key]
2065
+ }
2066
+ }
2067
+ }
2068
+ }
2069
+ }
2070
+ }
2071
+
2072
+ if (shouldFlatten) {
2073
+ // DO FLATTEN
2074
+ if (shouldPrintDebug) {
2075
+ logger.info([' [✅] flattening', originalNodeName, flatNode].join(' '))
2076
+ }
2077
+ // @ts-ignore
2078
+ node.name.name = flatNode
2079
+ res.flattened++
2080
+ if (closingElement) {
2081
+ // @ts-ignore
2082
+ closingElement.name.name = flatNode
2083
+ }
2084
+ }
2085
+
2086
+ if (shouldPrintDebug) {
2087
+ // prettier-ignore
2088
+ logger.info([` ❊❊ inline props (${inlined.size}):`, shouldDeopt ? ' deopted' : '', hasSpread ? ' has spread' : '', staticConfig.neverFlatten ? 'neverFlatten' : ''].join(' '))
2089
+ logger.info(` - attrs (end):\n ${logLines(attrs.map(attrStr).join(', '))}`)
2090
+ }
2091
+
2092
+ onExtractTag({
2093
+ parserProps: propsWithFileInfo,
2094
+ attrs,
2095
+ node,
2096
+ lineNumbers,
2097
+ filePath,
2098
+ attemptEval,
2099
+ jsxPath: traversePath,
2100
+ originalNodeName,
2101
+ isFlattened: shouldFlatten,
2102
+ programPath: programPath!,
2103
+ completeProps,
2104
+ staticConfig,
2105
+ })
2106
+ } finally {
2107
+ if (debugPropValue) {
2108
+ shouldPrintDebug = ogDebug
2109
+ }
2110
+ }
2111
+ },
2112
+ })
2113
+
2114
+ tm.mark('jsx-done', !!shouldPrintDebug)
2115
+
2116
+ /**
2117
+ * Step 3: Remove dead code from removed media query / theme hooks
2118
+ */
2119
+ if (modifiedComponents.size) {
2120
+ const all = Array.from(modifiedComponents)
2121
+ if (shouldPrintDebug) {
2122
+ logger.info(` [🪝] hook check ${all.length}`)
2123
+ }
2124
+ for (const comp of all) {
2125
+ removeUnusedHooks(comp, shouldPrintDebug)
2126
+ }
2127
+ }
2128
+
2129
+ tm.done(shouldPrintDebug === 'verbose')
2130
+
2131
+ return res
2132
+ }
2133
+ }