@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,631 @@
1
+ import generate from '@babel/generator'
2
+ import type { NodePath } from '@babel/traverse'
3
+ import * as t from '@babel/types'
4
+ import { mergeProps, StyleObjectIdentifier, StyleObjectRules } from '@hanzogui/web'
5
+ import * as path from 'node:path'
6
+ import * as util from 'node:util'
7
+ import { requireGuiCore } from '../helpers/requireGuiCore'
8
+ import type { StyleObject, GuiOptions, Ternary } from '../types'
9
+ import { babelParse } from './babelParse'
10
+ import type { Extractor } from './createExtractor'
11
+ import { createLogger } from './createLogger'
12
+ import { extractMediaStyle } from './extractMediaStyle'
13
+ import { normalizeTernaries } from './normalizeTernaries'
14
+ import {
15
+ forwardFontFamilyName,
16
+ getFontFamilyNameFromProps,
17
+ } from './propsToFontFamilyCache'
18
+ import { timer } from './timer'
19
+ import { BailOptimizationError } from './errors'
20
+ import { concatClassName } from './concatClassName'
21
+
22
+ export type ExtractedResponse = {
23
+ js: string | Buffer
24
+ styles: string
25
+ stylesPath?: string
26
+ ast: t.File
27
+ map: any
28
+ stats: {
29
+ styled: number
30
+ flattened: number
31
+ optimized: number
32
+ found: number
33
+ }
34
+ }
35
+
36
+ export type ExtractToClassNamesProps = {
37
+ extractor: Extractor
38
+ source: string | Buffer
39
+ sourcePath?: string
40
+ options: GuiOptions
41
+ shouldPrintDebug: boolean | 'verbose'
42
+ }
43
+
44
+ // we only expand into ternaries or plain attr, all style is turned into a always-true ternary
45
+ // this lets us more easily combine everything easily
46
+ // all ternaries in this array ONLY have consequent, they are normalized
47
+ const remove = () => {} // we dont remove after this step
48
+ const spaceString = t.stringLiteral(' ')
49
+
50
+ export async function extractToClassNames({
51
+ extractor,
52
+ source,
53
+ sourcePath = '',
54
+ options,
55
+ shouldPrintDebug,
56
+ }: ExtractToClassNamesProps): Promise<ExtractedResponse | null> {
57
+ const tm = timer()
58
+ const { getCSSStylesAtomic, createMediaStyle } = requireGuiCore('web')
59
+
60
+ if (sourcePath.includes('node_modules')) {
61
+ return null
62
+ }
63
+
64
+ if (shouldPrintDebug) {
65
+ console.warn(`--- ${sourcePath} --- \n\n`)
66
+ }
67
+
68
+ if (typeof source !== 'string') {
69
+ throw new Error('`source` must be a string of javascript')
70
+ }
71
+ if (!path.isAbsolute(sourcePath)) {
72
+ throw new Error(
73
+ '`sourcePath` must be an absolute path to a .js file, got: ' + sourcePath
74
+ )
75
+ }
76
+
77
+ if (!/.[tj]sx?$/i.test(sourcePath || '')) {
78
+ console.warn(`${sourcePath.slice(0, 100)} - bad filename.`)
79
+ }
80
+
81
+ if (!options.disableExtraction && !options['_disableLoadGui']) {
82
+ // dont include loading in timing of parsing (one time cost)
83
+ await extractor.loadGui(options)
84
+ }
85
+
86
+ const printLog = createLogger(sourcePath, options)
87
+
88
+ // Using a map for (officially supported) guaranteed insertion order
89
+ let ast: t.File
90
+
91
+ try {
92
+ ast = babelParse(source, sourcePath)
93
+ } catch (err) {
94
+ console.error('babel parse error:', sourcePath.slice(0, 100))
95
+ throw err
96
+ }
97
+
98
+ tm.mark(`babel-parse`, shouldPrintDebug === 'verbose')
99
+
100
+ const cssMap = new Map<string, { css: string; commentTexts: string[] }>()
101
+ const guiConfig = extractor.getGui()!
102
+
103
+ const res = await extractor.parse(ast, {
104
+ shouldPrintDebug,
105
+ ...options,
106
+ platform: 'web',
107
+ sourcePath,
108
+ extractStyledDefinitions: true,
109
+ onStyledDefinitionRule(identifier, rules) {
110
+ const css = rules.join('\n')
111
+ if (shouldPrintDebug) {
112
+ console.info(`adding styled() rule: .${identifier} ${css}`)
113
+ }
114
+ cssMap.set(`.${identifier}`, { css, commentTexts: [] })
115
+ },
116
+ getFlattenedNode: ({ tag }) => {
117
+ return tag
118
+ },
119
+ onExtractTag: ({
120
+ parserProps,
121
+ attrs,
122
+ node,
123
+ attemptEval,
124
+ jsxPath,
125
+ originalNodeName,
126
+ filePath,
127
+ lineNumbers,
128
+ staticConfig,
129
+ }) => {
130
+ // bail out of views that don't accept className (falls back to runtime + style={})
131
+ if (staticConfig.acceptsClassName === false) {
132
+ throw new BailOptimizationError()
133
+ }
134
+
135
+ // re-worked how we do this
136
+ // merging ternaries on top of base styles is not simple, because we need to ensure the final
137
+ // className has no duplicate style props and selector order is preserved
138
+ // before we tried to be smart and build a big binary expression
139
+ // instead, what we'll do now is pre-calculate the entire className for every possible path
140
+ // for super complex components that means we *will* output a lot of bigger classNames
141
+ // but its so much simpler than trying to implement a multi-stage solver here
142
+ // and in the end its just strings that gzip very well
143
+ // its also much easier to intuit/debug for end users and ourselves
144
+
145
+ // example:
146
+ // a ? 'a' : 'b'
147
+ // b ? 'c' : 'd'
148
+ // we want:
149
+ // a && b ? 'a c' : ''
150
+ // !a && b ? 'b c' : ''
151
+ // a && !b ? 'a d' : ''
152
+ // !a && !b ? 'b d' : ''
153
+
154
+ // we also simplified the compiler to only handle views that can be fully flattened
155
+ // this means we don't need to account for strange in-between spreads, so we can merge things
156
+ // fairly simply. first, we just merge forward all the non-ternary styles into ternaries.
157
+
158
+ // save for the end
159
+ const finalAttrs: t.JSXAttribute[] = []
160
+
161
+ let mergeForwardBaseStyle: object | null = null
162
+ let attrClassName: t.Expression | null = null
163
+ let baseFontFamily = ''
164
+ let mediaStylesSeen = 1
165
+
166
+ const comment = util.format(
167
+ '/* %s:%s (%s) */',
168
+ filePath,
169
+ lineNumbers,
170
+ originalNodeName
171
+ )
172
+
173
+ function addStyle(style: StyleObject) {
174
+ const identifier = style[StyleObjectIdentifier]
175
+ const rules = style[StyleObjectRules]
176
+ const selector = `.${identifier}`
177
+ if (cssMap.has(selector)) {
178
+ const val = cssMap.get(selector)!
179
+ val.commentTexts.push(comment)
180
+ } else if (rules.length) {
181
+ cssMap.set(selector, {
182
+ css: rules.join('\n'),
183
+ commentTexts: [comment],
184
+ })
185
+ }
186
+ return identifier
187
+ }
188
+
189
+ function addStyles(style: object) {
190
+ const cssStyles = getCSSStylesAtomic(style as any)
191
+ const classNames: string[] = []
192
+
193
+ for (const style of cssStyles) {
194
+ const property = style[0]
195
+ const mediaName = property.slice(1)
196
+
197
+ // $group- styles must bail out entirely - they need runtime handling because
198
+ // group changes can affect children that may be animated and need hard values.
199
+ // In the future, CSS animation drivers could potentially optimize this.
200
+ if (mediaName.startsWith('group-')) {
201
+ throw new BailOptimizationError()
202
+ }
203
+
204
+ // Check for theme/platform media queries (e.g., $theme-dark, $platform-web)
205
+ const mediaTypeMatch = mediaName.match(/^(theme|platform)-/)
206
+ if (mediaTypeMatch) {
207
+ const mediaType = mediaTypeMatch[1] as 'theme' | 'platform'
208
+ const mediaStyle = createMediaStyle(
209
+ style,
210
+ mediaName,
211
+ extractor.getGui()!.media,
212
+ mediaType,
213
+ false,
214
+ mediaStylesSeen
215
+ )
216
+ const identifier = addStyle(mediaStyle)
217
+ classNames.push(identifier)
218
+ continue
219
+ }
220
+
221
+ if (mediaName in guiConfig.media) {
222
+ const mediaStyle = createMediaStyle(
223
+ style,
224
+ mediaName,
225
+ extractor.getGui()!.media,
226
+ true,
227
+ false,
228
+ mediaStylesSeen
229
+ )
230
+ const identifier = addStyle(mediaStyle)
231
+ classNames.push(identifier)
232
+ continue
233
+ }
234
+
235
+ const identifier = addStyle(style)
236
+ classNames.push(identifier)
237
+ }
238
+
239
+ return classNames
240
+ }
241
+
242
+ const onlyTernaries: Ternary[] = attrs.flatMap((attr) => {
243
+ if (attr.type === 'attr') {
244
+ const value = attr.value
245
+
246
+ if (t.isJSXSpreadAttribute(value)) {
247
+ // we only handle flattened stuff now so skip this
248
+ console.error(`Should never happen`)
249
+ return []
250
+ }
251
+
252
+ if (value.name.name === 'className') {
253
+ let inner: any = value.value
254
+ if (t.isJSXExpressionContainer(inner)) {
255
+ inner = inner.expression
256
+ }
257
+ try {
258
+ const evaluatedValue = inner ? attemptEval(inner) : null
259
+ if (typeof evaluatedValue === 'string') {
260
+ attrClassName = t.stringLiteral(evaluatedValue)
261
+ }
262
+ } catch (e) {
263
+ if (inner) {
264
+ attrClassName ||= inner
265
+ }
266
+ }
267
+ return []
268
+ }
269
+
270
+ finalAttrs.push(value)
271
+ return []
272
+ }
273
+
274
+ if (attr.type === 'style') {
275
+ mergeForwardBaseStyle = mergeProps(mergeForwardBaseStyle || {}, attr.value)
276
+ baseFontFamily = getFontFamilyNameFromProps(attr.value) || ''
277
+ return []
278
+ }
279
+
280
+ let ternary = attr.value
281
+
282
+ if (ternary.inlineMediaQuery) {
283
+ const mediaExtraction = extractMediaStyle(
284
+ parserProps,
285
+ attr.value,
286
+ jsxPath,
287
+ extractor.getGui()!,
288
+ sourcePath || '',
289
+ mediaStylesSeen++,
290
+ shouldPrintDebug
291
+ )
292
+
293
+ if (mediaExtraction) {
294
+ if (mediaExtraction.mediaStyles) {
295
+ mergeForwardBaseStyle = mergeProps(mergeForwardBaseStyle || {}, {
296
+ [`$${ternary.inlineMediaQuery}`]: attr.value.consequent!,
297
+ })
298
+ }
299
+ if (mediaExtraction.ternaryWithoutMedia) {
300
+ ternary = mediaExtraction.ternaryWithoutMedia
301
+ } else {
302
+ return []
303
+ }
304
+ }
305
+ }
306
+
307
+ let mergedAlternate
308
+ let mergedConsequent
309
+
310
+ if (ternary.alternate && Object.keys(ternary.alternate).length) {
311
+ mergedAlternate = mergeProps(
312
+ mergeForwardBaseStyle || {},
313
+ ternary.alternate || {}
314
+ )
315
+ forwardFontFamilyName(ternary.alternate, mergedAlternate, baseFontFamily)
316
+ }
317
+
318
+ if (ternary.consequent && Object.keys(ternary.consequent).length) {
319
+ mergedConsequent = mergeProps(
320
+ mergeForwardBaseStyle || {},
321
+ ternary.consequent || {}
322
+ )
323
+ forwardFontFamilyName(ternary.consequent, mergedConsequent, baseFontFamily)
324
+ }
325
+
326
+ // merge the base style forward into both sides
327
+ return {
328
+ ...ternary,
329
+ alternate: mergedAlternate,
330
+ consequent: mergedConsequent,
331
+ }
332
+ })
333
+
334
+ const hasTernaries = Boolean(onlyTernaries.length)
335
+
336
+ const baseClassNames = mergeForwardBaseStyle
337
+ ? addStyles(mergeForwardBaseStyle)
338
+ : null
339
+
340
+ let baseClassNameStr = !baseClassNames ? '' : baseClassNames.join(' ')
341
+
342
+ if (baseFontFamily) {
343
+ baseClassNameStr = `font_${baseFontFamily}${baseClassNameStr ? ` ${baseClassNameStr}` : ''}`
344
+ }
345
+
346
+ // add is_View or is_Text base class matching runtime behavior
347
+ const baseTypeClass = staticConfig.isText ? 'is_Text' : 'is_View'
348
+ baseClassNameStr = `${baseTypeClass}${baseClassNameStr ? ` ${baseClassNameStr}` : ''}`
349
+
350
+ // add component name class (skip 'Text' since is_Text already covers it)
351
+ const componentNameFinal = staticConfig.componentName
352
+ let base =
353
+ componentNameFinal && componentNameFinal !== 'Text'
354
+ ? t.stringLiteral(
355
+ `is_${componentNameFinal}${baseClassNameStr ? ` ${baseClassNameStr}` : ''}`
356
+ )
357
+ : t.stringLiteral(baseClassNameStr || '')
358
+
359
+ attrClassName = attrClassName as t.Expression | null // actual typescript bug, flatMap doesn't update from never
360
+
361
+ const baseClassNameExpression: t.Expression = (() => {
362
+ if (attrClassName) {
363
+ if (t.isStringLiteral(attrClassName)) {
364
+ return t.stringLiteral(
365
+ base.value ? `${base.value} ${attrClassName.value}` : attrClassName.value
366
+ )
367
+ } else {
368
+ // space after to ensure its a string and its spaced
369
+ return t.binaryExpression(
370
+ '+',
371
+ t.binaryExpression('+', attrClassName, spaceString),
372
+ base
373
+ )
374
+ }
375
+ }
376
+
377
+ return base
378
+ })()
379
+
380
+ const expandedTernaries: Ternary[] = []
381
+
382
+ if (onlyTernaries.length) {
383
+ // normalize tests to reduce duplicates
384
+ const normalizedTernaries = normalizeTernaries(onlyTernaries)
385
+
386
+ for (const ternary of normalizedTernaries) {
387
+ if (!expandedTernaries.length) {
388
+ expandTernary(ternary)
389
+ continue
390
+ }
391
+ // snapshot current array before iterating - expandTernary mutates expandedTernaries
392
+ const prevTernaries = [...expandedTernaries]
393
+ for (const prev of prevTernaries) {
394
+ expandTernary(ternary, prev)
395
+ }
396
+ }
397
+ }
398
+
399
+ function expandTernary(ternary: Ternary, prev?: Ternary) {
400
+ // need to diverge into two (or four if alternate)
401
+ if (ternary.consequent && Object.keys(ternary.consequent).length) {
402
+ const fontFamily = getFontFamilyNameFromProps(ternary.consequent)
403
+
404
+ expandedTernaries.push({
405
+ fontFamily,
406
+ // prevTest && test: merge consequent
407
+ test: prev
408
+ ? t.logicalExpression('&&', prev.test, ternary.test)
409
+ : ternary.test,
410
+ consequent: prev
411
+ ? mergeProps(prev.consequent!, ternary.consequent)
412
+ : ternary.consequent,
413
+ remove,
414
+ alternate: null,
415
+ })
416
+
417
+ if (prev) {
418
+ expandedTernaries.push({
419
+ fontFamily,
420
+ // !prevTest && test: just consequent
421
+ test: t.logicalExpression(
422
+ '&&',
423
+ t.unaryExpression('!', prev.test),
424
+ ternary.test
425
+ ),
426
+ consequent: ternary.consequent,
427
+ alternate: null,
428
+ remove,
429
+ })
430
+ }
431
+ }
432
+
433
+ if (ternary.alternate && Object.keys(ternary.alternate).length) {
434
+ const fontFamily = getFontFamilyNameFromProps(ternary.alternate)
435
+ const negated = t.unaryExpression('!', ternary.test)
436
+ expandedTernaries.push({
437
+ fontFamily,
438
+ // prevTest && !test: merge alternate
439
+ test: prev ? t.logicalExpression('&&', prev.test, negated) : negated,
440
+ consequent: prev
441
+ ? mergeProps(prev.alternate!, ternary.alternate)
442
+ : ternary.alternate,
443
+ remove,
444
+ alternate: null,
445
+ })
446
+
447
+ if (prev) {
448
+ expandedTernaries.push({
449
+ fontFamily,
450
+ test: t.logicalExpression(
451
+ '&&',
452
+ t.unaryExpression('!', prev.test),
453
+ ternary.test
454
+ ),
455
+ consequent: ternary.alternate,
456
+ remove,
457
+ alternate: null,
458
+ })
459
+ }
460
+ }
461
+ }
462
+
463
+ let ternaryClassNameExpr: t.Expression | null = null
464
+
465
+ // next: create all CSS, build className strings and hoist, and create final node with props
466
+ if (hasTernaries) {
467
+ for (const ternary of expandedTernaries) {
468
+ if (!ternary.consequent) continue
469
+ const classNames = addStyles(ternary.consequent)
470
+ if (ternary.fontFamily) {
471
+ classNames.unshift(`font_${ternary.fontFamily}`)
472
+ }
473
+ const baseString = t.isStringLiteral(baseClassNameExpression)
474
+ ? baseClassNameExpression.value
475
+ : ''
476
+
477
+ const fullClassNameWithDups =
478
+ (baseString ? `${baseString} ` : '') + classNames.join(' ')
479
+
480
+ // we concat here as the base could be conditionally overriden by our classNames
481
+ const fullClassName = concatClassName(fullClassNameWithDups)
482
+
483
+ const classNameLiteral = t.stringLiteral(fullClassName)
484
+
485
+ if (!ternaryClassNameExpr) {
486
+ ternaryClassNameExpr = t.conditionalExpression(
487
+ ternary.test,
488
+ classNameLiteral,
489
+ baseClassNameExpression
490
+ )
491
+ } else {
492
+ ternaryClassNameExpr = t.conditionalExpression(
493
+ ternary.test,
494
+ classNameLiteral,
495
+ ternaryClassNameExpr
496
+ )
497
+ }
498
+ }
499
+ }
500
+
501
+ let finalExpression: t.Expression | null =
502
+ ternaryClassNameExpr || baseClassNameExpression || null
503
+
504
+ if (shouldPrintDebug) {
505
+ console.info('attrs', JSON.stringify(attrs, null, 2))
506
+ console.info('expandedTernaries', JSON.stringify(expandedTernaries, null, 2))
507
+ console.info('finalExpression', JSON.stringify(finalExpression, null, 2))
508
+ console.info({ hasTernaries, baseClassNameExpression })
509
+ }
510
+
511
+ if (finalExpression) {
512
+ // hoist to global variables
513
+ finalExpression = hoistClassNames(jsxPath, finalExpression)
514
+
515
+ // console.log('finalExpression', finalExpression)
516
+
517
+ const classNameProp = t.jsxAttribute(
518
+ t.jsxIdentifier('className'),
519
+ t.jsxExpressionContainer(finalExpression!)
520
+ )
521
+ finalAttrs.unshift(classNameProp)
522
+ }
523
+
524
+ node.attributes = finalAttrs
525
+ },
526
+ })
527
+
528
+ if (!res || (!res.modified && !res.optimized && !res.flattened && !res.styled)) {
529
+ if (shouldPrintDebug) {
530
+ console.info('no res or none modified', res)
531
+ }
532
+ return null
533
+ }
534
+
535
+ const styles = Array.from(cssMap.values())
536
+ .map((x) => x.css)
537
+ .join('\n')
538
+ .trim()
539
+
540
+ // @ts-ignore
541
+ const result = generate(
542
+ ast as any,
543
+ {
544
+ concise: false,
545
+ filename: sourcePath,
546
+ // this makes the debug output terrible, and i think sourcemap works already
547
+ retainLines: false,
548
+ sourceFileName: sourcePath,
549
+ sourceMaps: true,
550
+ },
551
+ source
552
+ )
553
+
554
+ if (shouldPrintDebug) {
555
+ console.info(
556
+ '\n -------- output code ------- \n\n',
557
+ result.code
558
+ .split('\n')
559
+ .filter((x) => !x.startsWith('//'))
560
+ .join('\n')
561
+ )
562
+ console.info('\n -------- output style -------- \n\n', styles)
563
+ }
564
+
565
+ printLog(res)
566
+
567
+ return {
568
+ ast,
569
+ styles,
570
+ js: result.code,
571
+ map: result.map,
572
+ stats: {
573
+ styled: res.styled,
574
+ flattened: res.flattened,
575
+ optimized: res.optimized,
576
+ found: res.found,
577
+ },
578
+ }
579
+ }
580
+
581
+ function hoistClassNames(path: NodePath<t.JSXElement>, expr: t.Expression) {
582
+ if (t.isStringLiteral(expr)) {
583
+ return hoistClassName(path, expr.value)
584
+ }
585
+
586
+ if (t.isLogicalExpression(expr)) {
587
+ const left = t.isStringLiteral(expr.left)
588
+ ? hoistClassName(path, expr.left.value)
589
+ : expr.left
590
+ const right = t.isStringLiteral(expr.right)
591
+ ? hoistClassName(path, expr.right.value)
592
+ : hoistClassNames(path, expr.right)
593
+ return t.logicalExpression(expr.operator, left, right)
594
+ }
595
+
596
+ if (t.isConditionalExpression(expr)) {
597
+ const cons = t.isStringLiteral(expr.consequent)
598
+ ? hoistClassName(path, expr.consequent.value)
599
+ : hoistClassNames(path, expr.consequent)
600
+
601
+ const alt = t.isStringLiteral(expr.alternate)
602
+ ? hoistClassName(path, expr.alternate.value)
603
+ : hoistClassNames(path, expr.alternate)
604
+
605
+ return t.conditionalExpression(expr.test, cons, alt)
606
+ }
607
+
608
+ return expr
609
+ }
610
+
611
+ function hoistClassName(path: NodePath<t.JSXElement>, str: string) {
612
+ const uid = path.scope.generateUidIdentifier('cn')
613
+ const parent = path.findParent((path) => path.isProgram())
614
+ if (!parent) throw new Error(`no program?`)
615
+ const variable = t.variableDeclaration('const', [
616
+ t.variableDeclarator(uid, t.stringLiteral(cleanupClassName(str))),
617
+ ])
618
+ // @ts-ignore
619
+ parent.unshiftContainer('body', variable)
620
+ return uid
621
+ }
622
+
623
+ function cleanupClassName(inStr: string) {
624
+ const out = new Set<string>()
625
+ for (const part of inStr.split(' ')) {
626
+ if (!part || part === ' ') continue
627
+ if (part === 'font_') continue
628
+ out.add(part)
629
+ }
630
+ return [...out].join(' ')
631
+ }