@hanzogui/static 2.0.0

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 (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/dist/check-dep-versions.cjs +267 -0
  4. package/dist/checkDeps.cjs +200 -0
  5. package/dist/constants.cjs +49 -0
  6. package/dist/exports.cjs +44 -0
  7. package/dist/extractor/accessSafe.cjs +46 -0
  8. package/dist/extractor/babelParse.cjs +54 -0
  9. package/dist/extractor/buildClassName.cjs +62 -0
  10. package/dist/extractor/bundle.cjs +185 -0
  11. package/dist/extractor/bundleConfig.cjs +513 -0
  12. package/dist/extractor/concatClassName.cjs +80 -0
  13. package/dist/extractor/createEvaluator.cjs +75 -0
  14. package/dist/extractor/createExtractor.cjs +1150 -0
  15. package/dist/extractor/createLogger.cjs +45 -0
  16. package/dist/extractor/detectModuleFormat.cjs +49 -0
  17. package/dist/extractor/ensureImportingConcat.cjs +48 -0
  18. package/dist/extractor/errors.cjs +26 -0
  19. package/dist/extractor/esbuildAliasPlugin.cjs +44 -0
  20. package/dist/extractor/esbuildTsconfigPaths.cjs +86 -0
  21. package/dist/extractor/evaluateAstNode.cjs +83 -0
  22. package/dist/extractor/extractHelpers.cjs +119 -0
  23. package/dist/extractor/extractMediaStyle.cjs +128 -0
  24. package/dist/extractor/extractToClassNames.cjs +321 -0
  25. package/dist/extractor/extractToNative.cjs +266 -0
  26. package/dist/extractor/findTopmostFunction.cjs +34 -0
  27. package/dist/extractor/generatedUid.cjs +47 -0
  28. package/dist/extractor/getGuiConfigPathFromOptionsConfig.cjs +35 -0
  29. package/dist/extractor/getPrefixLogs.cjs +29 -0
  30. package/dist/extractor/getPropValueFromAttributes.cjs +67 -0
  31. package/dist/extractor/getSourceModule.cjs +64 -0
  32. package/dist/extractor/getStaticBindingsForScope.cjs +150 -0
  33. package/dist/extractor/hoistClassNames.cjs +62 -0
  34. package/dist/extractor/literalToAst.cjs +84 -0
  35. package/dist/extractor/loadFile.cjs +11 -0
  36. package/dist/extractor/loadGui.cjs +322 -0
  37. package/dist/extractor/logLines.cjs +35 -0
  38. package/dist/extractor/normalizeTernaries.cjs +73 -0
  39. package/dist/extractor/propsToFontFamilyCache.cjs +38 -0
  40. package/dist/extractor/regenerateConfig.cjs +150 -0
  41. package/dist/extractor/removeUnusedHooks.cjs +76 -0
  42. package/dist/extractor/timer.cjs +43 -0
  43. package/dist/extractor/validHTMLAttributes.cjs +77 -0
  44. package/dist/extractor/watchGuiConfig.cjs +58 -0
  45. package/dist/getPragmaOptions.cjs +58 -0
  46. package/dist/helpers/memoize.cjs +37 -0
  47. package/dist/helpers/requireGuiCore.cjs +33 -0
  48. package/dist/index.cjs +40 -0
  49. package/dist/registerRequire.cjs +115 -0
  50. package/dist/server.cjs +75 -0
  51. package/dist/setup.cjs +0 -0
  52. package/dist/types.cjs +16 -0
  53. package/dist/worker.cjs +89 -0
  54. package/package.json +101 -0
  55. package/src/check-dep-versions.ts +738 -0
  56. package/src/checkDeps.ts +371 -0
  57. package/src/constants.ts +14 -0
  58. package/src/exports.ts +15 -0
  59. package/src/extractor/accessSafe.ts +22 -0
  60. package/src/extractor/babelParse.ts +37 -0
  61. package/src/extractor/buildClassName.ts +76 -0
  62. package/src/extractor/bundle.ts +277 -0
  63. package/src/extractor/bundleConfig.ts +958 -0
  64. package/src/extractor/concatClassName.ts +105 -0
  65. package/src/extractor/createEvaluator.ts +82 -0
  66. package/src/extractor/createExtractor.ts +2665 -0
  67. package/src/extractor/createLogger.ts +41 -0
  68. package/src/extractor/detectModuleFormat.ts +42 -0
  69. package/src/extractor/ensureImportingConcat.ts +36 -0
  70. package/src/extractor/errors.ts +1 -0
  71. package/src/extractor/esbuildAliasPlugin.ts +40 -0
  72. package/src/extractor/esbuildTsconfigPaths.ts +103 -0
  73. package/src/extractor/evaluateAstNode.ts +136 -0
  74. package/src/extractor/extractHelpers.ts +211 -0
  75. package/src/extractor/extractMediaStyle.ts +194 -0
  76. package/src/extractor/extractToClassNames.ts +631 -0
  77. package/src/extractor/extractToNative.ts +463 -0
  78. package/src/extractor/findTopmostFunction.ts +24 -0
  79. package/src/extractor/generatedUid.ts +41 -0
  80. package/src/extractor/getGuiConfigPathFromOptionsConfig.ts +24 -0
  81. package/src/extractor/getPrefixLogs.ts +9 -0
  82. package/src/extractor/getPropValueFromAttributes.ts +95 -0
  83. package/src/extractor/getSourceModule.ts +96 -0
  84. package/src/extractor/getStaticBindingsForScope.ts +237 -0
  85. package/src/extractor/hoistClassNames.ts +52 -0
  86. package/src/extractor/literalToAst.ts +85 -0
  87. package/src/extractor/loadFile.ts +17 -0
  88. package/src/extractor/loadGui.ts +487 -0
  89. package/src/extractor/logLines.ts +16 -0
  90. package/src/extractor/normalizeTernaries.ts +75 -0
  91. package/src/extractor/propsToFontFamilyCache.ts +18 -0
  92. package/src/extractor/regenerateConfig.ts +186 -0
  93. package/src/extractor/removeUnusedHooks.ts +83 -0
  94. package/src/extractor/timer.ts +25 -0
  95. package/src/extractor/validHTMLAttributes.ts +52 -0
  96. package/src/extractor/watchGuiConfig.ts +57 -0
  97. package/src/getPragmaOptions.ts +57 -0
  98. package/src/helpers/memoize.ts +24 -0
  99. package/src/helpers/requireGuiCore.ts +28 -0
  100. package/src/index.ts +4 -0
  101. package/src/registerRequire.ts +236 -0
  102. package/src/server.ts +43 -0
  103. package/src/setup.ts +0 -0
  104. package/src/types.ts +109 -0
  105. package/src/worker.ts +142 -0
  106. package/types/check-dep-versions.d.ts +37 -0
  107. package/types/check-dep-versions.d.ts.map +1 -0
  108. package/types/checkDeps.d.ts +19 -0
  109. package/types/checkDeps.d.ts.map +1 -0
  110. package/types/constants.d.ts +6 -0
  111. package/types/constants.d.ts.map +1 -0
  112. package/types/exports.d.ts +16 -0
  113. package/types/exports.d.ts.map +1 -0
  114. package/types/extractor/accessSafe.d.ts +3 -0
  115. package/types/extractor/accessSafe.d.ts.map +1 -0
  116. package/types/extractor/babelParse.d.ts +5 -0
  117. package/types/extractor/babelParse.d.ts.map +1 -0
  118. package/types/extractor/buildClassName.d.ts +7 -0
  119. package/types/extractor/buildClassName.d.ts.map +1 -0
  120. package/types/extractor/bundle.d.ts +121 -0
  121. package/types/extractor/bundle.d.ts.map +1 -0
  122. package/types/extractor/bundleConfig.d.ts +49 -0
  123. package/types/extractor/bundleConfig.d.ts.map +1 -0
  124. package/types/extractor/concatClassName.d.ts +8 -0
  125. package/types/extractor/concatClassName.d.ts.map +1 -0
  126. package/types/extractor/createEvaluator.d.ts +12 -0
  127. package/types/extractor/createEvaluator.d.ts.map +1 -0
  128. package/types/extractor/createExtractor.d.ts +32 -0
  129. package/types/extractor/createExtractor.d.ts.map +1 -0
  130. package/types/extractor/createLogger.d.ts +3 -0
  131. package/types/extractor/createLogger.d.ts.map +1 -0
  132. package/types/extractor/detectModuleFormat.d.ts +5 -0
  133. package/types/extractor/detectModuleFormat.d.ts.map +1 -0
  134. package/types/extractor/ensureImportingConcat.d.ts +4 -0
  135. package/types/extractor/ensureImportingConcat.d.ts.map +1 -0
  136. package/types/extractor/errors.d.ts +3 -0
  137. package/types/extractor/errors.d.ts.map +1 -0
  138. package/types/extractor/esbuildAliasPlugin.d.ts +18 -0
  139. package/types/extractor/esbuildAliasPlugin.d.ts.map +1 -0
  140. package/types/extractor/esbuildTsconfigPaths.d.ts +11 -0
  141. package/types/extractor/esbuildTsconfigPaths.d.ts.map +1 -0
  142. package/types/extractor/evaluateAstNode.d.ts +3 -0
  143. package/types/extractor/evaluateAstNode.d.ts.map +1 -0
  144. package/types/extractor/extractHelpers.d.ts +28 -0
  145. package/types/extractor/extractHelpers.d.ts.map +1 -0
  146. package/types/extractor/extractMediaStyle.d.ts +11 -0
  147. package/types/extractor/extractMediaStyle.d.ts.map +1 -0
  148. package/types/extractor/extractToClassNames.d.ts +25 -0
  149. package/types/extractor/extractToClassNames.d.ts.map +1 -0
  150. package/types/extractor/extractToNative.d.ts +13 -0
  151. package/types/extractor/extractToNative.d.ts.map +1 -0
  152. package/types/extractor/findTopmostFunction.d.ts +4 -0
  153. package/types/extractor/findTopmostFunction.d.ts.map +1 -0
  154. package/types/extractor/generatedUid.d.ts +5 -0
  155. package/types/extractor/generatedUid.d.ts.map +1 -0
  156. package/types/extractor/getGuiConfigPathFromOptionsConfig.d.ts +3 -0
  157. package/types/extractor/getGuiConfigPathFromOptionsConfig.d.ts.map +1 -0
  158. package/types/extractor/getPrefixLogs.d.ts +3 -0
  159. package/types/extractor/getPrefixLogs.d.ts.map +1 -0
  160. package/types/extractor/getPropValueFromAttributes.d.ts +19 -0
  161. package/types/extractor/getPropValueFromAttributes.d.ts.map +1 -0
  162. package/types/extractor/getSourceModule.d.ts +16 -0
  163. package/types/extractor/getSourceModule.d.ts.map +1 -0
  164. package/types/extractor/getStaticBindingsForScope.d.ts +5 -0
  165. package/types/extractor/getStaticBindingsForScope.d.ts.map +1 -0
  166. package/types/extractor/hoistClassNames.d.ts +6 -0
  167. package/types/extractor/hoistClassNames.d.ts.map +1 -0
  168. package/types/extractor/literalToAst.d.ts +4 -0
  169. package/types/extractor/literalToAst.d.ts.map +1 -0
  170. package/types/extractor/loadFile.d.ts +1 -0
  171. package/types/extractor/loadFile.d.ts.map +1 -0
  172. package/types/extractor/loadGui.d.ts +22 -0
  173. package/types/extractor/loadGui.d.ts.map +1 -0
  174. package/types/extractor/logLines.d.ts +2 -0
  175. package/types/extractor/logLines.d.ts.map +1 -0
  176. package/types/extractor/normalizeTernaries.d.ts +3 -0
  177. package/types/extractor/normalizeTernaries.d.ts.map +1 -0
  178. package/types/extractor/propsToFontFamilyCache.d.ts +4 -0
  179. package/types/extractor/propsToFontFamilyCache.d.ts.map +1 -0
  180. package/types/extractor/regenerateConfig.d.ts +9 -0
  181. package/types/extractor/regenerateConfig.d.ts.map +1 -0
  182. package/types/extractor/removeUnusedHooks.d.ts +3 -0
  183. package/types/extractor/removeUnusedHooks.d.ts.map +1 -0
  184. package/types/extractor/timer.d.ts +5 -0
  185. package/types/extractor/timer.d.ts.map +1 -0
  186. package/types/extractor/validHTMLAttributes.d.ts +52 -0
  187. package/types/extractor/validHTMLAttributes.d.ts.map +1 -0
  188. package/types/extractor/watchGuiConfig.d.ts +5 -0
  189. package/types/extractor/watchGuiConfig.d.ts.map +1 -0
  190. package/types/getPragmaOptions.d.ts +8 -0
  191. package/types/getPragmaOptions.d.ts.map +1 -0
  192. package/types/helpers/memoize.d.ts +8 -0
  193. package/types/helpers/memoize.d.ts.map +1 -0
  194. package/types/helpers/requireGuiCore.d.ts +3 -0
  195. package/types/helpers/requireGuiCore.d.ts.map +1 -0
  196. package/types/index.d.ts +4 -0
  197. package/types/index.d.ts.map +1 -0
  198. package/types/registerRequire.d.ts +10 -0
  199. package/types/registerRequire.d.ts.map +1 -0
  200. package/types/server.d.ts +3 -0
  201. package/types/server.d.ts.map +1 -0
  202. package/types/setup.d.ts +1 -0
  203. package/types/setup.d.ts.map +1 -0
  204. package/types/types.d.ts +92 -0
  205. package/types/types.d.ts.map +1 -0
  206. package/types/worker.d.ts +46 -0
  207. package/types/worker.d.ts.map +1 -0
@@ -0,0 +1,463 @@
1
+ import { type BabelFileResult, transformFromAstSync } from '@babel/core'
2
+ import generator from '@babel/generator'
3
+ import { declare } from '@babel/helper-plugin-utils'
4
+ import { parse } from '@babel/parser'
5
+ import template from '@babel/template'
6
+ import * as t from '@babel/types'
7
+ import { basename } from 'node:path'
8
+ import { getPragmaOptions } from '../getPragmaOptions'
9
+ import type { GuiOptions } from '../types'
10
+ import { createExtractor } from './createExtractor'
11
+ import { createLogger } from './createLogger'
12
+ import { isSimpleSpread } from './extractHelpers'
13
+ import { literalToAst } from './literalToAst'
14
+ import { loadGuiBuildConfigSync } from './loadGui'
15
+
16
+ const importNativeView = template(`
17
+ const __ReactNativeView = require('react-native').View;
18
+ const __ReactNativeText = require('react-native').Text;
19
+ `)
20
+
21
+ const importStyleSheet = template(`
22
+ const __ReactNativeStyleSheet = require('react-native').StyleSheet;
23
+ `)
24
+
25
+ const importWithStyle = template.ast(`import { _withStableStyle } from '@hanzogui/core';`)
26
+
27
+ const extractor = createExtractor({ platform: 'native' })
28
+
29
+ let guiBuildOptionsLoaded: GuiOptions | null
30
+
31
+ export function extractToNative(
32
+ sourceFileName: string,
33
+ sourceCode: string,
34
+ options: GuiOptions
35
+ ): BabelFileResult {
36
+ const ast = parse(sourceCode, {
37
+ sourceType: 'module',
38
+ plugins: ['jsx', 'typescript'],
39
+ })
40
+
41
+ const babelPlugin = getBabelPlugin()
42
+
43
+ const out = transformFromAstSync(ast, sourceCode, {
44
+ plugins: [[babelPlugin, options]],
45
+ configFile: false,
46
+ sourceFileName,
47
+ filename: sourceFileName,
48
+ })
49
+
50
+ if (!out) {
51
+ throw new Error(`No output returned`)
52
+ }
53
+
54
+ return out
55
+ }
56
+
57
+ export function getBabelPlugin() {
58
+ return declare((api, options: GuiOptions) => {
59
+ api.assertVersion(7)
60
+ return getBabelParseDefinition(options)
61
+ })
62
+ }
63
+
64
+ export function getBabelParseDefinition(options: GuiOptions) {
65
+ return {
66
+ name: 'hanzo-gui',
67
+
68
+ visitor: {
69
+ Program: {
70
+ enter(this: any, root) {
71
+ let sourcePath = this.file.opts.filename
72
+ if (sourcePath?.includes('node_modules')) {
73
+ return
74
+ }
75
+ // by default only pick up .jsx / .tsx
76
+ if (!sourcePath?.endsWith('.jsx') && !sourcePath?.endsWith('.tsx')) {
77
+ return
78
+ }
79
+
80
+ // this filename comes back incorrect in react-native, it adds /ios/ for some reason
81
+ // adding a fix here, but it's a bit tentative...
82
+ if (process.env.SOURCE_ROOT?.endsWith('ios')) {
83
+ sourcePath = sourcePath.replace('/ios', '')
84
+ }
85
+
86
+ let hasImportedView = false
87
+ let hasImportedViewWrapper = false
88
+ let wrapperCount = 0
89
+ const sheetStyles = {}
90
+ const sheetIdentifier = root.scope.generateUidIdentifier('sheet')
91
+
92
+ // babel doesnt append the `//` so we need to
93
+ const firstCommentContents = // join because you can join together multiple pragmas
94
+ root.node.body[0]?.leadingComments
95
+ ?.map((comment) => comment?.value || ' ')
96
+ .join(' ') ?? ''
97
+ const firstComment = firstCommentContents ? `//${firstCommentContents}` : ''
98
+
99
+ const { shouldPrintDebug, shouldDisable } = getPragmaOptions({
100
+ source: firstComment,
101
+ path: sourcePath,
102
+ })
103
+
104
+ if (shouldDisable) {
105
+ return
106
+ }
107
+
108
+ if (!options.config && !options.components) {
109
+ // if no config/components given try and load from the gui.build.ts file
110
+ guiBuildOptionsLoaded ||= loadGuiBuildConfigSync({})
111
+ }
112
+
113
+ const finalOptions = {
114
+ // @ts-ignore just in case they leave it out
115
+ platform: 'native',
116
+ ...guiBuildOptionsLoaded,
117
+ ...options,
118
+ } satisfies GuiOptions
119
+
120
+ const printLog = createLogger(sourcePath, finalOptions)
121
+
122
+ function addSheetStyle(style: any, node: t.JSXOpeningElement) {
123
+ const styleIndex = `${Object.keys(sheetStyles).length}`
124
+ let key = `${styleIndex}`
125
+ if (process.env.NODE_ENV === 'development') {
126
+ const lineNumbers = node.loc
127
+ ? node.loc.start.line +
128
+ (node.loc.start.line !== node.loc.end.line
129
+ ? `-${node.loc.end.line}`
130
+ : '')
131
+ : ''
132
+ key += `:${basename(sourcePath)}:${lineNumbers}`
133
+ }
134
+ sheetStyles[key] = style
135
+ return readStyleExpr(key)
136
+ }
137
+
138
+ function readStyleExpr(key: string) {
139
+ return template(`SHEET['KEY']`)({
140
+ SHEET: sheetIdentifier.name,
141
+ KEY: key,
142
+ })['expression'] as t.MemberExpression
143
+ }
144
+
145
+ let res
146
+
147
+ try {
148
+ res = extractor.parseSync(root, {
149
+ importsWhitelist: ['constants.js', 'colors.js'],
150
+ excludeProps: new Set([
151
+ 'className',
152
+ 'userSelect',
153
+ 'whiteSpace',
154
+ 'textOverflow',
155
+ 'cursor',
156
+ 'contain',
157
+ ]),
158
+ // native props that should pass through without preventing extraction
159
+ inlineProps: new Set([
160
+ 'testID',
161
+ 'nativeID',
162
+ 'accessibilityLabel',
163
+ 'accessibilityHint',
164
+ 'accessibilityRole',
165
+ 'accessibilityState',
166
+ 'accessibilityValue',
167
+ 'accessibilityActions',
168
+ 'accessibilityLabelledBy',
169
+ 'accessibilityLiveRegion',
170
+ 'accessibilityElementsHidden',
171
+ 'accessibilityViewIsModal',
172
+ 'importantForAccessibility',
173
+ 'onAccessibilityAction',
174
+ 'onAccessibilityEscape',
175
+ 'onAccessibilityTap',
176
+ 'onMagicTap',
177
+ 'collapsable',
178
+ 'needsOffscreenAlphaCompositing',
179
+ 'removeClippedSubviews',
180
+ 'renderToHardwareTextureAndroid',
181
+ 'shouldRasterizeIOS',
182
+ 'hitSlop',
183
+ 'pointerEvents',
184
+ ]),
185
+ shouldPrintDebug,
186
+ ...finalOptions,
187
+ // disable extracting variables as no native concept of them (only theme values)
188
+ disableExtractVariables: false,
189
+ sourcePath,
190
+
191
+ // disabling flattening for now
192
+ // it's flattening a plain <Paragraph>hello</Paragraph> which breaks things because themes
193
+ // thinking it's not really worth the effort to do much compilation on native
194
+ // for now just disable flatten as it can only run in narrow places on native
195
+ // disableFlattening: 'styled',
196
+
197
+ getFlattenedNode({ isTextView }) {
198
+ if (!hasImportedView) {
199
+ hasImportedView = true
200
+ root.unshiftContainer('body', importNativeView())
201
+ }
202
+ return isTextView ? '__ReactNativeText' : '__ReactNativeView'
203
+ },
204
+
205
+ onExtractTag(props) {
206
+ assertValidTag(props.node)
207
+ const stylesExpr = t.arrayExpression([])
208
+ const hocStylesExpr = t.arrayExpression([])
209
+ const expressions: t.Expression[] = []
210
+ const finalAttrs: (t.JSXAttribute | t.JSXSpreadAttribute)[] = []
211
+ const themeKeysUsed = new Set<string>()
212
+
213
+ function getStyleExpression(style: object | null) {
214
+ if (!style) return
215
+
216
+ // split theme properties and leave them as props since RN has no concept of theme
217
+ const { plain, themed } = splitThemeStyles(style)
218
+
219
+ // TODO: themed is not a good name, because it's not just theme it also includes tokens
220
+ let themeExpr: t.ObjectExpression | null = null
221
+ if (themed) {
222
+ for (const key in themed) {
223
+ themeKeysUsed.add(themed[key].split('$')[1])
224
+ }
225
+
226
+ // make a sub-array
227
+ themeExpr = getThemedStyleExpression(themed)
228
+ }
229
+ const ident = addSheetStyle(plain, props.node)
230
+ if (themeExpr) {
231
+ addStyleExpression(ident)
232
+ addStyleExpression(ident, true)
233
+ return themeExpr
234
+ }
235
+ // since we only do flattened disabling this path
236
+ return ident
237
+ }
238
+
239
+ function addStyleExpression(expr: any, HOC = false) {
240
+ if (Array.isArray(expr)) {
241
+ ;(HOC ? hocStylesExpr : stylesExpr).elements.push(...expr)
242
+ } else {
243
+ ;(HOC ? hocStylesExpr : stylesExpr).elements.push(expr)
244
+ }
245
+ }
246
+
247
+ function getThemedStyleExpression(styles: object) {
248
+ const themedStylesAst = literalToAst(styles) as t.ObjectExpression
249
+ themedStylesAst.properties.forEach((_) => {
250
+ const prop = _ as t.ObjectProperty
251
+ if (prop.value.type === 'StringLiteral') {
252
+ const propVal = prop.value.value.slice(1)
253
+ const isComputed = !t.isValidIdentifier(propVal)
254
+ prop.value = t.callExpression(
255
+ t.memberExpression(
256
+ t.memberExpression(
257
+ t.identifier('theme'),
258
+ isComputed ? t.stringLiteral(propVal) : t.identifier(propVal),
259
+ isComputed
260
+ ),
261
+ t.identifier('get')
262
+ ),
263
+ []
264
+ )
265
+ }
266
+ })
267
+ return themedStylesAst
268
+ }
269
+
270
+ let hasDynamicStyle = false
271
+
272
+ for (const attr of props.attrs) {
273
+ switch (attr.type) {
274
+ case 'style': {
275
+ let styleExpr = getStyleExpression(attr.value)
276
+ addStyleExpression(styleExpr)
277
+ addStyleExpression(styleExpr, true)
278
+ break
279
+ }
280
+
281
+ case 'ternary': {
282
+ const { consequent, alternate } = attr.value
283
+ const consExpr = getStyleExpression(consequent)
284
+ const altExpr = getStyleExpression(alternate)
285
+
286
+ expressions.push(attr.value.test)
287
+ addStyleExpression(
288
+ t.conditionalExpression(
289
+ t.identifier(`_expressions[${expressions.length - 1}]`),
290
+ consExpr || t.nullLiteral(),
291
+ altExpr || t.nullLiteral()
292
+ ),
293
+ true
294
+ )
295
+
296
+ const styleExpr = t.conditionalExpression(
297
+ attr.value.test,
298
+ consExpr || t.nullLiteral(),
299
+ altExpr || t.nullLiteral()
300
+ )
301
+ addStyleExpression(styleExpr)
302
+ break
303
+ }
304
+
305
+ case 'attr': {
306
+ if (t.isJSXSpreadAttribute(attr.value)) {
307
+ if (isSimpleSpread(attr.value)) {
308
+ stylesExpr.elements.push(
309
+ t.memberExpression(attr.value.argument, t.identifier('style'))
310
+ )
311
+ hocStylesExpr.elements.push(
312
+ t.memberExpression(attr.value.argument, t.identifier('style'))
313
+ )
314
+ }
315
+ }
316
+ finalAttrs.push(attr.value)
317
+ break
318
+ }
319
+ }
320
+ }
321
+
322
+ props.node.attributes = finalAttrs
323
+
324
+ if (
325
+ themeKeysUsed.size ||
326
+ hocStylesExpr.elements.length > 1 ||
327
+ hasDynamicStyle
328
+ ) {
329
+ if (!hasImportedViewWrapper) {
330
+ root.unshiftContainer('body', importWithStyle)
331
+ hasImportedViewWrapper = true
332
+ }
333
+
334
+ const name = props.flatNodeName || props.node.name['name']
335
+ // Use a unique name that won't conflict with the base component
336
+ const wrapperName = `_${name.replace(/^_+/, '')}Styled${wrapperCount++}`
337
+ // Use regular identifier for variable declarations, JSX identifier for JSX elements
338
+ const WrapperIdentifier = t.identifier(wrapperName)
339
+ const WrapperJSXIdentifier = t.jsxIdentifier(wrapperName)
340
+
341
+ root.pushContainer(
342
+ 'body',
343
+ t.variableDeclaration('const', [
344
+ t.variableDeclarator(
345
+ WrapperIdentifier,
346
+ t.callExpression(t.identifier('_withStableStyle'), [
347
+ t.identifier(name),
348
+ t.arrowFunctionExpression(
349
+ [t.identifier('theme'), t.identifier('_expressions')],
350
+ // return styles directly - no useMemo, theme changes must trigger style recalc
351
+ t.arrayExpression([...hocStylesExpr.elements])
352
+ ),
353
+ ])
354
+ ),
355
+ ])
356
+ )
357
+
358
+ // @ts-ignore - use JSX identifier for JSX elements
359
+ props.node.name = WrapperJSXIdentifier
360
+ // Also set the opening element directly via the path
361
+ props.jsxPath.node.openingElement.name = WrapperJSXIdentifier
362
+ if (props.jsxPath.node.closingElement) {
363
+ // @ts-ignore
364
+ props.jsxPath.node.closingElement.name = t.jsxIdentifier(wrapperName)
365
+ }
366
+
367
+ if (expressions.length) {
368
+ props.node.attributes.push(
369
+ t.jsxAttribute(
370
+ t.jsxIdentifier('_expressions'),
371
+ t.jsxExpressionContainer(t.arrayExpression(expressions))
372
+ )
373
+ )
374
+ }
375
+ } else {
376
+ props.node.attributes.push(
377
+ t.jsxAttribute(
378
+ t.jsxIdentifier('style'),
379
+ t.jsxExpressionContainer(
380
+ stylesExpr.elements.length === 1
381
+ ? (stylesExpr.elements[0] as any)
382
+ : stylesExpr
383
+ )
384
+ )
385
+ )
386
+ }
387
+ },
388
+ })
389
+ } catch (err) {
390
+ if (err instanceof Error) {
391
+ // metro doesn't show stack so we can
392
+ let message = `${shouldPrintDebug === 'verbose' ? err : err.message}`
393
+ if (message.includes('Unexpected return value from visitor method')) {
394
+ message = 'Unexpected return value from visitor method'
395
+ }
396
+ console.warn('Error in Hanzo GUI parse, skipping', message, err.stack)
397
+ return
398
+ }
399
+ }
400
+
401
+ if (!Object.keys(sheetStyles).length) {
402
+ if (shouldPrintDebug) {
403
+ console.info('END no styles')
404
+ }
405
+ if (res) printLog(res)
406
+ return
407
+ }
408
+
409
+ const sheetObject = literalToAst(sheetStyles)
410
+ const sheetOuter = template(
411
+ 'const SHEET = __ReactNativeStyleSheet.create(null)'
412
+ )({
413
+ SHEET: sheetIdentifier.name,
414
+ }) as any
415
+
416
+ // replace the null with our object
417
+ sheetOuter.declarations[0].init.arguments[0] = sheetObject
418
+ root.unshiftContainer('body', sheetOuter)
419
+ // add import
420
+ root.unshiftContainer('body', importStyleSheet())
421
+
422
+ if (shouldPrintDebug) {
423
+ console.info('\n -------- output code ------- \n')
424
+ console.info(
425
+ generator(root.parent)
426
+ .code.split('\n')
427
+ .filter((x) => !x.startsWith('//'))
428
+ .join('\n')
429
+ )
430
+ }
431
+
432
+ if (res) printLog(res)
433
+ },
434
+ },
435
+ },
436
+ }
437
+ }
438
+
439
+ function assertValidTag(node: t.JSXOpeningElement) {
440
+ if (node.attributes.find((x) => x.type === 'JSXAttribute' && x.name.name === 'style')) {
441
+ // we can just deopt here instead and log warning
442
+ // need to make onExtractTag have a special catch error or similar
443
+ if (process.env.DEBUG?.startsWith('@hanzo/gui')) {
444
+ console.warn('⚠️ Cannot pass style attribute to extracted style')
445
+ }
446
+ }
447
+ }
448
+
449
+ function splitThemeStyles(style: object) {
450
+ const themed: object = {}
451
+ const plain: object = {}
452
+ let noTheme = true
453
+ for (const key in style) {
454
+ const val = style[key]
455
+ if (val && val[0] === '$') {
456
+ themed[key] = val
457
+ noTheme = false
458
+ } else {
459
+ plain[key] = val
460
+ }
461
+ }
462
+ return { themed: noTheme ? null : themed, plain }
463
+ }
@@ -0,0 +1,24 @@
1
+ import type { NodePath } from '@babel/traverse'
2
+ import type * as t from '@babel/types'
3
+
4
+ export function findTopmostFunction(jsxPath: NodePath<t.JSXElement>) {
5
+ // get topmost fn
6
+ const isFunction = (path: NodePath<any>) =>
7
+ path.isArrowFunctionExpression() ||
8
+ path.isFunctionDeclaration() ||
9
+ path.isFunctionExpression()
10
+ let compFn: NodePath<any> | null = jsxPath.findParent(isFunction)
11
+ while (compFn) {
12
+ const parent = compFn.findParent(isFunction)
13
+ if (parent) {
14
+ compFn = parent
15
+ } else {
16
+ break
17
+ }
18
+ }
19
+ if (!compFn) {
20
+ // console.error(`Couldn't find a topmost function for media query extraction`)
21
+ return null
22
+ }
23
+ return compFn
24
+ }
@@ -0,0 +1,41 @@
1
+ import * as t from '@babel/types'
2
+
3
+ // TODO: open a PR upstream
4
+ declare module '@babel/types' {
5
+ export function toIdentifier(input: string): string
6
+ }
7
+
8
+ // A clone of path.scope.generateUid that doesn't prepend underscores
9
+ export function generateUid(scope: any, name: string): string {
10
+ if (!(typeof scope === 'object'))
11
+ throw 'generateUid expects a scope object as its first parameter'
12
+ if (!(typeof name === 'string' && name !== ''))
13
+ throw 'generateUid expects a valid name as its second parameter'
14
+
15
+ name = t
16
+ .toIdentifier(name)
17
+ .replace(/^_+/, '')
18
+ .replace(/[0-9]+$/g, '')
19
+
20
+ let uid
21
+ let i = 0
22
+ do {
23
+ if (i > 1) {
24
+ uid = name + i
25
+ } else {
26
+ uid = name
27
+ }
28
+ i++
29
+ } while (
30
+ scope.hasLabel(uid) ||
31
+ scope.hasBinding(uid) ||
32
+ scope.hasGlobal(uid) ||
33
+ scope.hasReference(uid)
34
+ )
35
+
36
+ const program = scope.getProgramParent()
37
+ program.references[uid] = true
38
+ program.uids[uid] = true
39
+
40
+ return uid
41
+ }
@@ -0,0 +1,24 @@
1
+ import { isAbsolute, join } from 'node:path'
2
+
3
+ import { statSync } from 'node:fs'
4
+ import type { GuiOptions } from '../types'
5
+
6
+ export function getGuiConfigPathFromOptionsConfig(
7
+ config: NonNullable<GuiOptions['config']>
8
+ ) {
9
+ if (isAbsolute(config)) {
10
+ return config
11
+ }
12
+
13
+ const fullPath = join(process.cwd(), config)
14
+
15
+ try {
16
+ if (statSync(fullPath).isFile()) {
17
+ return fullPath
18
+ }
19
+ } catch {
20
+ //
21
+ }
22
+
23
+ return config
24
+ }
@@ -0,0 +1,9 @@
1
+ import { Color, colorString } from '@hanzogui/cli-color'
2
+ import type { GuiOptions } from '../types'
3
+
4
+ export function getPrefixLogs(options?: GuiOptions) {
5
+ return (
6
+ options?.prefixLogs ??
7
+ ` 🐥 [hanzo-gui] ${colorString(Color.FgYellow, options?.platform || 'web')}`
8
+ )
9
+ }
@@ -0,0 +1,95 @@
1
+ import generate from '@babel/generator'
2
+ import * as t from '@babel/types'
3
+
4
+ import { accessSafe } from './accessSafe'
5
+
6
+ /**
7
+ * getPropValueFromAttributes gets a prop by name from a list of attributes and accounts for potential spread operators.
8
+ * Here's an example. Given this component:
9
+ * ```
10
+ * <Block coolProp="wow" {...spread1} neatProp="ok" {...spread2} />```
11
+ * getPropValueFromAttributes will return the following:
12
+ * - for propName `coolProp`:
13
+ * ```
14
+ * accessSafe(spread1, 'coolProp') || accessSafe(spread2, 'coolProp') || 'wow'```
15
+ * - for propName `neatProp`:
16
+ * ```
17
+ * accessSafe(spread2, 'neatProp') || 'ok'```
18
+ * - for propName `notPresent`: `null`
19
+ *
20
+ * The returned value should (obviously) be placed after spread operators.
21
+ */
22
+ export function getPropValueFromAttributes(
23
+ propName: string,
24
+ attrs: (t.JSXAttribute | t.JSXSpreadAttribute)[]
25
+ ): t.Expression | null {
26
+ let propIndex = -1
27
+ let jsxAttr: t.JSXAttribute | null = null
28
+ for (let idx = -1, len = attrs.length; ++idx < len; ) {
29
+ const attr = attrs[idx]
30
+ if (t.isJSXAttribute(attr) && attr.name && attr.name.name === propName) {
31
+ propIndex = idx
32
+ jsxAttr = attr
33
+ break
34
+ }
35
+ }
36
+
37
+ if (!jsxAttr || jsxAttr.value == null) {
38
+ return null
39
+ }
40
+
41
+ let propValue:
42
+ | t.JSXElement
43
+ | t.JSXFragment
44
+ | t.StringLiteral
45
+ | t.JSXExpressionContainer
46
+ | t.JSXEmptyExpression
47
+ | t.Expression = jsxAttr.value
48
+
49
+ if (t.isJSXExpressionContainer(propValue)) {
50
+ propValue = propValue.expression
51
+ }
52
+
53
+ // TODO how to handle this??
54
+ if (t.isJSXEmptyExpression(propValue)) {
55
+ console.error('encountered JSXEmptyExpression')
56
+ return null
57
+ }
58
+
59
+ // filter out spread props that occur before propValue
60
+ const applicableSpreads = attrs
61
+ .filter(
62
+ // 1. idx is greater than propValue prop index
63
+ // 2. attr is a spread operator
64
+ (attr, idx): attr is t.JSXSpreadAttribute => {
65
+ if (t.isJSXSpreadAttribute(attr)) {
66
+ if (t.isIdentifier(attr.argument) || t.isMemberExpression(attr.argument)) {
67
+ return idx > propIndex
68
+ }
69
+ if (t.isLogicalExpression(attr.argument)) {
70
+ return false
71
+ }
72
+ throw new Error(
73
+ `unsupported spread of type "${attr.argument.type}": ${
74
+ // @ts-ignore
75
+ generate(attr as any).code
76
+ }`
77
+ )
78
+ }
79
+ return false
80
+ }
81
+ )
82
+ .map((attr) => attr.argument)
83
+
84
+ // if spread operators occur after propValue, create a binary expression for each operator
85
+ // i.e. before1.propValue || before2.propValue || propValue
86
+ // TODO: figure out how to do this without all the extra parens
87
+ if (applicableSpreads.length > 0) {
88
+ propValue = applicableSpreads.reduce<t.Expression>(
89
+ (acc, val) => t.logicalExpression('||', accessSafe(val, propName), acc),
90
+ propValue
91
+ )
92
+ }
93
+
94
+ return propValue
95
+ }