@tamagui/static 1.0.1-beta.19 → 1.0.1-beta.191

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