@tamagui/static 1.0.1-beta.20 → 1.0.1-beta.201

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