@tamagui/static 1.0.1-beta.18 → 1.0.1-beta.180

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