@hanzogui/static 2.0.0-rc.41-hanzoai.5

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 (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/dist/check-dep-versions.cjs +372 -0
  4. package/dist/checkDeps.cjs +271 -0
  5. package/dist/constants.cjs +51 -0
  6. package/dist/exports.cjs +46 -0
  7. package/dist/extractor/accessSafe.cjs +48 -0
  8. package/dist/extractor/babelParse.cjs +56 -0
  9. package/dist/extractor/buildClassName.cjs +86 -0
  10. package/dist/extractor/bundle.cjs +224 -0
  11. package/dist/extractor/bundleConfig.cjs +716 -0
  12. package/dist/extractor/concatClassName.cjs +92 -0
  13. package/dist/extractor/createEvaluator.cjs +88 -0
  14. package/dist/extractor/createExtractor.cjs +1827 -0
  15. package/dist/extractor/createLogger.cjs +50 -0
  16. package/dist/extractor/detectModuleFormat.cjs +55 -0
  17. package/dist/extractor/ensureImportingConcat.cjs +54 -0
  18. package/dist/extractor/errors.cjs +28 -0
  19. package/dist/extractor/esbuildAliasPlugin.cjs +56 -0
  20. package/dist/extractor/esbuildTsconfigPaths.cjs +108 -0
  21. package/dist/extractor/evaluateAstNode.cjs +128 -0
  22. package/dist/extractor/extractHelpers.cjs +182 -0
  23. package/dist/extractor/extractMediaStyle.cjs +169 -0
  24. package/dist/extractor/extractToClassNames.cjs +427 -0
  25. package/dist/extractor/extractToNative.cjs +361 -0
  26. package/dist/extractor/findTopmostFunction.cjs +43 -0
  27. package/dist/extractor/generatedUid.cjs +58 -0
  28. package/dist/extractor/getHanzoguiConfigPathFromOptionsConfig.cjs +41 -0
  29. package/dist/extractor/getPrefixLogs.cjs +31 -0
  30. package/dist/extractor/getPropValueFromAttributes.cjs +85 -0
  31. package/dist/extractor/getSourceModule.cjs +100 -0
  32. package/dist/extractor/getStaticBindingsForScope.cjs +213 -0
  33. package/dist/extractor/hasTopLevelAwait.cjs +62 -0
  34. package/dist/extractor/hoistClassNames.cjs +76 -0
  35. package/dist/extractor/literalToAst.cjs +109 -0
  36. package/dist/extractor/loadFile.cjs +17 -0
  37. package/dist/extractor/loadHanzogui.cjs +367 -0
  38. package/dist/extractor/logLines.cjs +43 -0
  39. package/dist/extractor/normalizeTernaries.cjs +95 -0
  40. package/dist/extractor/propsToFontFamilyCache.cjs +42 -0
  41. package/dist/extractor/regenerateConfig.cjs +178 -0
  42. package/dist/extractor/removeUnusedHooks.cjs +108 -0
  43. package/dist/extractor/timer.cjs +52 -0
  44. package/dist/extractor/validHTMLAttributes.cjs +79 -0
  45. package/dist/extractor/watchHanzoguiConfig.cjs +70 -0
  46. package/dist/getPragmaOptions.cjs +73 -0
  47. package/dist/helpers/memoize.cjs +45 -0
  48. package/dist/helpers/requireHanzoguiCore.cjs +40 -0
  49. package/dist/index.cjs +42 -0
  50. package/dist/registerRequire.cjs +208 -0
  51. package/dist/server.cjs +82 -0
  52. package/dist/setup.cjs +0 -0
  53. package/dist/types.cjs +18 -0
  54. package/dist/worker.cjs +111 -0
  55. package/package.json +101 -0
  56. package/src/check-dep-versions.ts +738 -0
  57. package/src/checkDeps.ts +371 -0
  58. package/src/constants.ts +14 -0
  59. package/src/exports.ts +15 -0
  60. package/src/extractor/accessSafe.ts +22 -0
  61. package/src/extractor/babelParse.ts +38 -0
  62. package/src/extractor/buildClassName.ts +76 -0
  63. package/src/extractor/bundle.ts +277 -0
  64. package/src/extractor/bundleConfig.ts +958 -0
  65. package/src/extractor/concatClassName.ts +105 -0
  66. package/src/extractor/createEvaluator.ts +82 -0
  67. package/src/extractor/createExtractor.ts +2680 -0
  68. package/src/extractor/createLogger.ts +41 -0
  69. package/src/extractor/detectModuleFormat.ts +42 -0
  70. package/src/extractor/ensureImportingConcat.ts +36 -0
  71. package/src/extractor/errors.ts +1 -0
  72. package/src/extractor/esbuildAliasPlugin.ts +40 -0
  73. package/src/extractor/esbuildTsconfigPaths.ts +103 -0
  74. package/src/extractor/evaluateAstNode.ts +136 -0
  75. package/src/extractor/extractHelpers.ts +211 -0
  76. package/src/extractor/extractMediaStyle.ts +194 -0
  77. package/src/extractor/extractToClassNames.ts +631 -0
  78. package/src/extractor/extractToNative.ts +489 -0
  79. package/src/extractor/findTopmostFunction.ts +24 -0
  80. package/src/extractor/generatedUid.ts +41 -0
  81. package/src/extractor/getHanzoguiConfigPathFromOptionsConfig.ts +24 -0
  82. package/src/extractor/getPrefixLogs.ts +9 -0
  83. package/src/extractor/getPropValueFromAttributes.ts +95 -0
  84. package/src/extractor/getSourceModule.ts +96 -0
  85. package/src/extractor/getStaticBindingsForScope.ts +237 -0
  86. package/src/extractor/hasTopLevelAwait.ts +28 -0
  87. package/src/extractor/hoistClassNames.ts +52 -0
  88. package/src/extractor/literalToAst.ts +85 -0
  89. package/src/extractor/loadFile.ts +17 -0
  90. package/src/extractor/loadHanzogui.ts +487 -0
  91. package/src/extractor/logLines.ts +16 -0
  92. package/src/extractor/normalizeTernaries.ts +75 -0
  93. package/src/extractor/propsToFontFamilyCache.ts +18 -0
  94. package/src/extractor/regenerateConfig.ts +186 -0
  95. package/src/extractor/removeUnusedHooks.ts +83 -0
  96. package/src/extractor/timer.ts +25 -0
  97. package/src/extractor/validHTMLAttributes.ts +52 -0
  98. package/src/extractor/watchHanzoguiConfig.ts +57 -0
  99. package/src/getPragmaOptions.ts +57 -0
  100. package/src/helpers/memoize.ts +24 -0
  101. package/src/helpers/requireHanzoguiCore.ts +28 -0
  102. package/src/index.ts +4 -0
  103. package/src/registerRequire.ts +283 -0
  104. package/src/server.ts +43 -0
  105. package/src/setup.ts +0 -0
  106. package/src/types.ts +109 -0
  107. package/src/worker.ts +142 -0
  108. package/types/check-dep-versions.d.ts +37 -0
  109. package/types/check-dep-versions.d.ts.map +1 -0
  110. package/types/checkDeps.d.ts +19 -0
  111. package/types/checkDeps.d.ts.map +1 -0
  112. package/types/constants.d.ts +6 -0
  113. package/types/constants.d.ts.map +1 -0
  114. package/types/exports.d.ts +16 -0
  115. package/types/exports.d.ts.map +1 -0
  116. package/types/extractor/accessSafe.d.ts +3 -0
  117. package/types/extractor/accessSafe.d.ts.map +1 -0
  118. package/types/extractor/babelParse.d.ts +5 -0
  119. package/types/extractor/babelParse.d.ts.map +1 -0
  120. package/types/extractor/buildClassName.d.ts +7 -0
  121. package/types/extractor/buildClassName.d.ts.map +1 -0
  122. package/types/extractor/bundle.d.ts +121 -0
  123. package/types/extractor/bundle.d.ts.map +1 -0
  124. package/types/extractor/bundleConfig.d.ts +49 -0
  125. package/types/extractor/bundleConfig.d.ts.map +1 -0
  126. package/types/extractor/concatClassName.d.ts +8 -0
  127. package/types/extractor/concatClassName.d.ts.map +1 -0
  128. package/types/extractor/createEvaluator.d.ts +12 -0
  129. package/types/extractor/createEvaluator.d.ts.map +1 -0
  130. package/types/extractor/createExtractor.d.ts +32 -0
  131. package/types/extractor/createExtractor.d.ts.map +1 -0
  132. package/types/extractor/createLogger.d.ts +3 -0
  133. package/types/extractor/createLogger.d.ts.map +1 -0
  134. package/types/extractor/detectModuleFormat.d.ts +5 -0
  135. package/types/extractor/detectModuleFormat.d.ts.map +1 -0
  136. package/types/extractor/ensureImportingConcat.d.ts +4 -0
  137. package/types/extractor/ensureImportingConcat.d.ts.map +1 -0
  138. package/types/extractor/errors.d.ts +3 -0
  139. package/types/extractor/errors.d.ts.map +1 -0
  140. package/types/extractor/esbuildAliasPlugin.d.ts +18 -0
  141. package/types/extractor/esbuildAliasPlugin.d.ts.map +1 -0
  142. package/types/extractor/esbuildTsconfigPaths.d.ts +11 -0
  143. package/types/extractor/esbuildTsconfigPaths.d.ts.map +1 -0
  144. package/types/extractor/evaluateAstNode.d.ts +3 -0
  145. package/types/extractor/evaluateAstNode.d.ts.map +1 -0
  146. package/types/extractor/extractHelpers.d.ts +28 -0
  147. package/types/extractor/extractHelpers.d.ts.map +1 -0
  148. package/types/extractor/extractMediaStyle.d.ts +11 -0
  149. package/types/extractor/extractMediaStyle.d.ts.map +1 -0
  150. package/types/extractor/extractToClassNames.d.ts +25 -0
  151. package/types/extractor/extractToClassNames.d.ts.map +1 -0
  152. package/types/extractor/extractToNative.d.ts +13 -0
  153. package/types/extractor/extractToNative.d.ts.map +1 -0
  154. package/types/extractor/findTopmostFunction.d.ts +4 -0
  155. package/types/extractor/findTopmostFunction.d.ts.map +1 -0
  156. package/types/extractor/generatedUid.d.ts +5 -0
  157. package/types/extractor/generatedUid.d.ts.map +1 -0
  158. package/types/extractor/getHanzoguiConfigPathFromOptionsConfig.d.ts +3 -0
  159. package/types/extractor/getHanzoguiConfigPathFromOptionsConfig.d.ts.map +1 -0
  160. package/types/extractor/getPrefixLogs.d.ts +3 -0
  161. package/types/extractor/getPrefixLogs.d.ts.map +1 -0
  162. package/types/extractor/getPropValueFromAttributes.d.ts +19 -0
  163. package/types/extractor/getPropValueFromAttributes.d.ts.map +1 -0
  164. package/types/extractor/getSourceModule.d.ts +16 -0
  165. package/types/extractor/getSourceModule.d.ts.map +1 -0
  166. package/types/extractor/getStaticBindingsForScope.d.ts +5 -0
  167. package/types/extractor/getStaticBindingsForScope.d.ts.map +1 -0
  168. package/types/extractor/hasTopLevelAwait.d.ts +2 -0
  169. package/types/extractor/hasTopLevelAwait.d.ts.map +1 -0
  170. package/types/extractor/hoistClassNames.d.ts +6 -0
  171. package/types/extractor/hoistClassNames.d.ts.map +1 -0
  172. package/types/extractor/literalToAst.d.ts +4 -0
  173. package/types/extractor/literalToAst.d.ts.map +1 -0
  174. package/types/extractor/loadFile.d.ts +1 -0
  175. package/types/extractor/loadFile.d.ts.map +1 -0
  176. package/types/extractor/loadHanzogui.d.ts +22 -0
  177. package/types/extractor/loadHanzogui.d.ts.map +1 -0
  178. package/types/extractor/logLines.d.ts +2 -0
  179. package/types/extractor/logLines.d.ts.map +1 -0
  180. package/types/extractor/normalizeTernaries.d.ts +3 -0
  181. package/types/extractor/normalizeTernaries.d.ts.map +1 -0
  182. package/types/extractor/propsToFontFamilyCache.d.ts +4 -0
  183. package/types/extractor/propsToFontFamilyCache.d.ts.map +1 -0
  184. package/types/extractor/regenerateConfig.d.ts +9 -0
  185. package/types/extractor/regenerateConfig.d.ts.map +1 -0
  186. package/types/extractor/removeUnusedHooks.d.ts +3 -0
  187. package/types/extractor/removeUnusedHooks.d.ts.map +1 -0
  188. package/types/extractor/timer.d.ts +5 -0
  189. package/types/extractor/timer.d.ts.map +1 -0
  190. package/types/extractor/validHTMLAttributes.d.ts +52 -0
  191. package/types/extractor/validHTMLAttributes.d.ts.map +1 -0
  192. package/types/extractor/watchHanzoguiConfig.d.ts +5 -0
  193. package/types/extractor/watchHanzoguiConfig.d.ts.map +1 -0
  194. package/types/getPragmaOptions.d.ts +8 -0
  195. package/types/getPragmaOptions.d.ts.map +1 -0
  196. package/types/helpers/memoize.d.ts +8 -0
  197. package/types/helpers/memoize.d.ts.map +1 -0
  198. package/types/helpers/requireHanzoguiCore.d.ts +3 -0
  199. package/types/helpers/requireHanzoguiCore.d.ts.map +1 -0
  200. package/types/index.d.ts +4 -0
  201. package/types/index.d.ts.map +1 -0
  202. package/types/registerRequire.d.ts +10 -0
  203. package/types/registerRequire.d.ts.map +1 -0
  204. package/types/server.d.ts +3 -0
  205. package/types/server.d.ts.map +1 -0
  206. package/types/setup.d.ts +1 -0
  207. package/types/setup.d.ts.map +1 -0
  208. package/types/types.d.ts +92 -0
  209. package/types/types.d.ts.map +1 -0
  210. package/types/worker.d.ts +46 -0
  211. package/types/worker.d.ts.map +1 -0
@@ -0,0 +1,2680 @@
1
+ import type { NodePath, TraverseOptions } from '@babel/traverse'
2
+ import traverse from '@babel/traverse'
3
+ import * as t from '@babel/types'
4
+ import { Color, colorLog } from '@hanzogui/cli-color'
5
+ import * as reactNativeWebInternals from '@hanzogui/react-native-web-internals'
6
+ import {
7
+ StyleObjectIdentifier,
8
+ StyleObjectRules,
9
+ type GetStyleState,
10
+ type PseudoStyles,
11
+ type SplitStyleProps,
12
+ type StaticConfig,
13
+ type HanzoguiComponentState,
14
+ } from '@hanzogui/web'
15
+ import { existsSync, readFileSync } from 'node:fs'
16
+ import { basename, dirname, resolve, relative } from 'node:path'
17
+ import { nodeModuleNameResolver, sys } from 'typescript'
18
+ import type { ViewStyle } from 'react-native'
19
+
20
+ import { FAILED_EVAL } from '../constants'
21
+ import { requireHanzoguiCore } from '../helpers/requireHanzoguiCore'
22
+ import type {
23
+ ExtractedAttr,
24
+ ExtractedAttrStyle,
25
+ ExtractorOptions,
26
+ ExtractorParseProps,
27
+ HanzoguiOptions,
28
+ HanzoguiOptionsWithFileInfo,
29
+ Ternary,
30
+ } from '../types'
31
+ import type { LoadedComponents, HanzoguiProjectInfo } from './bundleConfig'
32
+ import { createEvaluator, createSafeEvaluator } from './createEvaluator'
33
+ import { evaluateAstNode } from './evaluateAstNode'
34
+ import {
35
+ attrStr,
36
+ findComponentName,
37
+ getValidComponent,
38
+ getValidComponentsPaths,
39
+ getValidImport,
40
+ isPresent,
41
+ isValidImport,
42
+ objToStr,
43
+ } from './extractHelpers'
44
+ import { findTopmostFunction } from './findTopmostFunction'
45
+ import { cleanupBeforeExit, getStaticBindingsForScope } from './getStaticBindingsForScope'
46
+ import { literalToAst } from './literalToAst'
47
+ import { loadHanzogui, loadHanzoguiSync } from './loadHanzogui'
48
+ import { logLines } from './logLines'
49
+ import { normalizeTernaries } from './normalizeTernaries'
50
+ import { setPropsToFontFamily } from './propsToFontFamilyCache'
51
+ import { timer } from './timer'
52
+ import { validHTMLAttributes } from './validHTMLAttributes'
53
+ import { BailOptimizationError } from './errors'
54
+ import { loadCompilerOptionsFromTsconfig } from './esbuildTsconfigPaths'
55
+
56
+ const UNTOUCHED_PROPS = {
57
+ key: true,
58
+ style: true,
59
+ className: true,
60
+ }
61
+
62
+ // Platform variants that can't be resolved at compile time on native builds.
63
+ // Defined at module level (not inside the loop) to avoid repeated Set allocations during compilation.
64
+ // (requires runtime Platform.OS + Platform.isTV checks via react-native-tvos)
65
+ const nativeOnlyPlatforms = new Set(['android', 'ios', 'tv', 'androidtv', 'tvos'])
66
+
67
+ const createTernary = (x: Ternary) => x
68
+
69
+ export type Extractor = ReturnType<typeof createExtractor>
70
+
71
+ type FileOrPath = NodePath<t.Program> | t.File
72
+
73
+ let hasLoggedBaseInfo = false
74
+
75
+ function isFullyDisabled(props: HanzoguiOptions) {
76
+ return props.disableExtraction && props.disableDebugAttr
77
+ }
78
+
79
+ export function createExtractor(
80
+ { logger = console, platform = 'web' }: ExtractorOptions = { logger: console }
81
+ ) {
82
+ const INLINE_EXTRACTABLE = {
83
+ ref: 'ref',
84
+ key: 'key',
85
+ ...(platform === 'web' && {
86
+ onPress: 'onClick',
87
+ onHoverIn: 'onMouseEnter',
88
+ onHoverOut: 'onMouseLeave',
89
+ onPressIn: 'onMouseDown',
90
+ onPressOut: 'onMouseUp',
91
+ }),
92
+ ...(platform === 'native' && {
93
+ // native view props that should pass through without preventing flattening
94
+ testID: 'testID',
95
+ nativeID: 'nativeID',
96
+ accessibilityLabel: 'accessibilityLabel',
97
+ accessibilityHint: 'accessibilityHint',
98
+ accessibilityRole: 'accessibilityRole',
99
+ accessibilityState: 'accessibilityState',
100
+ accessibilityValue: 'accessibilityValue',
101
+ accessibilityActions: 'accessibilityActions',
102
+ accessibilityLabelledBy: 'accessibilityLabelledBy',
103
+ accessibilityLiveRegion: 'accessibilityLiveRegion',
104
+ accessibilityElementsHidden: 'accessibilityElementsHidden',
105
+ accessibilityViewIsModal: 'accessibilityViewIsModal',
106
+ importantForAccessibility: 'importantForAccessibility',
107
+ collapsable: 'collapsable',
108
+ needsOffscreenAlphaCompositing: 'needsOffscreenAlphaCompositing',
109
+ removeClippedSubviews: 'removeClippedSubviews',
110
+ renderToHardwareTextureAndroid: 'renderToHardwareTextureAndroid',
111
+ shouldRasterizeIOS: 'shouldRasterizeIOS',
112
+ hitSlop: 'hitSlop',
113
+ pointerEvents: 'pointerEvents',
114
+ }),
115
+ }
116
+
117
+ const componentState: HanzoguiComponentState = {
118
+ focus: false,
119
+ focusVisible: false,
120
+ focusWithin: false,
121
+ hover: false,
122
+ unmounted: true,
123
+ press: false,
124
+ pressIn: false,
125
+ disabled: false,
126
+ } as const
127
+
128
+ const styleProps: SplitStyleProps = {
129
+ resolveValues: platform === 'native' ? 'value' : 'variable',
130
+ noClass: false,
131
+ isAnimated: false,
132
+ }
133
+
134
+ const shouldAddDebugProp =
135
+ // really basic disable this for next.js because it messes with ssr
136
+ !process.env.npm_package_dependencies_next &&
137
+ platform !== 'native' &&
138
+ process.env.IDENTIFY_TAGS !== 'false' &&
139
+ (process.env.NODE_ENV === 'development' || process.env.IDENTIFY_TAGS)
140
+
141
+ let projectInfo: HanzoguiProjectInfo | null = null
142
+
143
+ // cache of dynamically discovered styled components, keyed by absolute file path
144
+ // persists across files within the same worker/extractor instance
145
+ const dynamicComponentCache = new Map<string, LoadedComponents>()
146
+ const dynamicLoadingInProgress = new Set<string>()
147
+
148
+ // lazily loaded tsconfig compiler options for path alias resolution
149
+ let _compilerOptions: any = null
150
+ function getCompilerOptions() {
151
+ if (!_compilerOptions) {
152
+ try {
153
+ _compilerOptions = loadCompilerOptionsFromTsconfig()
154
+ } catch {
155
+ _compilerOptions = {}
156
+ }
157
+ }
158
+ return _compilerOptions
159
+ }
160
+
161
+ function resolveImportPath(fromFile: string, importPath: string): string | null {
162
+ if (importPath.startsWith('.')) {
163
+ // relative path resolution
164
+ const dir = dirname(fromFile)
165
+ const base = resolve(dir, importPath)
166
+ const extensions = ['.tsx', '.ts', '.jsx', '.js']
167
+ for (const ext of extensions) {
168
+ const full = base + ext
169
+ if (existsSync(full)) return full
170
+ }
171
+ // try index files
172
+ for (const ext of extensions) {
173
+ const full = resolve(base, `index${ext}`)
174
+ if (existsSync(full)) return full
175
+ }
176
+ return null
177
+ }
178
+
179
+ // tsconfig path alias resolution (e.g. ~/foo, @/bar)
180
+ const compilerOptions = getCompilerOptions()
181
+ if (compilerOptions.paths) {
182
+ try {
183
+ const { resolvedModule } = nodeModuleNameResolver(
184
+ importPath,
185
+ fromFile,
186
+ compilerOptions,
187
+ sys
188
+ )
189
+ if (
190
+ resolvedModule &&
191
+ !resolvedModule.resolvedFileName.endsWith('.d.ts') &&
192
+ !resolvedModule.isExternalLibraryImport
193
+ ) {
194
+ return resolvedModule.resolvedFileName
195
+ }
196
+ } catch {
197
+ // fallback - tsconfig resolution failed
198
+ }
199
+ }
200
+
201
+ return null
202
+ }
203
+
204
+ const styledCheckCache = new Map<string, boolean>()
205
+
206
+ function mightHaveStyledComponents(filePath: string): boolean {
207
+ const cached = styledCheckCache.get(filePath)
208
+ if (cached !== undefined) return cached
209
+ try {
210
+ const content = readFileSync(filePath, 'utf-8')
211
+ const result = content.includes('styled(')
212
+ styledCheckCache.set(filePath, result)
213
+ return result
214
+ } catch {
215
+ styledCheckCache.set(filePath, false)
216
+ return false
217
+ }
218
+ }
219
+
220
+ // we load hanzogui delayed because we need to set some global/env stuff before importing
221
+ // otherwise we'd import `rnw` and cause it to evaluate react-native-web which causes errors
222
+
223
+ function loadSync(props: HanzoguiOptions) {
224
+ if (isFullyDisabled(props)) {
225
+ return null
226
+ }
227
+ return (projectInfo ||= loadHanzoguiSync(props))
228
+ }
229
+
230
+ async function load(props: HanzoguiOptions) {
231
+ if (isFullyDisabled(props)) {
232
+ return null
233
+ }
234
+ return (projectInfo ||= await loadHanzogui(props))
235
+ }
236
+
237
+ return {
238
+ options: {
239
+ logger,
240
+ },
241
+ cleanupBeforeExit,
242
+ loadHanzogui: load,
243
+ loadHanzoguiSync: loadSync,
244
+ getHanzogui() {
245
+ return projectInfo?.hanzoguiConfig
246
+ },
247
+ parseSync: (f: FileOrPath, props: ExtractorParseProps) => {
248
+ globalThis.expo ||= {} // expo-modules-core checks this and avoids loading "native" modules if exists
249
+ const projectInfo = loadSync(props)
250
+ return parseWithConfig(projectInfo || {}, f, props)
251
+ },
252
+ parse: async (f: FileOrPath, props: ExtractorParseProps) => {
253
+ globalThis.expo ||= {} // expo-modules-core checks this and avoids loading "native" modules if exists
254
+ const projectInfo = await load(props)
255
+ return parseWithConfig(projectInfo || {}, f, props)
256
+ },
257
+ }
258
+
259
+ function parseWithConfig(
260
+ { components, hanzoguiConfig }: HanzoguiProjectInfo,
261
+ fileOrPath: FileOrPath,
262
+ options: ExtractorParseProps
263
+ ) {
264
+ const {
265
+ config = 'hanzogui.config.ts',
266
+ importsWhitelist = ['constants.js'],
267
+ evaluateVars = true,
268
+ sourcePath = '',
269
+ onExtractTag,
270
+ onStyledDefinitionRule,
271
+ getFlattenedNode,
272
+ disable,
273
+ disableExtraction,
274
+ disableExtractVariables,
275
+ disableDebugAttr,
276
+ enableDynamicEvaluation = false,
277
+ includeExtensions = ['.ts', '.tsx', '.jsx'],
278
+ extractStyledDefinitions = false,
279
+ prefixLogs,
280
+ excludeProps,
281
+ platform,
282
+ ...restProps
283
+ } = options
284
+
285
+ // invalidate dynamic cache for this file on re-parse (HMR)
286
+ if (sourcePath && dynamicComponentCache.has(sourcePath)) {
287
+ dynamicComponentCache.delete(sourcePath)
288
+ styledCheckCache.delete(sourcePath)
289
+ }
290
+
291
+ if (sourcePath.includes('.hanzogui-dynamic-eval')) {
292
+ return null
293
+ }
294
+
295
+ const {
296
+ normalizeStyle,
297
+ getSplitStyles,
298
+ mediaQueryConfig,
299
+ propMapper,
300
+ proxyThemeVariables,
301
+ getDefaultProps,
302
+ pseudoDescriptors,
303
+ } = requireHanzoguiCore(platform)
304
+
305
+ let shouldPrintDebug = options.shouldPrintDebug || false
306
+
307
+ if (disable === true || (Array.isArray(disable) && disable.includes(sourcePath))) {
308
+ return null
309
+ }
310
+
311
+ if (!isFullyDisabled(options)) {
312
+ if (!components) {
313
+ throw new Error(`Must provide components`)
314
+ }
315
+ }
316
+
317
+ if (
318
+ sourcePath &&
319
+ includeExtensions &&
320
+ !includeExtensions.some((ext) => sourcePath.endsWith(ext))
321
+ ) {
322
+ if (shouldPrintDebug) {
323
+ logger.info(
324
+ `Ignoring file due to includeExtensions: ${sourcePath}, includeExtensions: ${includeExtensions.join(
325
+ ', '
326
+ )}`
327
+ )
328
+ }
329
+ return null
330
+ }
331
+
332
+ function isValidStyleKey(name: string, staticConfig: StaticConfig) {
333
+ if (!projectInfo) {
334
+ throw new Error(`Hanzogui extractor not loaded yet`)
335
+ }
336
+ if (platform === 'native' && name[0] === '$' && mediaQueryConfig[name.slice(1)]) {
337
+ return false
338
+ }
339
+ // Check for $theme-, $platform-, $group- prefixed keys
340
+ if (name[0] === '$') {
341
+ const mediaName = name.slice(1)
342
+ if (
343
+ mediaName.startsWith('theme-') ||
344
+ mediaName.startsWith('platform-') ||
345
+ mediaName.startsWith('group-')
346
+ ) {
347
+ return true
348
+ }
349
+ if (mediaQueryConfig[mediaName]) {
350
+ return true
351
+ }
352
+ }
353
+ return !!(
354
+ staticConfig.validStyles?.[name] ||
355
+ pseudoDescriptors[name] ||
356
+ // don't disable variants or else you lose many things flattening
357
+ staticConfig.variants?.[name] ||
358
+ projectInfo?.hanzoguiConfig?.shorthands[name]
359
+ )
360
+ }
361
+
362
+ /**
363
+ * Step 1: Determine if importing any statically extractable components
364
+ */
365
+
366
+ const isTargetingHTML = platform === 'web'
367
+ const ogDebug = shouldPrintDebug
368
+ const tm = timer()
369
+ const propsWithFileInfo: HanzoguiOptionsWithFileInfo = {
370
+ ...options,
371
+ sourcePath,
372
+ allLoadedComponents: components ? [...components] : [],
373
+ }
374
+
375
+ if (!hasLoggedBaseInfo) {
376
+ hasLoggedBaseInfo = true
377
+ if (shouldPrintDebug) {
378
+ logger.info(
379
+ [
380
+ 'loaded components:',
381
+ propsWithFileInfo.allLoadedComponents
382
+ .map((comp) => Object.keys(comp.nameToInfo).join(', '))
383
+ .join(', '),
384
+ ].join(' ')
385
+ )
386
+ }
387
+ if (process.env.DEBUG?.startsWith('hanzogui')) {
388
+ logger.info(
389
+ [
390
+ 'loaded:',
391
+ propsWithFileInfo.allLoadedComponents.map((x) => x.moduleName),
392
+ ].join('\n')
393
+ )
394
+ }
395
+ }
396
+
397
+ tm.mark('load-hanzogui', !!shouldPrintDebug)
398
+
399
+ if (!isFullyDisabled(options)) {
400
+ if (!hanzoguiConfig?.themes) {
401
+ console.error(
402
+ `⛔️ Error: Missing "themes" in your hanzogui.config file:
403
+
404
+ You may not need the compiler! Remember you can run Hanzogui with no configuration at all.
405
+
406
+ You may have not "export default" your config (you can also "export const config").
407
+
408
+ Or this may be due to duplicated dependency versions:
409
+ - try out https://github.com/bmish/check-dependency-version-consistency to see if there are mis-matches.
410
+ - or search your lockfile for mis-matches.
411
+ `
412
+ )
413
+ console.info(` Got config:`, hanzoguiConfig)
414
+ process.exit(0)
415
+ }
416
+ }
417
+
418
+ const firstThemeName = Object.keys(hanzoguiConfig?.themes || {})[0]
419
+ const firstTheme = hanzoguiConfig?.themes[firstThemeName] || {}
420
+
421
+ if (!firstTheme || typeof firstTheme !== 'object') {
422
+ const err = `Missing theme ${firstThemeName}, an error occurred when importing your config`
423
+ console.info(err, `Got config:`, hanzoguiConfig)
424
+ console.info(`Looking for theme:`, firstThemeName)
425
+ throw new Error(err)
426
+ }
427
+
428
+ const proxiedTheme = proxyThemeVariables(firstTheme)
429
+ type AccessListener = (key: string) => void
430
+ const themeAccessListeners = new Set<AccessListener>()
431
+ const defaultTheme = new Proxy(proxiedTheme, {
432
+ get(target, key) {
433
+ if (Reflect.has(target, key)) {
434
+ themeAccessListeners.forEach((cb) => cb(String(key)))
435
+ }
436
+ return Reflect.get(target, key)
437
+ },
438
+ })
439
+
440
+ const body: t.Statement[] | NodePath<t.Statement>[] =
441
+ fileOrPath.type === 'Program' ? fileOrPath.get('body') : fileOrPath.program.body
442
+
443
+ if (!isFullyDisabled(options)) {
444
+ if (Object.keys(components || []).length === 0) {
445
+ console.warn(
446
+ `Warning: Hanzogui didn't find any valid components (DEBUG=hanzogui for more)`
447
+ )
448
+ if (process.env.DEBUG === 'hanzogui') {
449
+ console.info(`components`, Object.keys(components || []), components)
450
+ }
451
+ }
452
+ }
453
+
454
+ if (shouldPrintDebug === 'verbose') {
455
+ logger.info(
456
+ `allLoadedComponent modules ${propsWithFileInfo.allLoadedComponents
457
+ .map((k) => k.moduleName)
458
+ .join(', ')}`
459
+ )
460
+ logger.info(
461
+ `valid import paths: ${JSON.stringify(
462
+ getValidComponentsPaths(propsWithFileInfo)
463
+ )}`
464
+ )
465
+ }
466
+
467
+ let doesUseValidImport = false
468
+ let hasImportedTheme = false
469
+
470
+ const importDeclarations: t.ImportDeclaration[] = []
471
+
472
+ for (const bodyPath of body) {
473
+ if (bodyPath.type !== 'ImportDeclaration') continue
474
+ const node = ('node' in bodyPath ? bodyPath.node : bodyPath) as t.ImportDeclaration
475
+ const moduleName = node.source.value
476
+
477
+ // if importing valid module
478
+ const valid = isValidImport(propsWithFileInfo, moduleName)
479
+
480
+ if (valid) {
481
+ importDeclarations.push(node)
482
+ }
483
+
484
+ if (shouldPrintDebug === 'verbose') {
485
+ logger.info(` - import via ${moduleName} ${valid}`)
486
+ }
487
+
488
+ if (extractStyledDefinitions && enableDynamicEvaluation) {
489
+ // check all imports for `styled`, not just valid packages
490
+ // styled( is basically guaranteed to be hanzogui regardless of source
491
+ if (node.specifiers.some((specifier) => specifier.local.name === 'styled')) {
492
+ doesUseValidImport = true
493
+ // don't break - need to collect all import declarations for the styled() handler
494
+ }
495
+ }
496
+
497
+ if (valid) {
498
+ const names = node.specifiers.map((specifier) => specifier.local.name)
499
+ const isValidComponent = names.some((name) =>
500
+ Boolean(isValidImport(propsWithFileInfo, moduleName, name))
501
+ )
502
+ if (shouldPrintDebug === 'verbose') {
503
+ logger.info(
504
+ ` - import ${isValidComponent ? '✅' : '⇣'} - ${names.join(
505
+ ', '
506
+ )} via package '${moduleName}' - (valid: ${JSON.stringify(
507
+ getValidComponentsPaths(propsWithFileInfo)
508
+ )})`
509
+ )
510
+ }
511
+ if (isValidComponent) {
512
+ doesUseValidImport = true
513
+ if (!(extractStyledDefinitions && enableDynamicEvaluation)) break
514
+ }
515
+ }
516
+ }
517
+
518
+ if (shouldPrintDebug) {
519
+ logger.info(
520
+ `${JSON.stringify({ doesUseValidImport, hasImportedTheme }, null, 2)}\n`
521
+ )
522
+ }
523
+
524
+ if (
525
+ !doesUseValidImport &&
526
+ extractStyledDefinitions &&
527
+ enableDynamicEvaluation &&
528
+ sourcePath
529
+ ) {
530
+ // check if any local import is in the dynamic cache or has styled components
531
+ for (const bodyPath of body) {
532
+ if (bodyPath.type !== 'ImportDeclaration') continue
533
+ const node = (
534
+ 'node' in bodyPath ? bodyPath.node : bodyPath
535
+ ) as t.ImportDeclaration
536
+ const moduleName = node.source.value
537
+
538
+ const resolved = resolveImportPath(sourcePath, moduleName)
539
+ if (!resolved) continue
540
+
541
+ if (dynamicComponentCache.has(resolved)) {
542
+ doesUseValidImport = true
543
+ break
544
+ }
545
+
546
+ if (mightHaveStyledComponents(resolved)) {
547
+ doesUseValidImport = true
548
+ break
549
+ }
550
+ }
551
+ }
552
+
553
+ if (!doesUseValidImport) {
554
+ return null
555
+ }
556
+
557
+ function getValidImportedComponent(componentName: string) {
558
+ const importDeclaration = importDeclarations.find((dec) =>
559
+ dec.specifiers.some((spec) => spec.local.name === componentName)
560
+ )
561
+ if (!importDeclaration) {
562
+ return null
563
+ }
564
+ return getValidImport(
565
+ propsWithFileInfo,
566
+ importDeclaration.source.value,
567
+ componentName
568
+ )
569
+ }
570
+
571
+ tm.mark('import-check', !!shouldPrintDebug)
572
+
573
+ let couldntParse = false
574
+ const modifiedComponents = new Set<NodePath<any>>()
575
+
576
+ // only keeping a cache around per-file, reset it if it changes
577
+ const bindingCache: Record<string, string | null> = {}
578
+
579
+ const callTraverse = (a: TraverseOptions<any>) => {
580
+ // @ts-ignore
581
+ return fileOrPath.type === 'File' ? traverse(fileOrPath, a) : fileOrPath.traverse(a)
582
+ }
583
+
584
+ const shouldDisableExtraction =
585
+ disableExtraction === true ||
586
+ (Array.isArray(disableExtraction) && disableExtraction.includes(sourcePath))
587
+
588
+ /**
589
+ * Step 2: Statically extract from JSX < /> nodes
590
+ */
591
+ let programPath: NodePath<t.Program> | null = null
592
+
593
+ const res = {
594
+ styled: 0,
595
+ flattened: 0,
596
+ optimized: 0,
597
+ modified: 0,
598
+ found: 0,
599
+ }
600
+
601
+ const version = `${Math.random()}`
602
+
603
+ callTraverse({
604
+ // @ts-ignore
605
+ Program: {
606
+ enter(path) {
607
+ programPath = path
608
+ },
609
+ },
610
+
611
+ // styled() calls
612
+ CallExpression(path) {
613
+ if (disable || shouldDisableExtraction || extractStyledDefinitions === false) {
614
+ return
615
+ }
616
+
617
+ if (!t.isIdentifier(path.node.callee) || path.node.callee.name !== 'styled') {
618
+ return
619
+ }
620
+
621
+ const variableName =
622
+ t.isVariableDeclarator(path.parent) && t.isIdentifier(path.parent.id)
623
+ ? path.parent.id.name
624
+ : 'unknown'
625
+
626
+ if (shouldPrintDebug) {
627
+ logger.info(` [styled] Found styled(${variableName})`)
628
+ }
629
+
630
+ const parentNode = path.node.arguments[0]
631
+
632
+ if (!t.isIdentifier(parentNode)) {
633
+ return
634
+ }
635
+ const parentName = parentNode.name
636
+ const definition = path.node.arguments[1]
637
+
638
+ if (!parentName || !definition || !t.isObjectExpression(definition)) {
639
+ return
640
+ }
641
+
642
+ // look up by parent first (e.g. View in `styled(View, {...})`), then by self
643
+ let Component =
644
+ getValidImportedComponent(parentName) || getValidImportedComponent(variableName)
645
+
646
+ if (!Component) {
647
+ if (!enableDynamicEvaluation) {
648
+ return
649
+ }
650
+
651
+ try {
652
+ if (shouldPrintDebug) {
653
+ logger.info(
654
+ `Unknown component: ${variableName} = styled(${parentName}) attempting dynamic load: ${sourcePath}`
655
+ )
656
+ }
657
+
658
+ const out = loadHanzoguiSync({
659
+ forceExports: true,
660
+ components: [sourcePath],
661
+ cacheKey: version,
662
+ })
663
+
664
+ if (!out?.components) {
665
+ if (shouldPrintDebug) {
666
+ logger.info(`Couldn't load, got ${out}`)
667
+ }
668
+ return
669
+ }
670
+
671
+ propsWithFileInfo.allLoadedComponents = [
672
+ ...propsWithFileInfo.allLoadedComponents,
673
+ ...out.components,
674
+ ]
675
+
676
+ Component = out.components.flatMap((x) => x.nameToInfo[variableName] ?? [])[0]
677
+
678
+ if (!out.cached) {
679
+ const foundNames = out.components
680
+ ?.map((x) => Object.keys(x.nameToInfo).join(', '))
681
+ .join(', ')
682
+ .trim()
683
+
684
+ if (foundNames) {
685
+ colorLog(
686
+ Color.FgYellow,
687
+ ` | Hanzogui found dynamic components: ${foundNames}`
688
+ )
689
+ }
690
+ }
691
+ } catch (err: any) {
692
+ if (shouldPrintDebug) {
693
+ logger.info(
694
+ `skip optimize styled(${variableName}), unable to pre-process (DEBUG=hanzogui for more)`
695
+ )
696
+ }
697
+ }
698
+ }
699
+
700
+ if (!Component) {
701
+ if (shouldPrintDebug) {
702
+ logger.info(` No component found`)
703
+ }
704
+
705
+ /**
706
+ * We could/should still extract CSS just limited to validStyleProps
707
+ */
708
+ return
709
+ }
710
+
711
+ const componentSkipProps = new Set([
712
+ ...(Component.staticConfig.inlineWhenUnflattened || []),
713
+ ...(Component.staticConfig.inlineProps || []),
714
+ // for now skip variants, will return to them
715
+ 'variants',
716
+ 'defaultVariants',
717
+ // skip fontFamily its basically a "variant", important for theme use to be value always
718
+ 'fontFamily',
719
+ 'name',
720
+ 'focusStyle',
721
+ 'focusVisibleStyle',
722
+ 'focusWithinStyle',
723
+ 'disabledStyle',
724
+ 'hoverStyle',
725
+ 'pressStyle',
726
+ ])
727
+
728
+ // for now dont parse variants, spreads, etc
729
+ const skipped = new Set<t.ObjectProperty | t.SpreadElement | t.ObjectMethod>()
730
+ const styles = {}
731
+ const staticDefaultProps = {}
732
+
733
+ // Generate scope object at this level
734
+ const staticNamespace = getStaticBindingsForScope(
735
+ path.scope,
736
+ importsWhitelist,
737
+ sourcePath,
738
+ bindingCache,
739
+ shouldPrintDebug
740
+ )
741
+
742
+ const attemptEval = !evaluateVars
743
+ ? evaluateAstNode
744
+ : createEvaluator({
745
+ props: propsWithFileInfo,
746
+ staticNamespace,
747
+ sourcePath,
748
+ shouldPrintDebug,
749
+ })
750
+ const attemptEvalSafe = createSafeEvaluator(attemptEval)
751
+
752
+ for (const property of definition.properties) {
753
+ if (
754
+ t.isObjectProperty(property) &&
755
+ (t.isIdentifier(property.key) || t.isStringLiteral(property.key))
756
+ ) {
757
+ const key = t.isIdentifier(property.key)
758
+ ? property.key.name
759
+ : property.key.value
760
+ const defaultPropValue = attemptEvalSafe(property.value)
761
+ if (defaultPropValue !== FAILED_EVAL) {
762
+ staticDefaultProps[key] = defaultPropValue
763
+ }
764
+ }
765
+
766
+ if (
767
+ !t.isObjectProperty(property) ||
768
+ !t.isIdentifier(property.key) ||
769
+ !isValidStyleKey(property.key.name, Component.staticConfig) ||
770
+ // TODO make pseudos and variants work
771
+ // skip pseudos
772
+ pseudoDescriptors[property.key.name] ||
773
+ // skip variants
774
+ Component.staticConfig.variants?.[property.key.name] ||
775
+ componentSkipProps.has(property.key.name)
776
+ ) {
777
+ skipped.add(property)
778
+ continue
779
+ }
780
+ // attempt eval
781
+ const out = attemptEvalSafe(property.value)
782
+ if (out === FAILED_EVAL) {
783
+ skipped.add(property)
784
+ } else {
785
+ styles[property.key.name] = out
786
+ }
787
+ }
788
+
789
+ const out = getSplitStyles(
790
+ styles,
791
+ Component.staticConfig,
792
+ defaultTheme,
793
+ '',
794
+ componentState,
795
+ styleProps,
796
+ undefined,
797
+ undefined,
798
+ undefined,
799
+ undefined,
800
+ false,
801
+ shouldPrintDebug
802
+ )!
803
+
804
+ const classNames = {
805
+ ...out.classNames,
806
+ }
807
+
808
+ // // add in the style object as classnames
809
+ // const atomics = getPropsAtomic(out.style)
810
+ // for (const atomic of atomics) {
811
+ // out.rulesToInsert = out.rulesToInsert || []
812
+ // out.rulesToInsert.push(atomic)
813
+ // classNames[atomic.property] = atomic.identifier
814
+ // }
815
+
816
+ if (shouldPrintDebug) {
817
+ logger.info(
818
+ [
819
+ `Extracted styled(${variableName})\n`,
820
+ JSON.stringify(styles, null, 2),
821
+ '\n classNames:',
822
+ JSON.stringify(classNames, null, 2),
823
+ '\n rulesToInsert:',
824
+ out.rulesToInsert,
825
+ ].join(' ')
826
+ )
827
+ }
828
+
829
+ // don't replace definition values with class name strings -
830
+ // the runtime needs real values for animations, context, and group styles.
831
+ // we only emit the CSS rules so they're available if the runtime uses classNames.
832
+
833
+ if (out.rulesToInsert) {
834
+ for (const key in out.rulesToInsert) {
835
+ const styleObject = out.rulesToInsert[key]
836
+ onStyledDefinitionRule?.(
837
+ styleObject[StyleObjectIdentifier],
838
+ styleObject[StyleObjectRules]
839
+ )
840
+ }
841
+ }
842
+
843
+ res.styled++
844
+
845
+ // register so JSX handler can find this component (same-file and cross-file)
846
+ if (extractStyledDefinitions && enableDynamicEvaluation && Component) {
847
+ const dynamicStaticConfig = {
848
+ ...Component.staticConfig,
849
+ defaultProps: {
850
+ ...Component.staticConfig.defaultProps,
851
+ ...staticDefaultProps,
852
+ },
853
+ }
854
+
855
+ // add to allLoadedComponents with '' so getValidComponent matches when moduleName is ''
856
+ // (same-file styled components have '' as moduleName in JSX handler)
857
+ propsWithFileInfo.allLoadedComponents.push({
858
+ moduleName: '',
859
+ nameToInfo: { [variableName]: { staticConfig: dynamicStaticConfig } },
860
+ })
861
+
862
+ // also cache by file path so other files importing from this path can find it
863
+ if (sourcePath) {
864
+ let existing = dynamicComponentCache.get(sourcePath)
865
+ if (!existing) {
866
+ existing = { moduleName: sourcePath, nameToInfo: {} }
867
+ dynamicComponentCache.set(sourcePath, existing)
868
+ }
869
+ existing.nameToInfo[variableName] = { staticConfig: dynamicStaticConfig }
870
+ }
871
+ }
872
+
873
+ if (shouldPrintDebug) {
874
+ logger.info(`Extracted styled(${variableName})`)
875
+ }
876
+ },
877
+
878
+ JSXElement(traversePath) {
879
+ tm.mark('jsx-element', !!shouldPrintDebug)
880
+
881
+ const node = traversePath.node.openingElement
882
+ const ogAttributes = node.attributes.map((attr) => ({ ...attr }))
883
+ const componentName = findComponentName(traversePath.scope)
884
+ const closingElement = traversePath.node.closingElement
885
+
886
+ // skip non-identifier opening elements (member expressions, etc.)
887
+ if (
888
+ (closingElement && t.isJSXMemberExpression(closingElement?.name)) ||
889
+ !t.isJSXIdentifier(node.name)
890
+ ) {
891
+ if (shouldPrintDebug) {
892
+ logger.info(` skip non-identifier element`)
893
+ }
894
+
895
+ return
896
+ }
897
+
898
+ // validate its a proper import from hanzogui (or internally inside hanzogui)
899
+ const binding = traversePath.scope.getBinding(node.name.name)
900
+ let moduleName = ''
901
+ let dynamicComponent: { staticConfig: any } | null = null
902
+
903
+ if (binding) {
904
+ if (t.isImportDeclaration(binding.path.parent)) {
905
+ moduleName = binding.path.parent.source.value
906
+ if (!isValidImport(propsWithFileInfo, moduleName, binding.identifier.name)) {
907
+ // fallback: try dynamic component cache for local imports (relative or tsconfig alias)
908
+ if (enableDynamicEvaluation && sourcePath) {
909
+ const resolved = resolveImportPath(sourcePath, moduleName)
910
+ if (resolved) {
911
+ // check cache first
912
+ const cached = dynamicComponentCache.get(resolved)
913
+ if (cached?.nameToInfo[binding.identifier.name]) {
914
+ dynamicComponent = cached.nameToInfo[binding.identifier.name]
915
+ } else if (
916
+ !dynamicLoadingInProgress.has(resolved) &&
917
+ mightHaveStyledComponents(resolved)
918
+ ) {
919
+ // proactively load the file
920
+ dynamicLoadingInProgress.add(resolved)
921
+ try {
922
+ const out = loadHanzoguiSync({
923
+ forceExports: true,
924
+ components: [resolved],
925
+ })
926
+ if (out?.components) {
927
+ for (const comp of out.components) {
928
+ // merge into cache
929
+ let existing = dynamicComponentCache.get(resolved)
930
+ if (!existing) {
931
+ existing = { moduleName: resolved, nameToInfo: {} }
932
+ dynamicComponentCache.set(resolved, existing)
933
+ }
934
+ Object.assign(existing.nameToInfo, comp.nameToInfo)
935
+ // also add to allLoadedComponents so getValidComponent works
936
+ propsWithFileInfo.allLoadedComponents.push({
937
+ moduleName: resolved,
938
+ nameToInfo: comp.nameToInfo,
939
+ })
940
+ }
941
+ const cachedNow = dynamicComponentCache.get(resolved)
942
+ if (cachedNow?.nameToInfo[binding.identifier.name]) {
943
+ dynamicComponent = cachedNow.nameToInfo[binding.identifier.name]
944
+ }
945
+ }
946
+ } catch (err) {
947
+ if (shouldPrintDebug) {
948
+ logger.info(` - Failed to dynamically load ${resolved}: ${err}`)
949
+ }
950
+ } finally {
951
+ dynamicLoadingInProgress.delete(resolved)
952
+ }
953
+ }
954
+ }
955
+ }
956
+
957
+ if (!dynamicComponent) {
958
+ if (shouldPrintDebug) {
959
+ logger.info(
960
+ ` - Binding in component ${componentName} not valid import: "${binding.identifier.name}" isn't in ${moduleName}\n`
961
+ )
962
+ }
963
+ return
964
+ }
965
+ }
966
+ }
967
+ }
968
+
969
+ const component =
970
+ dynamicComponent ||
971
+ getValidComponent(propsWithFileInfo, moduleName, node.name.name)
972
+ if (!component || !component.staticConfig) {
973
+ if (shouldPrintDebug) {
974
+ logger.info(`\n - No Hanzogui conf for: ${node.name.name}\n`)
975
+ }
976
+ return
977
+ }
978
+
979
+ const originalNodeName = node.name.name
980
+
981
+ // found a valid tag
982
+ res.found++
983
+
984
+ const filePath = `./${relative(process.cwd(), sourcePath)}`
985
+ const lineNumbers = node.loc
986
+ ? node.loc.start.line +
987
+ (node.loc.start.line !== node.loc.end.line ? `-${node.loc.end.line}` : '')
988
+ : ''
989
+
990
+ const codePosition = `${filePath}:${lineNumbers}`
991
+
992
+ // debug just one
993
+ const debugPropValue = node.attributes
994
+ .filter(
995
+ (n) =>
996
+ t.isJSXAttribute(n) && t.isJSXIdentifier(n.name) && n.name.name === 'debug'
997
+ )
998
+ // @ts-ignore
999
+ .map((n: t.JSXAttribute) => {
1000
+ if (n.value === null) return true
1001
+ if (t.isStringLiteral(n.value)) return n.value.value as 'verbose'
1002
+ return false
1003
+ })[0] as boolean | 'verbose' | undefined
1004
+
1005
+ if (debugPropValue) {
1006
+ shouldPrintDebug = debugPropValue
1007
+ }
1008
+
1009
+ if (shouldPrintDebug) {
1010
+ logger.info(
1011
+ `\x1b[33m\x1b[0m ` + `${componentName} | ${codePosition} -------------------`
1012
+ )
1013
+ // prettier-ignore
1014
+ logger.info(
1015
+ [
1016
+ '\x1b[1m',
1017
+ '\x1b[32m',
1018
+ `<${originalNodeName} />`,
1019
+ disableDebugAttr ? '' : '🐛',
1020
+ ].join(' ')
1021
+ )
1022
+ }
1023
+
1024
+ // add data-* debug attributes
1025
+ if (platform !== 'native') {
1026
+ if (shouldAddDebugProp && !disableDebugAttr) {
1027
+ res.modified++
1028
+ node.attributes.unshift(
1029
+ t.jsxAttribute(t.jsxIdentifier('data-is'), t.stringLiteral(node.name.name))
1030
+ )
1031
+ if (componentName) {
1032
+ node.attributes.unshift(
1033
+ t.jsxAttribute(t.jsxIdentifier('data-in'), t.stringLiteral(componentName))
1034
+ )
1035
+ }
1036
+
1037
+ node.attributes.unshift(
1038
+ t.jsxAttribute(
1039
+ t.jsxIdentifier('data-at'),
1040
+ t.stringLiteral(`${basename(filePath)}:${lineNumbers}`)
1041
+ )
1042
+ )
1043
+ }
1044
+ }
1045
+
1046
+ if (shouldDisableExtraction) {
1047
+ if (shouldPrintDebug === 'verbose') {
1048
+ logger.info(` ❌ Extraction disabled: ${JSON.stringify(disableExtraction)}\n`)
1049
+ }
1050
+ return
1051
+ }
1052
+
1053
+ try {
1054
+ const { staticConfig } = component
1055
+
1056
+ const defaultProps = {
1057
+ ...getDefaultProps(staticConfig),
1058
+ }
1059
+ const variants = staticConfig.variants || {}
1060
+ const isTextView = staticConfig.isText || false
1061
+ const validStyles = staticConfig?.validStyles ?? {}
1062
+
1063
+ // find render="a" render="main" etc dom indicators
1064
+ let tagName = defaultProps.render ?? (isTextView ? 'span' : 'div')
1065
+ traversePath
1066
+ .get('openingElement')
1067
+ .get('attributes')
1068
+ .forEach((path) => {
1069
+ const attr = path.node
1070
+ if (t.isJSXSpreadAttribute(attr)) return
1071
+ if (attr.name.name !== 'render') return
1072
+ const val = attr.value
1073
+ if (!t.isStringLiteral(val)) return
1074
+ tagName = val.value
1075
+ })
1076
+
1077
+ if (shouldPrintDebug === 'verbose') {
1078
+ console.info(` Start tag ${tagName}`)
1079
+ }
1080
+
1081
+ const flatNodeName = getFlattenedNode?.({ isTextView, tag: tagName })
1082
+
1083
+ const inlineProps = new Set([
1084
+ // adding some always inline props
1085
+ ...(restProps.inlineProps || []),
1086
+ ...(staticConfig.inlineProps || []),
1087
+ ])
1088
+
1089
+ const deoptProps = new Set([
1090
+ // always de-opt animation these
1091
+ 'animation',
1092
+ 'animateOnly',
1093
+ 'animatePresence',
1094
+ 'disableOptimization',
1095
+
1096
+ ...(!isTargetingHTML
1097
+ ? [
1098
+ 'pressStyle',
1099
+ 'focusStyle',
1100
+ 'focusVisibleStyle',
1101
+ 'focusWithinStyle',
1102
+ 'disabledStyle',
1103
+ ]
1104
+ : []),
1105
+
1106
+ // when using a non-CSS driver, de-opt on enterStyle/exitStyle
1107
+ ...(hanzoguiConfig?.animations.isReactNative
1108
+ ? ['enterStyle', 'exitStyle']
1109
+ : []),
1110
+ ])
1111
+
1112
+ const inlineWhenUnflattened = new Set(staticConfig.inlineWhenUnflattened || [])
1113
+
1114
+ // Generate scope object at this level
1115
+ const staticNamespace = getStaticBindingsForScope(
1116
+ traversePath.scope,
1117
+ importsWhitelist,
1118
+ sourcePath,
1119
+ bindingCache,
1120
+ shouldPrintDebug
1121
+ )
1122
+
1123
+ const attemptEval = !evaluateVars
1124
+ ? evaluateAstNode
1125
+ : createEvaluator({
1126
+ props: propsWithFileInfo,
1127
+ staticNamespace,
1128
+ sourcePath,
1129
+ traversePath,
1130
+ shouldPrintDebug,
1131
+ })
1132
+ const attemptEvalSafe = createSafeEvaluator(attemptEval)
1133
+
1134
+ if (shouldPrintDebug) {
1135
+ logger.info(` staticNamespace ${Object.keys(staticNamespace).join(', ')}`)
1136
+ }
1137
+
1138
+ //
1139
+ // SPREADS SETUP
1140
+ //
1141
+
1142
+ if (couldntParse) {
1143
+ return
1144
+ }
1145
+
1146
+ tm.mark('jsx-element-flattened', !!shouldPrintDebug)
1147
+
1148
+ let attrs: ExtractedAttr[] = []
1149
+ let shouldDeopt = false
1150
+ const inlined = new Map<string, any>()
1151
+ const variantValues = new Map<string, any>()
1152
+ let hasSetOptimized = false
1153
+ const inlineWhenUnflattenedOGVals = {}
1154
+
1155
+ // RUN first pass
1156
+
1157
+ // normalize all conditionals so we can evaluate away easier later
1158
+ // at the same time lets normalize shorthand media queries into spreads:
1159
+ // that way we can parse them with the same logic later on
1160
+ //
1161
+ // {...media.sm && { color: x ? 'red' : 'blue' }}
1162
+ // => {...media.sm && x && { color: 'red' }}
1163
+ // => {...media.sm && !x && { color: 'blue' }}
1164
+ //
1165
+ // $sm={{ color: 'red' }}
1166
+ // => {...media.sm && { color: 'red' }}
1167
+ //
1168
+ // $sm={{ color: x ? 'red' : 'blue' }}
1169
+ // => {...media.sm && x && { color: 'red' }}
1170
+ // => {...media.sm && !x && { color: 'blue' }}
1171
+
1172
+ const propMapperStyleState: GetStyleState = {
1173
+ staticConfig,
1174
+ usedKeys: {},
1175
+ classNames: {},
1176
+ style: {},
1177
+ theme: defaultTheme,
1178
+ viewProps: defaultProps,
1179
+ conf: hanzoguiConfig!,
1180
+ props: defaultProps,
1181
+ componentState,
1182
+ styleProps: {
1183
+ ...styleProps,
1184
+ resolveValues: 'auto',
1185
+ },
1186
+ debug: shouldPrintDebug,
1187
+ }
1188
+
1189
+ attrs = traversePath
1190
+ .get('openingElement')
1191
+ .get('attributes')
1192
+ .flatMap((path) => {
1193
+ // avoid work
1194
+ if (shouldDeopt) {
1195
+ return
1196
+ }
1197
+
1198
+ try {
1199
+ const res = evaluateAttribute(path)
1200
+ if (!res) {
1201
+ path.remove()
1202
+ }
1203
+
1204
+ return res
1205
+ } catch (err: any) {
1206
+ if (shouldPrintDebug) {
1207
+ logger.info(
1208
+ [
1209
+ 'Recoverable error extracting attribute',
1210
+ err.message,
1211
+ shouldPrintDebug === 'verbose' ? err.stack : '',
1212
+ ].join(' ')
1213
+ )
1214
+ if (shouldPrintDebug === 'verbose') {
1215
+ logger.info(`node ${path.node?.type}`)
1216
+ }
1217
+ }
1218
+ // dont flatten if we run into error
1219
+ inlined.set(`${Math.random()}`, 'spread')
1220
+ return {
1221
+ type: 'attr',
1222
+ value: path.node,
1223
+ } as const
1224
+ }
1225
+ })
1226
+ .flat(4)
1227
+ .filter(isPresent)
1228
+
1229
+ if (shouldPrintDebug) {
1230
+ logger.info(
1231
+ [' - attrs (before):\n', logLines(attrs.map(attrStr).join(', '))].join(' ')
1232
+ )
1233
+ }
1234
+
1235
+ // START function evaluateAttribute
1236
+ function evaluateAttribute(
1237
+ path: NodePath<t.JSXAttribute | t.JSXSpreadAttribute>
1238
+ ): ExtractedAttr | ExtractedAttr[] | null {
1239
+ const attribute = path.node
1240
+ const attr: ExtractedAttr = { type: 'attr', value: attribute }
1241
+ // ...spreads
1242
+ if (t.isJSXSpreadAttribute(attribute)) {
1243
+ const arg = attribute.argument
1244
+ const conditional = t.isConditionalExpression(arg)
1245
+ ? // <YStack {...isSmall ? { color: 'red } : { color: 'blue }}
1246
+ ([arg.test, arg.consequent, arg.alternate] as const)
1247
+ : t.isLogicalExpression(arg) && arg.operator === '&&'
1248
+ ? // <YStack {...isSmall && { color: 'red }}
1249
+ ([arg.left, arg.right, null] as const)
1250
+ : null
1251
+
1252
+ if (conditional) {
1253
+ const [test, alt, cons] = conditional
1254
+ if (!test) throw new Error(`no test`)
1255
+ if ([alt, cons].some((side) => side && !isStaticObject(side))) {
1256
+ if (shouldPrintDebug) {
1257
+ logger.info(`not extractable ${alt} ${cons}`)
1258
+ }
1259
+ return attr
1260
+ }
1261
+ // split into individual ternaries per object property
1262
+ return [
1263
+ ...(flattenNestedTernaries(test, alt) || []),
1264
+ ...((cons &&
1265
+ flattenNestedTernaries(t.unaryExpression('!', test), cons)) ||
1266
+ []),
1267
+ ].map((ternary) => ({
1268
+ type: 'ternary',
1269
+ value: ternary,
1270
+ }))
1271
+ }
1272
+ }
1273
+ // END ...spreads
1274
+
1275
+ // directly keep these
1276
+ // couldn't evaluate spread, undefined name, or name is not string
1277
+ if (
1278
+ t.isJSXSpreadAttribute(attribute) ||
1279
+ !attribute.name ||
1280
+ typeof attribute.name.name !== 'string'
1281
+ ) {
1282
+ if (shouldPrintDebug) {
1283
+ logger.info(' ! inlining, spread attr')
1284
+ }
1285
+ inlined.set(`${Math.random()}`, 'spread')
1286
+ return attr
1287
+ }
1288
+
1289
+ const name = attribute.name.name
1290
+
1291
+ // in hanzogui style is handled at the end of the style loop so its not as simple as just
1292
+ // adding this as a "style" property
1293
+ // its not used often when using hanzogui so not optimizing it for now
1294
+ if (name === 'style') {
1295
+ shouldDeopt = true
1296
+ return null
1297
+ }
1298
+
1299
+ if (excludeProps?.has(name)) {
1300
+ if (shouldPrintDebug) {
1301
+ logger.info([' excluding prop', name].join(' '))
1302
+ }
1303
+ return null
1304
+ }
1305
+
1306
+ if (inlineProps.has(name)) {
1307
+ inlined.set(name, name)
1308
+ if (shouldPrintDebug) {
1309
+ logger.info([' ! inlining, inline prop', name].join(' '))
1310
+ }
1311
+ return attr
1312
+ }
1313
+
1314
+ // pass className, key, and style props through untouched
1315
+ if (UNTOUCHED_PROPS[name]) {
1316
+ return attr
1317
+ }
1318
+
1319
+ if (INLINE_EXTRACTABLE[name]) {
1320
+ inlined.set(name, INLINE_EXTRACTABLE[name])
1321
+ return attr
1322
+ }
1323
+
1324
+ if (
1325
+ name.startsWith('data-') ||
1326
+ name.startsWith('aria-') ||
1327
+ validHTMLAttributes[name]
1328
+ ) {
1329
+ return attr
1330
+ }
1331
+
1332
+ // de-opt on enterStyle={expression}
1333
+ if (
1334
+ (name === 'enterStyle' || name === 'exitStyle') &&
1335
+ t.isJSXExpressionContainer(attribute?.value)
1336
+ ) {
1337
+ shouldDeopt = true
1338
+ return attr
1339
+ }
1340
+
1341
+ // shorthand media queries
1342
+ if (name[0] === '$' && t.isJSXExpressionContainer(attribute?.value)) {
1343
+ const shortname = name.slice(1)
1344
+ if (mediaQueryConfig[shortname]) {
1345
+ const expression = attribute.value.expression
1346
+ if (!t.isJSXEmptyExpression(expression)) {
1347
+ const ternaries = flattenNestedTernaries(
1348
+ t.stringLiteral(shortname),
1349
+ expression,
1350
+ {
1351
+ inlineMediaQuery: shortname,
1352
+ }
1353
+ )
1354
+ if (ternaries) {
1355
+ return ternaries.map((value) => ({
1356
+ type: 'ternary',
1357
+ value,
1358
+ }))
1359
+ }
1360
+ }
1361
+ }
1362
+ }
1363
+
1364
+ const [value, valuePath] = (() => {
1365
+ if (t.isJSXExpressionContainer(attribute?.value)) {
1366
+ return [attribute.value.expression!, path.get('value')!] as const
1367
+ }
1368
+ return [attribute.value!, path.get('value')!] as const
1369
+ })()
1370
+
1371
+ const remove = () => {
1372
+ Array.isArray(valuePath)
1373
+ ? valuePath.map((p) => p.remove())
1374
+ : valuePath.remove()
1375
+ }
1376
+
1377
+ if (name === 'ref') {
1378
+ if (shouldPrintDebug) {
1379
+ logger.info([' ! inlining, ref', name].join(' '))
1380
+ }
1381
+ inlined.set('ref', 'ref')
1382
+ return attr
1383
+ }
1384
+
1385
+ if (name === 'render') {
1386
+ // Only optimize string literal render props
1387
+ // JSX elements and functions should deopt
1388
+ if (!value || value.type !== 'StringLiteral') {
1389
+ if (shouldPrintDebug) {
1390
+ logger.info(` ! deopt on render prop (not a string literal)`)
1391
+ }
1392
+ shouldDeopt = true
1393
+ }
1394
+ return {
1395
+ type: 'attr',
1396
+ value: path.node,
1397
+ }
1398
+ }
1399
+
1400
+ // native shouldn't extract variables
1401
+ if (disableExtractVariables === true) {
1402
+ if (value) {
1403
+ if (value.type === 'StringLiteral' && value.value[0] === '$') {
1404
+ if (shouldPrintDebug) {
1405
+ logger.info(
1406
+ [
1407
+ ` ! inlining, native disable extract: ${name} =`,
1408
+ value.value,
1409
+ ].join(' ')
1410
+ )
1411
+ }
1412
+ inlined.set(name, true)
1413
+ return attr
1414
+ }
1415
+ }
1416
+ }
1417
+
1418
+ if (name === 'theme') {
1419
+ inlined.set('theme', attr.value)
1420
+ return attr
1421
+ }
1422
+
1423
+ // if value can be evaluated, extract it and filter it out
1424
+ const styleValue = attemptEvalSafe(value)
1425
+
1426
+ // never flatten if a prop isn't a valid static attribute
1427
+ // only post prop-mapping
1428
+ if (!variants[name] && !isValidStyleKey(name, staticConfig)) {
1429
+ let out: any = null
1430
+
1431
+ // for now passing empty props {}, a bit odd, need to at least document
1432
+ // for now we don't expose custom components so just noting behavior
1433
+ propMapper(name, styleValue, propMapperStyleState, false, (key, val) => {
1434
+ out ||= {}
1435
+ out[key] = val
1436
+ })
1437
+
1438
+ if (out) {
1439
+ if (isTargetingHTML) {
1440
+ // translate to DOM-compat
1441
+ out = reactNativeWebInternals.createDOMProps(
1442
+ isTextView ? 'span' : 'div',
1443
+ out
1444
+ )
1445
+ // remove className - we dont use rnw styling
1446
+ delete out.className
1447
+ }
1448
+ }
1449
+
1450
+ let didInline = false
1451
+ const attributes = Object.keys(out).map((key) => {
1452
+ const val = out[key]
1453
+ const isStyle = isValidStyleKey(key, staticConfig)
1454
+ if (isStyle) {
1455
+ return {
1456
+ type: 'style',
1457
+ value: { [key]: styleValue },
1458
+ name: key,
1459
+ attr: path.node,
1460
+ } as const
1461
+ }
1462
+ if (
1463
+ validHTMLAttributes[key] ||
1464
+ key.startsWith('aria-') ||
1465
+ key.startsWith('data-') ||
1466
+ // this is debug stuff added by vite / new jsx transform
1467
+ key === '__source' ||
1468
+ key === '__self'
1469
+ ) {
1470
+ return attr
1471
+ }
1472
+ if (shouldPrintDebug) {
1473
+ logger.info(' ! inlining, non-static ' + key)
1474
+ }
1475
+ didInline = true
1476
+ inlined.set(key, val)
1477
+ return val
1478
+ })
1479
+
1480
+ // weird logic whats going on here
1481
+ if (didInline) {
1482
+ if (shouldPrintDebug) {
1483
+ logger.info(
1484
+ ` bailing flattening due to attributes ${attributes.map((x) =>
1485
+ x.toString()
1486
+ )}`
1487
+ )
1488
+ }
1489
+ // bail
1490
+ return attr
1491
+ }
1492
+
1493
+ // return evaluated attributes
1494
+ return attributes
1495
+ }
1496
+
1497
+ // FAILED = dynamic or ternary, keep going
1498
+ if (styleValue !== FAILED_EVAL) {
1499
+ if (inlineWhenUnflattened.has(name)) {
1500
+ // preserve original value for restoration
1501
+ inlineWhenUnflattenedOGVals[name] = { styleValue, attr }
1502
+ }
1503
+
1504
+ if (isValidStyleKey(name, staticConfig)) {
1505
+ // $theme-, $group- styles should not be flattened (needs runtime handling)
1506
+ // $platform- can be flattened if the platform matches
1507
+ if (name[0] === '$') {
1508
+ if (name.startsWith('$theme-') || name.startsWith('$group-')) {
1509
+ if (shouldPrintDebug) {
1510
+ logger.info(` ! not flattening media-like style: ${name}`)
1511
+ }
1512
+ inlined.set(name, true)
1513
+ return attr
1514
+ }
1515
+
1516
+ // $platform-web, $platform-native, $platform-ios, $platform-android, $platform-tv, $platform-androidtv, $platform-tvos
1517
+ if (name.startsWith('$platform-')) {
1518
+ const platformName = name.slice(10) // remove '$platform-'
1519
+ const isMatchingPlatform =
1520
+ platformName === platform ||
1521
+ (platformName === 'native' && platform === 'native') ||
1522
+ (platformName === 'web' && platform === 'web')
1523
+
1524
+ if (isMatchingPlatform && typeof styleValue === 'object') {
1525
+ // Flatten the inner styles directly
1526
+ if (shouldPrintDebug) {
1527
+ logger.info(
1528
+ ` flattening $platform-${platformName}: ${JSON.stringify(styleValue)}`
1529
+ )
1530
+ }
1531
+ return Object.entries(styleValue).map(([key, val]) => ({
1532
+ type: 'style' as const,
1533
+ value: { [key]: val },
1534
+ name: key,
1535
+ attr: path.node,
1536
+ }))
1537
+ } else {
1538
+ // On native builds, sub-platform variants (android, ios, tv, androidtv, tvos)
1539
+ // can't be resolved at compile time - leave for runtime evaluation
1540
+ if (
1541
+ platform === 'native' &&
1542
+ nativeOnlyPlatforms.has(platformName)
1543
+ ) {
1544
+ if (shouldPrintDebug) {
1545
+ logger.info(
1546
+ ` ! keeping platform-specific style for runtime evaluation: ${name}`
1547
+ )
1548
+ }
1549
+ inlined.set(name, true)
1550
+ return attr
1551
+ }
1552
+ // Platform doesn't match, skip these styles entirely
1553
+ if (shouldPrintDebug) {
1554
+ logger.info(` ! skipping non-matching platform style: ${name}`)
1555
+ }
1556
+ return []
1557
+ }
1558
+ }
1559
+ }
1560
+ if (shouldPrintDebug) {
1561
+ logger.info(` style: ${name} = ${JSON.stringify(styleValue)}`)
1562
+ }
1563
+ if (!(name in defaultProps)) {
1564
+ if (!hasSetOptimized) {
1565
+ res.optimized++
1566
+ hasSetOptimized = true
1567
+ }
1568
+ }
1569
+ return {
1570
+ type: 'style',
1571
+ value: { [name]: styleValue },
1572
+ name,
1573
+ attr: path.node,
1574
+ }
1575
+ }
1576
+ if (variants[name]) {
1577
+ variantValues.set(name, styleValue)
1578
+ }
1579
+ inlined.set(name, true)
1580
+ return attr
1581
+ }
1582
+
1583
+ // ternaries!
1584
+
1585
+ // binary ternary, we can eventually make this smarter but step 1
1586
+ // basically for the common use case of:
1587
+ // opacity={(conditional ? 0 : 1) * scale}
1588
+ if (t.isBinaryExpression(value)) {
1589
+ if (shouldPrintDebug) {
1590
+ logger.info(` binary expression ${name} = ${value}`)
1591
+ }
1592
+ const { operator, left, right } = value
1593
+ // if one side is a ternary, and the other side is evaluatable, we can maybe extract
1594
+ const lVal = attemptEvalSafe(left)
1595
+ const rVal = attemptEvalSafe(right)
1596
+ if (shouldPrintDebug) {
1597
+ logger.info(
1598
+ ` evalBinaryExpression lVal ${String(lVal)}, rVal ${String(rVal)}`
1599
+ )
1600
+ }
1601
+ if (lVal !== FAILED_EVAL && t.isConditionalExpression(right)) {
1602
+ const ternary = addBinaryConditional(operator, left, right)
1603
+ if (ternary) return ternary
1604
+ }
1605
+ if (rVal !== FAILED_EVAL && t.isConditionalExpression(left)) {
1606
+ const ternary = addBinaryConditional(operator, right, left)
1607
+ if (ternary) return ternary
1608
+ }
1609
+ if (shouldPrintDebug) {
1610
+ logger.info(` evalBinaryExpression cant extract`)
1611
+ }
1612
+ inlined.set(name, true)
1613
+ return attr
1614
+ }
1615
+
1616
+ const staticConditional = getStaticConditional(value)
1617
+ if (staticConditional) {
1618
+ if (shouldPrintDebug === 'verbose') {
1619
+ logger.info(` static conditional ${name} ${value}`)
1620
+ }
1621
+ return { type: 'ternary', value: staticConditional }
1622
+ }
1623
+
1624
+ const staticLogical = getStaticLogical(value)
1625
+ if (staticLogical) {
1626
+ if (shouldPrintDebug === 'verbose') {
1627
+ logger.info(` static ternary ${name} = ${value}`)
1628
+ }
1629
+ return { type: 'ternary', value: staticLogical }
1630
+ }
1631
+
1632
+ // if we've made it this far, the prop stays inline
1633
+ inlined.set(name, true)
1634
+ if (shouldPrintDebug) {
1635
+ logger.info(` ! inline no match ${name} ${value}`)
1636
+ }
1637
+
1638
+ //
1639
+ // RETURN ATTR
1640
+ //
1641
+ return attr
1642
+
1643
+ // attr helpers:
1644
+ function addBinaryConditional(
1645
+ operator: any,
1646
+ staticExpr: any,
1647
+ cond: t.ConditionalExpression
1648
+ ): ExtractedAttr | null {
1649
+ if (getStaticConditional(cond)) {
1650
+ const alt = attemptEval(
1651
+ t.binaryExpression(operator, staticExpr, cond.alternate)
1652
+ )
1653
+ const cons = attemptEval(
1654
+ t.binaryExpression(operator, staticExpr, cond.consequent)
1655
+ )
1656
+ if (shouldPrintDebug) {
1657
+ logger.info([' binaryConditional', cond.test, cons, alt].join(' '))
1658
+ }
1659
+ return {
1660
+ type: 'ternary',
1661
+ value: {
1662
+ test: cond.test,
1663
+ remove,
1664
+ alternate: { [name]: alt },
1665
+ consequent: { [name]: cons },
1666
+ },
1667
+ }
1668
+ }
1669
+ return null
1670
+ }
1671
+
1672
+ function getStaticConditional(value: t.Node): Ternary | null {
1673
+ if (t.isConditionalExpression(value)) {
1674
+ try {
1675
+ const aVal = attemptEval(value.alternate)
1676
+ const cVal = attemptEval(value.consequent)
1677
+ if (shouldPrintDebug) {
1678
+ const type = value.test.type
1679
+ logger.info([' static ternary', type, cVal, aVal].join(' '))
1680
+ }
1681
+ return {
1682
+ test: value.test,
1683
+ remove,
1684
+ consequent: { [name]: cVal },
1685
+ alternate: { [name]: aVal },
1686
+ }
1687
+ } catch (err: any) {
1688
+ if (shouldPrintDebug) {
1689
+ logger.info([' cant eval ternary', err.message].join(' '))
1690
+ }
1691
+ }
1692
+ }
1693
+ return null
1694
+ }
1695
+
1696
+ function getStaticLogical(value: t.Node): Ternary | null {
1697
+ if (t.isLogicalExpression(value)) {
1698
+ if (value.operator === '&&') {
1699
+ try {
1700
+ const val = attemptEval(value.right)
1701
+ if (shouldPrintDebug) {
1702
+ logger.info([' staticLogical', value.left, name, val].join(' '))
1703
+ }
1704
+ return {
1705
+ test: value.left,
1706
+ remove,
1707
+ consequent: { [name]: val },
1708
+ alternate: null,
1709
+ }
1710
+ } catch (err) {
1711
+ if (shouldPrintDebug) {
1712
+ logger.info([' cant static eval logical', err].join(' '))
1713
+ }
1714
+ }
1715
+ }
1716
+ }
1717
+ return null
1718
+ }
1719
+ } // END function evaluateAttribute
1720
+
1721
+ function isStaticObject(obj: t.Node): obj is t.ObjectExpression {
1722
+ return (
1723
+ t.isObjectExpression(obj) &&
1724
+ obj.properties.every((prop) => {
1725
+ if (!t.isObjectProperty(prop)) {
1726
+ // console.warn('not an object prop?', prop)
1727
+ return false
1728
+ }
1729
+ const propName = prop.key['name']
1730
+ if (!isValidStyleKey(propName, staticConfig) && propName !== 'render') {
1731
+ if (shouldPrintDebug) {
1732
+ logger.info([' not a valid style prop!', propName].join(' '))
1733
+ }
1734
+ return false
1735
+ }
1736
+ return true
1737
+ })
1738
+ )
1739
+ }
1740
+
1741
+ // side = {
1742
+ // color: 'red',
1743
+ // background: x ? 'red' : 'green',
1744
+ // $gtSm: { color: 'green' }
1745
+ // }
1746
+ // => Ternary<test, { color: 'red' }, null>
1747
+ // => Ternary<test && x, { background: 'red' }, null>
1748
+ // => Ternary<test && !x, { background: 'green' }, null>
1749
+ // => Ternary<test && '$gtSm', { color: 'green' }, null>
1750
+ function flattenNestedTernaries(
1751
+ test: t.Expression,
1752
+ side: t.Expression | null,
1753
+ ternaryPartial: Partial<Ternary> = {}
1754
+ ): null | Ternary[] {
1755
+ if (!side) {
1756
+ return null
1757
+ }
1758
+ if (!isStaticObject(side)) {
1759
+ throw new Error('not extractable')
1760
+ }
1761
+ return side.properties.flatMap((property) => {
1762
+ if (!t.isObjectProperty(property)) {
1763
+ throw new Error('expected object property')
1764
+ }
1765
+ // this could be a recurse here if we want to get fancy
1766
+ if (t.isConditionalExpression(property.value)) {
1767
+ // merge up into the parent conditional, split into two
1768
+ const [truthy, falsy] = [
1769
+ t.objectExpression([
1770
+ t.objectProperty(property.key, property.value.consequent),
1771
+ ]),
1772
+ t.objectExpression([
1773
+ t.objectProperty(property.key, property.value.alternate),
1774
+ ]),
1775
+ ].map((x) => attemptEval(x))
1776
+ return [
1777
+ createTernary({
1778
+ remove() {},
1779
+ ...ternaryPartial,
1780
+ test: t.logicalExpression('&&', test, property.value.test),
1781
+ consequent: truthy,
1782
+ alternate: null,
1783
+ }),
1784
+ createTernary({
1785
+ ...ternaryPartial,
1786
+ test: t.logicalExpression(
1787
+ '&&',
1788
+ test,
1789
+ t.unaryExpression('!', property.value.test)
1790
+ ),
1791
+ consequent: falsy,
1792
+ alternate: null,
1793
+ remove() {},
1794
+ }),
1795
+ ]
1796
+ }
1797
+ const obj = t.objectExpression([
1798
+ t.objectProperty(property.key, property.value),
1799
+ ])
1800
+ const consequent = attemptEval(obj)
1801
+ return createTernary({
1802
+ remove() {},
1803
+ ...ternaryPartial,
1804
+ test,
1805
+ consequent,
1806
+ alternate: null,
1807
+ })
1808
+ })
1809
+ }
1810
+
1811
+ if (couldntParse || shouldDeopt) {
1812
+ if (shouldPrintDebug) {
1813
+ logger.info(
1814
+ [` avoid optimizing:`, { couldntParse, shouldDeopt }].join(' ')
1815
+ )
1816
+ }
1817
+ node.attributes = ogAttributes
1818
+ return
1819
+ }
1820
+
1821
+ // before deopt, can still optimize
1822
+ const parentFn = findTopmostFunction(traversePath)
1823
+ if (parentFn) {
1824
+ modifiedComponents.add(parentFn)
1825
+ }
1826
+
1827
+ // flatten logic!
1828
+ // fairly simple check to see if all children are text
1829
+ const hasSpread = attrs.some(
1830
+ (x) => x.type === 'attr' && t.isJSXSpreadAttribute(x.value)
1831
+ )
1832
+
1833
+ const hasOnlyStringChildren =
1834
+ !hasSpread &&
1835
+ (node.selfClosing ||
1836
+ (traversePath.node.children &&
1837
+ traversePath.node.children.every((x) => x.type === 'JSXText')))
1838
+
1839
+ let themeVal = inlined.get('theme')
1840
+
1841
+ // on native we can't flatten when theme prop is set
1842
+ if (platform !== 'native') {
1843
+ inlined.delete('theme')
1844
+ }
1845
+
1846
+ for (const [key] of inlined) {
1847
+ const isStaticObjectVariant =
1848
+ staticConfig.variants?.[key] && variantValues.has(key)
1849
+ if (INLINE_EXTRACTABLE[key] || isStaticObjectVariant) {
1850
+ inlined.delete(key)
1851
+ }
1852
+ }
1853
+
1854
+ const canFlattenProps = inlined.size === 0
1855
+
1856
+ let shouldFlatten = Boolean(
1857
+ flatNodeName &&
1858
+ !shouldDeopt &&
1859
+ canFlattenProps &&
1860
+ !hasSpread &&
1861
+ !staticConfig.isStyledHOC &&
1862
+ !staticConfig.isHOC &&
1863
+ !staticConfig.isReactNative &&
1864
+ staticConfig.neverFlatten !== true &&
1865
+ (staticConfig.neverFlatten === 'jsx' ? hasOnlyStringChildren : true)
1866
+ )
1867
+
1868
+ const usedThemeKeys = new Set<string>()
1869
+ // if it accesses any theme values during evaluation
1870
+ themeAccessListeners.add((key) => {
1871
+ if (disableExtractVariables) {
1872
+ usedThemeKeys.add(key)
1873
+ shouldFlatten = false
1874
+ if (shouldPrintDebug === 'verbose') {
1875
+ logger.info([' ! accessing theme key, avoid flatten', key].join(' '))
1876
+ }
1877
+ }
1878
+ })
1879
+
1880
+ if (!shouldFlatten) {
1881
+ // were no longer partially optimizing, it adds a lot of complexity for dubious performance
1882
+ if (shouldPrintDebug) {
1883
+ logger.info(
1884
+ `Deopting ${JSON.stringify({
1885
+ shouldFlatten,
1886
+ shouldDeopt,
1887
+ canFlattenProps,
1888
+ hasSpread,
1889
+ neverFlatten: staticConfig.neverFlatten,
1890
+ })}`
1891
+ )
1892
+ }
1893
+ node.attributes = ogAttributes
1894
+ return
1895
+ }
1896
+
1897
+ // ensure the default styles are there
1898
+ let skipMap = false
1899
+ const defaultStyleAttrs = Object.keys(defaultProps).flatMap((key) => {
1900
+ if (skipMap) return []
1901
+ const value = defaultProps[key]
1902
+ if (key === 'theme' && !themeVal) {
1903
+ if (platform === 'native') {
1904
+ shouldFlatten = false
1905
+ skipMap = true
1906
+ inlined.set('theme', { value: t.stringLiteral(value) })
1907
+ }
1908
+ themeVal = { value: t.stringLiteral(value) }
1909
+ return []
1910
+ }
1911
+ if (!isValidStyleKey(key, staticConfig)) {
1912
+ return []
1913
+ }
1914
+ const name = hanzoguiConfig?.shorthands[key] || key
1915
+ if (value === undefined) {
1916
+ logger.warn(
1917
+ `⚠️ Error evaluating default style for component, prop ${key} ${value}`
1918
+ )
1919
+ shouldDeopt = true
1920
+ return
1921
+ }
1922
+ if (name[0] === '$' && mediaQueryConfig[name.slice(1)]) {
1923
+ defaultProps[key] = undefined
1924
+ return evaluateAttribute({
1925
+ node: t.jsxAttribute(
1926
+ t.jsxIdentifier(name),
1927
+ t.jsxExpressionContainer(
1928
+ t.objectExpression(
1929
+ Object.keys(value)
1930
+ .filter((k) => {
1931
+ return typeof value[k] !== 'undefined'
1932
+ })
1933
+ .map((k) => {
1934
+ return t.objectProperty(t.identifier(k), literalToAst(value[k]))
1935
+ })
1936
+ )
1937
+ )
1938
+ ),
1939
+ } as any)
1940
+ }
1941
+ const attr: ExtractedAttrStyle = {
1942
+ type: 'style',
1943
+ name,
1944
+ value: { [name]: value },
1945
+ }
1946
+ return attr
1947
+ }) as ExtractedAttr[]
1948
+
1949
+ if (!skipMap) {
1950
+ if (defaultStyleAttrs.length) {
1951
+ attrs = [...defaultStyleAttrs, ...attrs]
1952
+ }
1953
+ }
1954
+
1955
+ // combine ternaries
1956
+ let ternaries: Ternary[] = []
1957
+ attrs = attrs
1958
+ .reduce<(ExtractedAttr | ExtractedAttr[])[]>((out, cur) => {
1959
+ const next = attrs[attrs.indexOf(cur) + 1]
1960
+ if (cur.type === 'ternary') {
1961
+ ternaries.push(cur.value)
1962
+ }
1963
+ if ((!next || next.type !== 'ternary') && ternaries.length) {
1964
+ // finish, process
1965
+ const normalized = normalizeTernaries(ternaries).map(
1966
+ ({ alternate, consequent, ...rest }) => {
1967
+ return {
1968
+ type: 'ternary' as const,
1969
+ value: {
1970
+ ...rest,
1971
+ alternate: alternate || null,
1972
+ consequent: consequent || null,
1973
+ },
1974
+ }
1975
+ }
1976
+ )
1977
+ try {
1978
+ return [...out, ...normalized]
1979
+ } finally {
1980
+ if (shouldPrintDebug) {
1981
+ logger.info(
1982
+ ` normalizeTernaries (${ternaries.length} => ${normalized.length})`
1983
+ )
1984
+ }
1985
+ ternaries = []
1986
+ }
1987
+ }
1988
+ if (cur.type === 'ternary') {
1989
+ return out
1990
+ }
1991
+ out.push(cur)
1992
+ return out
1993
+ }, [])
1994
+ .flat()
1995
+
1996
+ // wrap theme around children on flatten
1997
+ // account for shouldFlatten could change w the above block "if (disableExtractVariables)"
1998
+ if (themeVal) {
1999
+ if (!programPath) {
2000
+ console.warn(
2001
+ `No program path found, avoiding importing flattening / importing theme in ${sourcePath}`
2002
+ )
2003
+ } else {
2004
+ if (shouldPrintDebug) {
2005
+ logger.info([' - wrapping theme', themeVal].join(' '))
2006
+ }
2007
+
2008
+ // remove theme attribute from flattened node
2009
+ attrs = attrs.filter(
2010
+ (x) =>
2011
+ !(
2012
+ x.type === 'attr' &&
2013
+ t.isJSXAttribute(x.value) &&
2014
+ x.value.name.name === 'theme'
2015
+ )
2016
+ )
2017
+
2018
+ // add import
2019
+ if (!hasImportedTheme) {
2020
+ hasImportedTheme = true
2021
+ programPath.node.body.push(
2022
+ t.importDeclaration(
2023
+ [
2024
+ t.importSpecifier(
2025
+ t.identifier('_HanzoguiTheme'),
2026
+ t.identifier('Theme')
2027
+ ),
2028
+ ],
2029
+ t.stringLiteral('@hanzogui/web')
2030
+ )
2031
+ )
2032
+ }
2033
+
2034
+ traversePath.replaceWith(
2035
+ t.jsxElement(
2036
+ t.jsxOpeningElement(t.jsxIdentifier('_HanzoguiTheme'), [
2037
+ t.jsxAttribute(t.jsxIdentifier('name'), themeVal.value),
2038
+ ]),
2039
+ t.jsxClosingElement(t.jsxIdentifier('_HanzoguiTheme')),
2040
+ [traversePath.node]
2041
+ )
2042
+ )
2043
+ }
2044
+ }
2045
+
2046
+ if (shouldPrintDebug) {
2047
+ logger.info(
2048
+ [' - attrs (flattened): \n', logLines(attrs.map(attrStr).join(', '))].join(
2049
+ ' '
2050
+ )
2051
+ )
2052
+ }
2053
+
2054
+ function mergeToEnd(obj: object, key: string, val: any) {
2055
+ if (key in obj) {
2056
+ delete obj[key]
2057
+ }
2058
+ obj[key] = val
2059
+ }
2060
+
2061
+ // preserves order
2062
+ function normalizeStyleWithoutVariants(style: any) {
2063
+ let res = {}
2064
+ for (const key in style) {
2065
+ if (staticConfig.variants && key in staticConfig.variants) {
2066
+ mergeToEnd(res, key, style[key])
2067
+ } else {
2068
+ const expanded = normalizeStyle({ [key]: style[key] }, true)
2069
+ for (const key in expanded) {
2070
+ mergeToEnd(res, key, expanded[key])
2071
+ }
2072
+ }
2073
+ }
2074
+ return res
2075
+ }
2076
+
2077
+ // evaluates all static attributes into a simple object
2078
+ let foundStaticProps = {}
2079
+
2080
+ for (const key in attrs) {
2081
+ const cur = attrs[key]
2082
+ if (cur.type === 'style') {
2083
+ // remove variants because they are processed later, and can lead to invalid values here
2084
+ // see <Spacer flex /> where flex looks like a valid style, but is a variant
2085
+ const expanded = normalizeStyleWithoutVariants(cur.value)
2086
+ // preserve order
2087
+ for (const key in expanded) {
2088
+ mergeToEnd(foundStaticProps, key, expanded[key])
2089
+ }
2090
+ continue
2091
+ }
2092
+ if (cur.type === 'attr') {
2093
+ if (t.isJSXSpreadAttribute(cur.value)) {
2094
+ continue
2095
+ }
2096
+ if (!t.isJSXIdentifier(cur.value.name)) {
2097
+ continue
2098
+ }
2099
+ const key = cur.value.name.name
2100
+ // undefined = boolean true
2101
+ const value = attemptEvalSafe(cur.value.value || t.booleanLiteral(true))
2102
+ if (value !== FAILED_EVAL) {
2103
+ mergeToEnd(foundStaticProps, key, value)
2104
+ }
2105
+ }
2106
+ }
2107
+
2108
+ // must preserve exact order
2109
+ const completeProps = {}
2110
+ for (const key in defaultProps) {
2111
+ if (!(key in foundStaticProps)) {
2112
+ completeProps[key] = defaultProps[key]
2113
+ }
2114
+ }
2115
+ for (const key in foundStaticProps) {
2116
+ completeProps[key] = foundStaticProps[key]
2117
+ }
2118
+
2119
+ // expand shorthands, de-opt variables
2120
+ attrs = attrs.reduce<ExtractedAttr[]>((acc, cur) => {
2121
+ if (!cur) return acc
2122
+ if (cur.type === 'attr' && !t.isJSXSpreadAttribute(cur.value)) {
2123
+ if (shouldFlatten) {
2124
+ const name = cur.value.name.name
2125
+ if (typeof name === 'string') {
2126
+ if (name === 'render') {
2127
+ // remove render=""
2128
+ return acc
2129
+ }
2130
+
2131
+ // if flattening, expand variants
2132
+ if (variants[name] && variantValues.has(name)) {
2133
+ const styleState = {
2134
+ ...propMapperStyleState,
2135
+ props: completeProps,
2136
+ }
2137
+
2138
+ let out: Record<string, any> = {}
2139
+ propMapper(
2140
+ name,
2141
+ variantValues.get(name),
2142
+ styleState,
2143
+ false,
2144
+ (key, val) => {
2145
+ out[key] = val
2146
+ }
2147
+ )
2148
+
2149
+ if (out && isTargetingHTML) {
2150
+ const cn = out.className
2151
+ // translate to DOM-compat
2152
+ out = reactNativeWebInternals.createDOMProps(
2153
+ isTextView ? 'span' : 'div',
2154
+ out
2155
+ )
2156
+ // remove rnw className use ours
2157
+ out.className = cn
2158
+ }
2159
+ if (shouldPrintDebug) {
2160
+ logger.info([' - expanded variant', name, out].join(' '))
2161
+ }
2162
+ for (const key in out) {
2163
+ const value = out[key]
2164
+ if (isValidStyleKey(key, staticConfig)) {
2165
+ acc.push({
2166
+ type: 'style',
2167
+ value: { [key]: value },
2168
+ name: key,
2169
+ attr: cur.value,
2170
+ } as const)
2171
+ } else {
2172
+ acc.push({
2173
+ type: 'attr',
2174
+ value: t.jsxAttribute(
2175
+ t.jsxIdentifier(key),
2176
+ t.jsxExpressionContainer(
2177
+ typeof value === 'string'
2178
+ ? t.stringLiteral(value)
2179
+ : literalToAst(value)
2180
+ )
2181
+ ),
2182
+ })
2183
+ }
2184
+ }
2185
+ }
2186
+ }
2187
+ }
2188
+ }
2189
+
2190
+ if (cur.type !== 'style') {
2191
+ acc.push(cur)
2192
+ return acc
2193
+ }
2194
+
2195
+ let key = Object.keys(cur.value)[0]
2196
+ const value = cur.value[key]
2197
+ const fullKey = hanzoguiConfig?.shorthands[key]
2198
+ // expand shorthands
2199
+ if (fullKey) {
2200
+ cur.value = { [fullKey]: value }
2201
+ key = fullKey
2202
+ }
2203
+
2204
+ // finally we have all styles + expansions, lets see if we need to skip
2205
+ // any and keep them as attrs
2206
+ if (disableExtractVariables) {
2207
+ if (
2208
+ value[0] === '$' &&
2209
+ (usedThemeKeys.has(key) || usedThemeKeys.has(fullKey))
2210
+ ) {
2211
+ if (shouldPrintDebug) {
2212
+ logger.info([` keeping variable inline: ${key} =`, value].join(' '))
2213
+ }
2214
+ acc.push({
2215
+ type: 'attr',
2216
+ value: t.jsxAttribute(
2217
+ t.jsxIdentifier(key),
2218
+ t.jsxExpressionContainer(t.stringLiteral(value))
2219
+ ),
2220
+ })
2221
+ return acc
2222
+ }
2223
+ }
2224
+
2225
+ acc.push(cur)
2226
+ return acc
2227
+ }, [])
2228
+
2229
+ tm.mark('jsx-element-expanded', !!shouldPrintDebug)
2230
+ if (shouldPrintDebug) {
2231
+ logger.info(
2232
+ [' - attrs (expanded): \n', logLines(attrs.map(attrStr).join(', '))].join(
2233
+ ' '
2234
+ )
2235
+ )
2236
+ }
2237
+
2238
+ // merge styles, leave undefined values
2239
+ let prev: ExtractedAttr | null = null
2240
+
2241
+ function mergeStyles(
2242
+ prev: ViewStyle & PseudoStyles,
2243
+ next: ViewStyle & PseudoStyles
2244
+ ) {
2245
+ for (const key in next) {
2246
+ // merge pseudos
2247
+ if (pseudoDescriptors[key]) {
2248
+ prev[key] = prev[key] || {}
2249
+ Object.assign(prev[key], next[key])
2250
+ } else {
2251
+ mergeToEnd(prev, key, next[key])
2252
+ }
2253
+ }
2254
+ }
2255
+
2256
+ // post process
2257
+ const getProps = (
2258
+ props: object | null,
2259
+ includeProps = false,
2260
+ debugName = ''
2261
+ ) => {
2262
+ if (!props) {
2263
+ if (shouldPrintDebug) logger.info([' getProps() no props'].join(' '))
2264
+ return {}
2265
+ }
2266
+ if (excludeProps?.size) {
2267
+ for (const key in props) {
2268
+ if (excludeProps.has(key)) {
2269
+ if (shouldPrintDebug) logger.info([' delete excluded', key].join(' '))
2270
+ delete props[key]
2271
+ }
2272
+ }
2273
+ }
2274
+
2275
+ const before = process.env.IS_STATIC
2276
+ process.env.IS_STATIC = 'is_static'
2277
+ try {
2278
+ const out = getSplitStyles(
2279
+ props,
2280
+ staticConfig,
2281
+ defaultTheme,
2282
+ '',
2283
+ componentState,
2284
+ {
2285
+ ...styleProps,
2286
+ noClass: true,
2287
+ fallbackProps: completeProps,
2288
+ ...(platform === 'native' && {
2289
+ resolveValues: 'except-theme',
2290
+ }),
2291
+ },
2292
+ undefined,
2293
+ undefined,
2294
+ undefined,
2295
+ undefined,
2296
+ false,
2297
+ debugPropValue || shouldPrintDebug
2298
+ )!
2299
+
2300
+ let outProps = {
2301
+ ...(includeProps ? out.viewProps : {}),
2302
+ ...out.style,
2303
+ ...out.pseudos,
2304
+ }
2305
+
2306
+ // check de-opt props again
2307
+ for (const key in outProps) {
2308
+ if (deoptProps.has(key)) {
2309
+ shouldFlatten = false
2310
+ }
2311
+ }
2312
+
2313
+ if (shouldPrintDebug) {
2314
+ logger.info(`(${debugName})`)
2315
+ // prettier-ignore
2316
+ logger.info(`\n getProps (props in): ${logLines(objToStr(props))}`)
2317
+ // prettier-ignore
2318
+ logger.info(
2319
+ `\n getProps (outProps): ${logLines(objToStr(outProps))}`
2320
+ )
2321
+ }
2322
+
2323
+ if (out.fontFamily) {
2324
+ setPropsToFontFamily(outProps, out.fontFamily)
2325
+ if (shouldPrintDebug) {
2326
+ logger.info(`\n 💬 new font fam: ${out.fontFamily}`)
2327
+ }
2328
+ }
2329
+
2330
+ return outProps
2331
+ } catch (err: any) {
2332
+ logger.info(['error', err.message, err.stack].join(' '))
2333
+ return {}
2334
+ } finally {
2335
+ process.env.IS_STATIC = before
2336
+ }
2337
+ }
2338
+
2339
+ // add default props
2340
+ attrs.unshift({
2341
+ type: 'style',
2342
+ value: defaultProps,
2343
+ })
2344
+
2345
+ attrs = attrs.reduce<ExtractedAttr[]>((acc, cur) => {
2346
+ if (cur.type === 'style') {
2347
+ const keys = Object.keys(cur.value || {})
2348
+ if (!keys.length) {
2349
+ return acc
2350
+ }
2351
+ const key = keys[0]
2352
+ const value = cur.value[key]
2353
+
2354
+ // Check if this is a media-like key ($theme-, $platform-, $group-, or $mediaQuery)
2355
+ const isMediaLikeKey =
2356
+ key[0] === '$' &&
2357
+ (key.startsWith('$theme-') ||
2358
+ key.startsWith('$platform-') ||
2359
+ key.startsWith('$group-') ||
2360
+ mediaQueryConfig[key.slice(1)])
2361
+
2362
+ const shouldKeepOriginalAttr =
2363
+ // !isStyleAndAttr[key] &&
2364
+ !shouldFlatten &&
2365
+ // de-opt if non-style
2366
+ !validStyles[key] &&
2367
+ !pseudoDescriptors[key] &&
2368
+ !isMediaLikeKey &&
2369
+ !(key.startsWith('data-') || key.startsWith('aria-'))
2370
+
2371
+ if (shouldKeepOriginalAttr) {
2372
+ if (shouldPrintDebug) {
2373
+ logger.info([' - keeping as non-style', key].join(' '))
2374
+ }
2375
+ prev = cur
2376
+ acc.push({
2377
+ type: 'attr',
2378
+ value: t.jsxAttribute(
2379
+ t.jsxIdentifier(key),
2380
+ t.jsxExpressionContainer(
2381
+ typeof value === 'string'
2382
+ ? t.stringLiteral(value)
2383
+ : literalToAst(value)
2384
+ )
2385
+ ),
2386
+ })
2387
+ acc.push(cur)
2388
+ return acc
2389
+ }
2390
+
2391
+ if (prev?.type === 'style') {
2392
+ mergeStyles(prev.value, cur.value)
2393
+ return acc
2394
+ }
2395
+ }
2396
+
2397
+ if (cur.type === 'style') {
2398
+ prev = cur
2399
+ }
2400
+ acc.push(cur)
2401
+ return acc
2402
+ }, [])
2403
+
2404
+ if (shouldPrintDebug) {
2405
+ logger.info(
2406
+ [
2407
+ ' - attrs (combined 🔀): \n',
2408
+ logLines(attrs.map(attrStr).join(', ')),
2409
+ ].join(' ')
2410
+ )
2411
+ }
2412
+
2413
+ let getStyleError: any = null
2414
+
2415
+ // fix up ternaries, combine final style values
2416
+ for (const attr of attrs) {
2417
+ try {
2418
+ if (shouldPrintDebug) {
2419
+ console.info(` Processing ${attr.type}:`)
2420
+ }
2421
+
2422
+ switch (attr.type) {
2423
+ case 'ternary': {
2424
+ const a = getProps(attr.value.alternate, false, 'ternary.alternate')
2425
+ const c = getProps(attr.value.consequent, false, 'ternary.consequent')
2426
+ if (a) attr.value.alternate = a
2427
+ if (c) attr.value.consequent = c
2428
+ if (shouldPrintDebug)
2429
+ logger.info([' => tern ', attrStr(attr)].join(' '))
2430
+ continue
2431
+ }
2432
+ case 'style': {
2433
+ // expand variants and such
2434
+ const styles = getProps(attr.value, false, 'style')
2435
+ if (styles) {
2436
+ // @ts-ignore
2437
+ attr.value = styles
2438
+ }
2439
+ // prettier-ignore
2440
+ if (shouldPrintDebug)
2441
+ logger.info(
2442
+ [' * styles (in)', logLines(objToStr(attr.value))].join(' ')
2443
+ )
2444
+ // prettier-ignore
2445
+ if (shouldPrintDebug)
2446
+ logger.info(
2447
+ [' * styles (out)', logLines(objToStr(styles))].join(' ')
2448
+ )
2449
+ continue
2450
+ }
2451
+ case 'attr': {
2452
+ if (shouldFlatten && t.isJSXAttribute(attr.value)) {
2453
+ // we know all attributes are static
2454
+ // this only does one at a time but it should really do the whole group together...
2455
+ // also awkward to be doing it using jsxAttributes...
2456
+ const key = attr.value.name.name as string
2457
+
2458
+ // dont process style/className can just stay attrs
2459
+ if (key === 'style' || key === 'className' || key === 'render') {
2460
+ continue
2461
+ }
2462
+
2463
+ // undefined = boolean true
2464
+ const value = attemptEvalSafe(
2465
+ attr.value.value || t.booleanLiteral(true)
2466
+ )
2467
+ if (value !== FAILED_EVAL) {
2468
+ const outProps = getProps({ [key]: value }, true, `attr.${key}`)
2469
+ const outKey = Object.keys(outProps)[0]
2470
+ if (outKey) {
2471
+ const outVal = outProps[outKey]
2472
+ attr.value = t.jsxAttribute(
2473
+ t.jsxIdentifier(outKey),
2474
+ t.jsxExpressionContainer(
2475
+ typeof outVal === 'string'
2476
+ ? t.stringLiteral(outVal)
2477
+ : literalToAst(outVal)
2478
+ )
2479
+ )
2480
+ }
2481
+ }
2482
+ }
2483
+ }
2484
+ }
2485
+ } catch (err) {
2486
+ // any error de-opt
2487
+ getStyleError = err
2488
+ }
2489
+ }
2490
+
2491
+ if (shouldPrintDebug) {
2492
+ // prettier-ignore
2493
+ logger.info(
2494
+ [
2495
+ ' - attrs (ternaries/combined):\n',
2496
+ logLines(attrs.map(attrStr).join(', ')),
2497
+ ].join(' ')
2498
+ )
2499
+ }
2500
+
2501
+ tm.mark('jsx-element-styles', !!shouldPrintDebug)
2502
+
2503
+ if (getStyleError) {
2504
+ logger.info([' ⚠️ postprocessing error, deopt', getStyleError].join(' '))
2505
+ node.attributes = ogAttributes
2506
+ return null
2507
+ }
2508
+
2509
+ // final lazy extra loop:
2510
+ const existingStyleKeys = new Set()
2511
+ for (let i = attrs.length - 1; i >= 0; i--) {
2512
+ const attr = attrs[i]
2513
+
2514
+ // if flattening map inline props to proper flattened names
2515
+ if (shouldFlatten) {
2516
+ if (attr.type === 'attr') {
2517
+ if (t.isJSXAttribute(attr.value)) {
2518
+ if (t.isJSXIdentifier(attr.value.name)) {
2519
+ const name = attr.value.name.name
2520
+ if (INLINE_EXTRACTABLE[name]) {
2521
+ // map to HTML only name
2522
+ attr.value.name.name = INLINE_EXTRACTABLE[name]
2523
+ }
2524
+ }
2525
+ }
2526
+ }
2527
+ }
2528
+
2529
+ // remove duplicate styles
2530
+ // so if you have:
2531
+ // style({ color: 'red' }), ...someProps, style({ color: 'green' })
2532
+ // this will mutate:
2533
+ // style({}), ...someProps, style({ color: 'green' })
2534
+ if (attr.type === 'style') {
2535
+ for (const key in attr.value) {
2536
+ if (existingStyleKeys.has(key)) {
2537
+ if (shouldPrintDebug) {
2538
+ logger.info([` >> delete existing ${key}`].join(' '))
2539
+ }
2540
+ delete attr.value[key]
2541
+ } else {
2542
+ existingStyleKeys.add(key)
2543
+ }
2544
+ }
2545
+ }
2546
+ }
2547
+
2548
+ attrs = attrs.filter(Boolean)
2549
+
2550
+ // inlineWhenUnflattened
2551
+ if (!shouldFlatten) {
2552
+ if (inlineWhenUnflattened.size) {
2553
+ for (const [index, attr] of attrs.entries()) {
2554
+ if (attr.type === 'style') {
2555
+ for (const key in attr.value) {
2556
+ if (!inlineWhenUnflattened.has(key)) continue
2557
+ const val = inlineWhenUnflattenedOGVals[key]
2558
+ if (val) {
2559
+ // delete the style
2560
+ delete attr.value[key]
2561
+
2562
+ // and insert it before
2563
+ attrs.splice(index - 1, 0, val.attr)
2564
+ } else {
2565
+ // just delete it, it was added during expansion but should be left inline
2566
+ delete attr.value[key]
2567
+ }
2568
+ }
2569
+ }
2570
+ }
2571
+ }
2572
+ }
2573
+
2574
+ // delete empty styles:
2575
+ attrs = attrs.filter((x) => {
2576
+ if (x.type === 'style' && Object.keys(x.value).length === 0) {
2577
+ return false
2578
+ }
2579
+ return true
2580
+ })
2581
+
2582
+ const isNativeNotFlat = !shouldFlatten && platform === 'native'
2583
+ if (isNativeNotFlat) {
2584
+ if (shouldPrintDebug) {
2585
+ logger.info(
2586
+ `Disabled flattening except for simple cases on native for now: ${JSON.stringify(
2587
+ {
2588
+ flatNode: flatNodeName,
2589
+ shouldDeopt,
2590
+ canFlattenProps,
2591
+ hasSpread,
2592
+ 'staticConfig.isStyledHOC': staticConfig.isStyledHOC,
2593
+ '!staticConfig.isHOC': !staticConfig.isHOC,
2594
+ 'staticConfig.isReactNative': staticConfig.isReactNative,
2595
+ 'staticConfig.neverFlatten': staticConfig.neverFlatten,
2596
+ },
2597
+ null,
2598
+ 2
2599
+ )}`
2600
+ )
2601
+ }
2602
+ node.attributes = ogAttributes
2603
+ return null
2604
+ }
2605
+
2606
+ if (shouldPrintDebug) {
2607
+ // prettier-ignore
2608
+ logger.info(
2609
+ [
2610
+ ` - inlined props (${inlined.size}):`,
2611
+ shouldDeopt ? ' deopted' : '',
2612
+ hasSpread ? ' has spread' : '',
2613
+ staticConfig.neverFlatten ? 'neverFlatten' : '',
2614
+ ].join(' ')
2615
+ )
2616
+ logger.info(` - attrs (end):\n ${logLines(attrs.map(attrStr).join(', '))}`)
2617
+ }
2618
+
2619
+ onExtractTag({
2620
+ parserProps: propsWithFileInfo,
2621
+ attrs,
2622
+ node,
2623
+ lineNumbers,
2624
+ filePath,
2625
+ config: hanzoguiConfig!,
2626
+ flatNodeName,
2627
+ attemptEval,
2628
+ jsxPath: traversePath,
2629
+ originalNodeName,
2630
+ programPath: programPath!,
2631
+ completeProps,
2632
+ staticConfig,
2633
+ })
2634
+
2635
+ if (shouldFlatten) {
2636
+ if (shouldPrintDebug) {
2637
+ logger.info([' [✅] flattened', originalNodeName, flatNodeName].join(' '))
2638
+ }
2639
+ // Only rename if onExtractTag hasn't already renamed to a custom wrapper
2640
+ // @ts-ignore - check if already renamed by callback (e.g., to a styled wrapper)
2641
+ const currentName = node.name?.name
2642
+ if (
2643
+ !currentName ||
2644
+ currentName === originalNodeName ||
2645
+ currentName.startsWith('__ReactNative')
2646
+ ) {
2647
+ // @ts-ignore
2648
+ node.name.name = flatNodeName
2649
+ if (closingElement) {
2650
+ // @ts-ignore
2651
+ closingElement.name.name = flatNodeName
2652
+ }
2653
+ }
2654
+ res.flattened++
2655
+ }
2656
+ } catch (err: any) {
2657
+ node.attributes = ogAttributes
2658
+
2659
+ if (!(err instanceof BailOptimizationError)) {
2660
+ console.error(
2661
+ `@hanzogui/static error, reverting optimization. In ${filePath} ${lineNumbers} on ${originalNodeName}: ${err.message}. For stack trace set environment TAMAGUI_DEBUG=1`
2662
+ )
2663
+ if (process.env.TAMAGUI_DEBUG === '1') {
2664
+ console.error(err.stack)
2665
+ }
2666
+ }
2667
+ } finally {
2668
+ if (debugPropValue) {
2669
+ shouldPrintDebug = ogDebug
2670
+ }
2671
+ }
2672
+ },
2673
+ })
2674
+
2675
+ tm.mark('jsx-done', !!shouldPrintDebug)
2676
+ tm.done(shouldPrintDebug === 'verbose')
2677
+
2678
+ return res
2679
+ }
2680
+ }