@tamagui/static 1.132.16 → 1.132.18

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 (87) hide show
  1. package/dist/extractor/concatClassName.js +69 -0
  2. package/dist/extractor/concatClassName.js.map +6 -0
  3. package/dist/extractor/concatClassName.native.js +69 -0
  4. package/dist/extractor/concatClassName.native.js.map +6 -0
  5. package/dist/extractor/createEvaluator.js +13 -1
  6. package/dist/extractor/createEvaluator.js.map +1 -1
  7. package/dist/extractor/createEvaluator.native.js +6 -1
  8. package/dist/extractor/createEvaluator.native.js.map +2 -2
  9. package/dist/extractor/createExtractor.js +60 -114
  10. package/dist/extractor/createExtractor.js.map +1 -1
  11. package/dist/extractor/createExtractor.native.js +70 -123
  12. package/dist/extractor/createExtractor.native.js.map +2 -2
  13. package/dist/extractor/errors.js +22 -0
  14. package/dist/extractor/errors.js.map +6 -0
  15. package/dist/extractor/errors.native.js +119 -0
  16. package/dist/extractor/errors.native.js.map +6 -0
  17. package/dist/extractor/extractMediaStyle.js +1 -1
  18. package/dist/extractor/extractMediaStyle.js.map +1 -1
  19. package/dist/extractor/extractMediaStyle.native.js +1 -1
  20. package/dist/extractor/extractMediaStyle.native.js.map +2 -2
  21. package/dist/extractor/extractToClassNames.js +212 -172
  22. package/dist/extractor/extractToClassNames.js.map +2 -2
  23. package/dist/extractor/extractToClassNames.native.js +212 -188
  24. package/dist/extractor/extractToClassNames.native.js.map +2 -2
  25. package/dist/extractor/extractToNative.js +47 -78
  26. package/dist/extractor/extractToNative.js.map +1 -1
  27. package/dist/extractor/extractToNative.native.js +23 -39
  28. package/dist/extractor/extractToNative.native.js.map +2 -2
  29. package/dist/extractor/normalizeTernaries.js +5 -3
  30. package/dist/extractor/normalizeTernaries.js.map +1 -1
  31. package/dist/extractor/normalizeTernaries.native.js +5 -3
  32. package/dist/extractor/normalizeTernaries.native.js.map +2 -2
  33. package/dist/extractor/propsToFontFamilyCache.js +7 -8
  34. package/dist/extractor/propsToFontFamilyCache.js.map +1 -1
  35. package/dist/extractor/propsToFontFamilyCache.native.js +9 -10
  36. package/dist/extractor/propsToFontFamilyCache.native.js.map +2 -2
  37. package/dist/registerRequire.js +1 -1
  38. package/dist/registerRequire.js.map +1 -1
  39. package/dist/registerRequire.native.js +1 -1
  40. package/dist/registerRequire.native.js.map +1 -1
  41. package/dist/types.native.js.map +1 -1
  42. package/package.json +15 -15
  43. package/src/extractor/concatClassName.ts +100 -0
  44. package/src/extractor/createEvaluator.ts +26 -1
  45. package/src/extractor/createExtractor.ts +108 -194
  46. package/src/extractor/errors.ts +1 -0
  47. package/src/extractor/extractMediaStyle.ts +1 -1
  48. package/src/extractor/extractToClassNames.ts +377 -264
  49. package/src/extractor/extractToNative.ts +68 -111
  50. package/src/extractor/normalizeTernaries.ts +10 -3
  51. package/src/extractor/propsToFontFamilyCache.ts +5 -5
  52. package/src/registerRequire.ts +1 -1
  53. package/src/types.ts +10 -13
  54. package/types/extractor/concatClassName.d.ts +8 -0
  55. package/types/extractor/concatClassName.d.ts.map +1 -0
  56. package/types/extractor/createEvaluator.d.ts.map +1 -1
  57. package/types/extractor/createExtractor.d.ts.map +1 -1
  58. package/types/extractor/errors.d.ts +3 -0
  59. package/types/extractor/errors.d.ts.map +1 -0
  60. package/types/extractor/extractToClassNames.d.ts.map +1 -1
  61. package/types/extractor/extractToNative.d.ts.map +1 -1
  62. package/types/extractor/normalizeTernaries.d.ts.map +1 -1
  63. package/types/extractor/propsToFontFamilyCache.d.ts +2 -2
  64. package/types/extractor/propsToFontFamilyCache.d.ts.map +1 -1
  65. package/types/types.d.ts +9 -10
  66. package/types/types.d.ts.map +1 -1
  67. package/dist/extractor/buildClassName.js +0 -72
  68. package/dist/extractor/buildClassName.js.map +0 -6
  69. package/dist/extractor/buildClassName.native.js +0 -67
  70. package/dist/extractor/buildClassName.native.js.map +0 -6
  71. package/dist/extractor/ensureImportingConcat.js +0 -50
  72. package/dist/extractor/ensureImportingConcat.js.map +0 -6
  73. package/dist/extractor/ensureImportingConcat.native.js +0 -49
  74. package/dist/extractor/ensureImportingConcat.native.js.map +0 -6
  75. package/dist/extractor/hoistClassNames.js +0 -63
  76. package/dist/extractor/hoistClassNames.js.map +0 -6
  77. package/dist/extractor/hoistClassNames.native.js +0 -66
  78. package/dist/extractor/hoistClassNames.native.js.map +0 -6
  79. package/src/extractor/buildClassName.ts +0 -76
  80. package/src/extractor/ensureImportingConcat.ts +0 -36
  81. package/src/extractor/hoistClassNames.ts +0 -52
  82. package/types/extractor/buildClassName.d.ts +0 -7
  83. package/types/extractor/buildClassName.d.ts.map +0 -1
  84. package/types/extractor/ensureImportingConcat.d.ts +0 -4
  85. package/types/extractor/ensureImportingConcat.d.ts.map +0 -1
  86. package/types/extractor/hoistClassNames.d.ts +0 -6
  87. package/types/extractor/hoistClassNames.d.ts.map +0 -1
@@ -1,30 +1,23 @@
1
- import * as path from 'node:path'
2
- import * as util from 'node:util'
3
-
4
1
  import generate from '@babel/generator'
2
+ import type { NodePath } from '@babel/traverse'
5
3
  import * as t from '@babel/types'
6
- import * as helpers from '@tamagui/helpers'
7
- import type { ViewStyle } from 'react-native'
8
-
4
+ import { mergeProps, StyleObjectIdentifier, StyleObjectRules } from '@tamagui/web'
5
+ import * as path from 'node:path'
6
+ import * as util from 'node:util'
9
7
  import { requireTamaguiCore } from '../helpers/requireTamaguiCore'
10
- import type { ClassNameObject, StyleObject, TamaguiOptions, Ternary } from '../types'
8
+ import type { StyleObject, TamaguiOptions, Ternary } from '../types'
11
9
  import { babelParse } from './babelParse'
12
- import { buildClassName } from './buildClassName'
13
10
  import type { Extractor } from './createExtractor'
14
11
  import { createLogger } from './createLogger'
15
- import { ensureImportingConcat } from './ensureImportingConcat'
16
- import { isSimpleSpread } from './extractHelpers'
17
12
  import { extractMediaStyle } from './extractMediaStyle'
18
- import { hoistClassNames } from './hoistClassNames'
19
- import { getFontFamilyClassNameFromProps } from './propsToFontFamilyCache'
13
+ import { normalizeTernaries } from './normalizeTernaries'
14
+ import {
15
+ forwardFontFamilyName,
16
+ getFontFamilyNameFromProps,
17
+ } from './propsToFontFamilyCache'
20
18
  import { timer } from './timer'
21
-
22
- const mergeStyleGroups = {
23
- shadowOpacity: true,
24
- shadowRadius: true,
25
- shadowColor: true,
26
- shadowOffset: true,
27
- }
19
+ import { BailOptimizationError } from './errors'
20
+ import { concatClassName } from './concatClassName'
28
21
 
29
22
  export type ExtractedResponse = {
30
23
  js: string | Buffer
@@ -42,6 +35,12 @@ export type ExtractToClassNamesProps = {
42
35
  shouldPrintDebug: boolean | 'verbose'
43
36
  }
44
37
 
38
+ // we only expand into ternaries or plain attr, all style is turned into a always-true ternary
39
+ // this lets us more easily combine everything easily
40
+ // all ternaries in this array ONLY have consequent, they are normalized
41
+ const remove = () => {} // we dont remove after this step
42
+ const spaceString = t.stringLiteral(' ')
43
+
45
44
  export async function extractToClassNames({
46
45
  extractor,
47
46
  source,
@@ -50,7 +49,7 @@ export async function extractToClassNames({
50
49
  shouldPrintDebug,
51
50
  }: ExtractToClassNamesProps): Promise<ExtractedResponse | null> {
52
51
  const tm = timer()
53
- const { getCSSStylesAtomic } = requireTamaguiCore('web')
52
+ const { getCSSStylesAtomic, createMediaStyle } = requireTamaguiCore('web')
54
53
 
55
54
  if (sourcePath.includes('node_modules')) {
56
55
  return null
@@ -93,9 +92,7 @@ export async function extractToClassNames({
93
92
  tm.mark(`babel-parse`, shouldPrintDebug === 'verbose')
94
93
 
95
94
  const cssMap = new Map<string, { css: string; commentTexts: string[] }>()
96
- const existingHoists: { [key: string]: t.Identifier } = {}
97
-
98
- let hasFlattened = false
95
+ const tamaguiConfig = extractor.getTamagui()!
99
96
 
100
97
  const res = await extractor.parse(ast, {
101
98
  shouldPrintDebug,
@@ -103,7 +100,7 @@ export async function extractToClassNames({
103
100
  platform: 'web',
104
101
  sourcePath,
105
102
  extractStyledDefinitions: true,
106
- onStyleRule(identifier, rules) {
103
+ onStyledDefinitionRule(identifier, rules) {
107
104
  const css = rules.join(';')
108
105
  if (shouldPrintDebug) {
109
106
  console.info(`adding styled() rule: .${identifier} ${css}`)
@@ -111,7 +108,6 @@ export async function extractToClassNames({
111
108
  cssMap.set(`.${identifier}`, { css, commentTexts: [] })
112
109
  },
113
110
  getFlattenedNode: ({ tag }) => {
114
- hasFlattened = true
115
111
  return tag
116
112
  },
117
113
  onExtractTag: ({
@@ -123,300 +119,364 @@ export async function extractToClassNames({
123
119
  originalNodeName,
124
120
  filePath,
125
121
  lineNumbers,
126
- programPath,
127
- isFlattened,
128
122
  staticConfig,
129
123
  }) => {
130
124
  // bail out of views that don't accept className (falls back to runtime + style={})
131
125
  if (staticConfig.acceptsClassName === false) {
132
- if (shouldPrintDebug) {
133
- console.info(`bail, acceptsClassName is false`)
134
- }
135
- return
126
+ throw new BailOptimizationError()
136
127
  }
137
128
 
138
- // reset hasFlattened
139
- const didFlattenThisTag = hasFlattened
140
- hasFlattened = false
129
+ // re-worked how we do this
130
+ // merging ternaries on top of base styles is not simple, because we need to ensure the final
131
+ // className has no duplicate style props and selector order is preserved
132
+ // before we tried to be smart and build a big binary expression
133
+ // instead, what we'll do now is pre-calculate the entire className for every possible path
134
+ // for super complex components that means we *will* output a lot of bigger classNames
135
+ // but its so much simpler than trying to implement a multi-stage solver here
136
+ // and in the end its just strings that gzip very well
137
+ // its also much easier to intuit/debug for end users and ourselves
138
+
139
+ // example:
140
+ // a ? 'a' : 'b'
141
+ // b ? 'c' : 'd'
142
+ // we want:
143
+ // a && b ? 'a c' : ''
144
+ // !a && b ? 'b c' : ''
145
+ // a && !b ? 'a d' : ''
146
+ // !a && !b ? 'b d' : ''
147
+
148
+ // we also simplified the compiler to only handle views that can be fully flattened
149
+ // this means we don't need to account for strange in-between spreads, so we can merge things
150
+ // fairly simply. first, we just merge forward all the non-ternary styles into ternaries.
151
+
152
+ // save for the end
153
+ const finalAttrs: t.JSXAttribute[] = []
154
+
155
+ let mergeForwardBaseStyle: Object | null = null
156
+ let attrClassName: t.Expression | null = null
157
+ let baseFontFamily = ''
158
+ let mediaStylesSeen = 1
141
159
 
142
- let finalClassNames: ClassNameObject[] = []
143
- const finalAttrs: (t.JSXAttribute | t.JSXSpreadAttribute)[] = []
144
- let finalStyles: StyleObject[] = []
160
+ const comment = util.format(
161
+ '/* %s:%s (%s) */',
162
+ filePath,
163
+ lineNumbers,
164
+ originalNodeName
165
+ )
145
166
 
146
- let viewStyles = {}
147
- for (const attr of attrs) {
148
- if (attr.type === 'style') {
149
- viewStyles = {
150
- ...viewStyles,
151
- ...attr.value,
152
- }
167
+ function addStyle(style: StyleObject) {
168
+ const identifier = style[StyleObjectIdentifier]
169
+ const rules = style[StyleObjectRules]
170
+ const selector = `.${identifier}`
171
+ if (cssMap.has(selector)) {
172
+ const val = cssMap.get(selector)!
173
+ val.commentTexts.push(comment)
174
+ } else if (rules.length) {
175
+ cssMap.set(selector, {
176
+ css: rules.join('\n'),
177
+ commentTexts: [comment],
178
+ })
153
179
  }
180
+ return identifier
154
181
  }
155
182
 
156
- const ensureNeededPrevStyle = (style: ViewStyle) => {
157
- // ensure all group keys are merged
158
- const keys = Object.keys(style)
159
- if (!keys.some((key) => mergeStyleGroups[key])) {
160
- return style
161
- }
162
- for (const k in mergeStyleGroups) {
163
- if (k in viewStyles) {
164
- style[k] = style[k] ?? viewStyles[k]
183
+ function addStyles(style: Object) {
184
+ const cssStyles = getCSSStylesAtomic(style as any)
185
+ const classNames: string[] = []
186
+
187
+ for (const style of cssStyles) {
188
+ const mediaName = style[0].slice(1)
189
+ if (tamaguiConfig.media[mediaName]) {
190
+ const mediaStyle = createMediaStyle(
191
+ style,
192
+ mediaName,
193
+ extractor.getTamagui()!.media,
194
+ true,
195
+ false,
196
+ mediaStylesSeen
197
+ )
198
+ const identifier = addStyle(mediaStyle)
199
+ classNames.push(identifier)
200
+ continue
165
201
  }
166
- }
167
- return style
168
- }
169
202
 
170
- const addStyles = (style: ViewStyle | null): StyleObject[] => {
171
- if (!style) return []
172
- const styleWithPrev = ensureNeededPrevStyle(style)
173
- const res = getCSSStylesAtomic(styleWithPrev as any)
174
- if (res.length) {
175
- finalStyles = [...finalStyles, ...res]
203
+ const identifier = addStyle(style)
204
+ classNames.push(identifier)
176
205
  }
177
- return res
206
+
207
+ return classNames
178
208
  }
179
209
 
180
- // 1 to start above any :hover styles
181
- let lastMediaImportance = 1
182
- for (const attr of attrs) {
183
- switch (attr.type) {
184
- case 'style': {
185
- if (!isFlattened) {
186
- const styles = getCSSStylesAtomic(attr.value as any)
187
-
188
- finalStyles = [...finalStyles, ...styles]
189
-
190
- for (const style of styles) {
191
- // leave them as attributes
192
- const prop = style[helpers.StyleObjectPseudo]
193
- ? `${style[helpers.StyleObjectProperty]}-${
194
- style[helpers.StyleObjectPseudo]
195
- }`
196
- : style[helpers.StyleObjectProperty]
197
- finalAttrs.push(
198
- t.jsxAttribute(
199
- t.jsxIdentifier(prop),
200
- t.stringLiteral(style[helpers.StyleObjectIdentifier])
201
- )
202
- )
203
- }
204
- } else {
205
- const styles = addStyles(attr.value)
206
- const newFontFamily = getFontFamilyClassNameFromProps(attr.value) || ''
207
- const newClassNames = helpers.concatClassName(
208
- styles.map((x) => x[helpers.StyleObjectIdentifier]).join(' ') +
209
- newFontFamily
210
- )
211
- const existing = finalClassNames.find(
212
- (x) => x.type == 'StringLiteral'
213
- ) as t.StringLiteral | null
214
-
215
- if (existing) {
216
- let previous = existing.value
217
- // replace existing font_ with new one
218
- if (newFontFamily) {
219
- if (shouldPrintDebug) {
220
- console.info(` newFontFamily: ${newFontFamily}`)
221
- }
222
- previous = previous.replace(/font_[a-z]+/i, '')
223
- }
224
- existing.value = `${previous} ${newClassNames}`
225
- } else {
226
- finalClassNames = [...finalClassNames, t.stringLiteral(newClassNames)]
227
- }
228
- }
210
+ const onlyTernaries: Ternary[] = attrs.flatMap((attr) => {
211
+ if (attr.type === 'attr') {
212
+ const value = attr.value
229
213
 
230
- break
214
+ if (t.isJSXSpreadAttribute(value)) {
215
+ // we only handle flattened stuff now so skip this
216
+ console.error(`Should never happen`)
217
+ return []
231
218
  }
232
- case 'attr': {
233
- const val = attr.value
234
- if (t.isJSXSpreadAttribute(val)) {
235
- if (isSimpleSpread(val)) {
236
- finalClassNames.push(
237
- t.logicalExpression(
238
- '&&',
239
- val.argument,
240
- t.memberExpression(val.argument, t.identifier('className'))
241
- )
242
- )
243
- }
244
- } else if (val.name.name === 'className') {
245
- const value = val.value
246
- if (value) {
247
- try {
248
- const evaluatedValue = attemptEval(value)
249
- finalClassNames.push(t.stringLiteral(evaluatedValue))
250
- } catch (e) {
251
- finalClassNames.push(value['expression'])
252
- }
253
- }
254
- continue
219
+
220
+ if (value.name.name === 'className') {
221
+ let inner: any = value.value
222
+ if (t.isJSXExpressionContainer(inner)) {
223
+ inner = inner.expression
255
224
  }
256
- finalAttrs.push(val)
257
- break
258
- }
259
- case 'ternary': {
260
- const mediaExtraction = extractMediaStyle(
261
- parserProps,
262
- attr.value,
263
- jsxPath,
264
- extractor.getTamagui()!,
265
- sourcePath || '',
266
- lastMediaImportance,
267
- shouldPrintDebug
268
- )
269
- if (shouldPrintDebug) {
270
- if (mediaExtraction) {
271
- console.info(
272
- 'ternary (mediaStyles)',
273
- mediaExtraction.ternaryWithoutMedia?.inlineMediaQuery ?? '',
274
- mediaExtraction.mediaStyles
275
- .map((x) => x[helpers.StyleObjectIdentifier])
276
- .join('.')
277
- )
225
+ try {
226
+ const evaluatedValue = inner ? attemptEval(inner) : null
227
+ if (typeof evaluatedValue === 'string') {
228
+ attrClassName = t.stringLiteral(evaluatedValue)
278
229
  }
279
- }
280
- if (!mediaExtraction) {
281
- if (shouldPrintDebug) {
282
- if (mediaExtraction) {
283
- console.info('add ternary')
284
- }
230
+ } catch (e) {
231
+ if (inner) {
232
+ attrClassName ||= inner
285
233
  }
286
- addTernaryStyle(
287
- attr.value,
288
- addStyles(attr.value.consequent),
289
- addStyles(attr.value.alternate)
290
- )
291
- continue
292
234
  }
293
- lastMediaImportance++
235
+ return []
236
+ }
237
+
238
+ finalAttrs.push(value)
239
+ return []
240
+ }
241
+
242
+ if (attr.type === 'style') {
243
+ mergeForwardBaseStyle = mergeProps(mergeForwardBaseStyle || {}, attr.value)
244
+ baseFontFamily = getFontFamilyNameFromProps(attr.value) || ''
245
+ return []
246
+ }
247
+
248
+ let ternary = attr.value
249
+
250
+ if (ternary.inlineMediaQuery) {
251
+ const mediaExtraction = extractMediaStyle(
252
+ parserProps,
253
+ attr.value,
254
+ jsxPath,
255
+ extractor.getTamagui()!,
256
+ sourcePath || '',
257
+ mediaStylesSeen++,
258
+ shouldPrintDebug
259
+ )
260
+
261
+ if (mediaExtraction) {
294
262
  if (mediaExtraction.mediaStyles) {
295
- finalStyles = [...finalStyles, ...mediaExtraction.mediaStyles]
263
+ mergeForwardBaseStyle = mergeProps(mergeForwardBaseStyle || {}, {
264
+ [`$${ternary.inlineMediaQuery}`]: attr.value.consequent!,
265
+ })
296
266
  }
297
267
  if (mediaExtraction.ternaryWithoutMedia) {
298
- addTernaryStyle(
299
- mediaExtraction.ternaryWithoutMedia,
300
- mediaExtraction.mediaStyles,
301
- []
302
- )
268
+ ternary = mediaExtraction.ternaryWithoutMedia
303
269
  } else {
304
- finalClassNames = [
305
- ...finalClassNames,
306
- ...mediaExtraction.mediaStyles.map((x) =>
307
- t.stringLiteral(x[helpers.StyleObjectIdentifier])
308
- ),
309
- ]
270
+ return []
310
271
  }
311
- break
312
272
  }
313
273
  }
314
- }
315
274
 
316
- function addTernaryStyle(ternary: Ternary, a: StyleObject[], b: StyleObject[]) {
317
- const cCN = a.map((x) => x[helpers.StyleObjectIdentifier]).join(' ')
318
- const aCN = b.map((x) => x[helpers.StyleObjectIdentifier]).join(' ')
275
+ let mergedAlternate
276
+ let mergedConsequent
319
277
 
320
- if (a.length && b.length) {
321
- finalClassNames.push(
322
- t.conditionalExpression(
323
- ternary.test,
324
- t.stringLiteral(cCN),
325
- t.stringLiteral(aCN)
326
- )
278
+ if (ternary.alternate && Object.keys(ternary.alternate).length) {
279
+ mergedAlternate = mergeProps(
280
+ mergeForwardBaseStyle || {},
281
+ ternary.alternate || {}
327
282
  )
328
- } else {
329
- finalClassNames.push(
330
- t.conditionalExpression(
331
- ternary.test,
332
- t.stringLiteral(' ' + cCN),
333
- t.stringLiteral(' ' + aCN)
334
- )
283
+ forwardFontFamilyName(ternary.alternate, mergedAlternate, baseFontFamily)
284
+ }
285
+
286
+ if (ternary.consequent && Object.keys(ternary.consequent).length) {
287
+ mergedConsequent = mergeProps(
288
+ mergeForwardBaseStyle || {},
289
+ ternary.consequent || {}
335
290
  )
291
+ forwardFontFamilyName(ternary.consequent, mergedConsequent, baseFontFamily)
336
292
  }
337
- }
338
293
 
339
- if (shouldPrintDebug === 'verbose') {
340
- console.info(' finalClassNames AST\n', JSON.stringify(finalClassNames, null, 2))
294
+ // merge the base style forward into both sides
295
+ return {
296
+ ...ternary,
297
+ alternate: mergedAlternate,
298
+ consequent: mergedConsequent,
299
+ }
300
+ })
301
+
302
+ const hasTernaries = Boolean(onlyTernaries.length)
303
+
304
+ const baseClassNames = mergeForwardBaseStyle
305
+ ? addStyles(mergeForwardBaseStyle)
306
+ : null
307
+
308
+ let baseClassNameStr = !baseClassNames ? '' : baseClassNames.join(' ')
309
+
310
+ if (!hasTernaries && baseFontFamily) {
311
+ baseClassNameStr = `font_${baseFontFamily}${baseClassNameStr ? ` ${baseClassNameStr}` : ''}`
341
312
  }
342
313
 
343
- node.attributes = finalAttrs
314
+ let base = staticConfig.componentName
315
+ ? t.stringLiteral(
316
+ `is_${staticConfig.componentName}${baseClassNameStr ? ` ${baseClassNameStr}` : ''}`
317
+ )
318
+ : t.stringLiteral(baseClassNameStr || '')
344
319
 
345
- if (finalClassNames.length) {
346
- const extraClassNames = (() => {
347
- let value = ''
348
- if (!isFlattened) {
349
- return value
350
- }
320
+ attrClassName = attrClassName as t.Expression | null // actual typescript bug, flatMap doesn't update from never
351
321
 
352
- // helper to see how many get flattened
353
- if (process.env.TAMAGUI_DEBUG_OPTIMIZATIONS) {
354
- value += `is_tamagui_flattened`
322
+ const baseClassNameExpression: t.Expression = (() => {
323
+ if (attrClassName) {
324
+ if (t.isStringLiteral(attrClassName)) {
325
+ return t.stringLiteral(
326
+ base.value ? `${base.value} ${attrClassName.value}` : attrClassName.value
327
+ )
328
+ } else {
329
+ // space after to ensure its a string and its spaced
330
+ return t.conditionalExpression(
331
+ attrClassName,
332
+ t.binaryExpression('+', attrClassName, spaceString),
333
+ base
334
+ )
355
335
  }
336
+ }
337
+ return base
338
+ })()
356
339
 
357
- // add is_Component className
358
- if (staticConfig.componentName) {
359
- value += ` is_${staticConfig.componentName}`
360
- }
340
+ const expandedTernaries: Ternary[] = []
361
341
 
362
- return value
363
- })()
342
+ if (onlyTernaries.length) {
343
+ // normalize tests to reduce duplicates
344
+ const normalizedTernaries = normalizeTernaries(onlyTernaries)
364
345
 
365
- // inserts the _cn variable and uses it for className
366
- const names = buildClassName(finalClassNames, extraClassNames)
346
+ for (const ternary of normalizedTernaries) {
347
+ if (!expandedTernaries.length) {
348
+ expandTernary(ternary)
349
+ continue
350
+ }
351
+ for (const prev of [...expandedTernaries]) {
352
+ expandTernary(ternary, prev)
353
+ }
354
+ }
355
+ }
367
356
 
368
- const nameExpr = names ? hoistClassNames(jsxPath, existingHoists, names) : null
369
- let expr = nameExpr
357
+ function expandTernary(ternary: Ternary, prev?: Ternary) {
358
+ // need to diverge into two (or four if alternate)
359
+ if (ternary.consequent && Object.keys(ternary.consequent).length) {
360
+ const fontFamily = getFontFamilyNameFromProps(ternary.consequent)
361
+
362
+ expandedTernaries.push({
363
+ fontFamily,
364
+ // prevTest && test: merge consequent
365
+ test: prev
366
+ ? t.logicalExpression('&&', prev.test, ternary.test)
367
+ : ternary.test,
368
+ consequent: prev
369
+ ? mergeProps(prev.consequent!, ternary.consequent)
370
+ : ternary.consequent,
371
+ remove,
372
+ alternate: null,
373
+ })
370
374
 
371
- // if has some spreads, use concat helper
372
- if (nameExpr && !t.isIdentifier(nameExpr)) {
373
- if (!didFlattenThisTag) {
374
- // not flat
375
- } else {
376
- ensureImportingConcat(programPath)
377
- const simpleSpreads = attrs.filter((x) => {
378
- return (
379
- x.type === 'attr' &&
380
- t.isJSXSpreadAttribute(x.value) &&
381
- isSimpleSpread(x.value)
382
- )
375
+ if (prev) {
376
+ expandedTernaries.push({
377
+ fontFamily,
378
+ // !prevTest && test: just consequent
379
+ test: t.logicalExpression(
380
+ '&&',
381
+ t.unaryExpression('!', prev.test),
382
+ ternary.test
383
+ ),
384
+ consequent: ternary.consequent,
385
+ alternate: null,
386
+ remove,
383
387
  })
384
- expr = t.callExpression(t.identifier('concatClassName'), [
385
- expr,
386
- ...simpleSpreads.map((val) => val.value['argument']),
387
- ])
388
388
  }
389
389
  }
390
390
 
391
- node.attributes.push(
392
- t.jsxAttribute(t.jsxIdentifier('className'), t.jsxExpressionContainer(expr))
393
- )
391
+ if (ternary.alternate && Object.keys(ternary.alternate).length) {
392
+ const fontFamily = getFontFamilyNameFromProps(ternary.alternate)
393
+ const negated = t.unaryExpression('!', ternary.test)
394
+ expandedTernaries.push({
395
+ fontFamily,
396
+ // prevTest && !test: merge alternate
397
+ test: prev ? t.logicalExpression('&&', prev.test, negated) : negated,
398
+ consequent: prev
399
+ ? mergeProps(prev.alternate!, ternary.alternate)
400
+ : ternary.alternate,
401
+ remove,
402
+ alternate: null,
403
+ })
404
+
405
+ if (prev) {
406
+ expandedTernaries.push({
407
+ fontFamily,
408
+ test: t.logicalExpression(
409
+ '&&',
410
+ t.unaryExpression('!', prev.test),
411
+ ternary.test
412
+ ),
413
+ consequent: ternary.alternate,
414
+ remove,
415
+ alternate: null,
416
+ })
417
+ }
418
+ }
394
419
  }
395
420
 
396
- const comment = util.format(
397
- '/* %s:%s (%s) */',
398
- filePath,
399
- lineNumbers,
400
- originalNodeName
401
- )
421
+ let ternaryClassNameExpr: t.Expression | null = null
402
422
 
403
- for (const styleObject of finalStyles) {
404
- const identifier = styleObject[helpers.StyleObjectIdentifier]
405
- const rules = styleObject[helpers.StyleObjectRules]
406
- const className = `.${identifier}`
407
- if (cssMap.has(className)) {
408
- if (comment) {
409
- const val = cssMap.get(className)!
410
- val.commentTexts.push(comment)
411
- cssMap.set(className, val)
423
+ // next: create all CSS, build className strings and hoist, and create final node with props
424
+ if (hasTernaries) {
425
+ for (const ternary of expandedTernaries) {
426
+ if (!ternary.consequent) continue
427
+ const classNames = addStyles(ternary.consequent)
428
+ if (ternary.fontFamily) {
429
+ classNames.unshift(`font_${ternary.fontFamily}`)
430
+ }
431
+ const baseString = t.isStringLiteral(baseClassNameExpression)
432
+ ? baseClassNameExpression.value
433
+ : ''
434
+
435
+ // we concat here as the base could be conditionally overriden by our classNames
436
+ const fullClassName = concatClassName(
437
+ (baseString ? `${baseString} ` : '') + classNames.join(' ')
438
+ )
439
+
440
+ const classNameLiteral = t.stringLiteral(fullClassName)
441
+
442
+ if (!ternaryClassNameExpr) {
443
+ ternaryClassNameExpr = t.conditionalExpression(
444
+ ternary.test,
445
+ classNameLiteral,
446
+ baseClassNameExpression
447
+ )
448
+ } else {
449
+ ternaryClassNameExpr = t.conditionalExpression(
450
+ ternary.test,
451
+ classNameLiteral,
452
+ ternaryClassNameExpr
453
+ )
412
454
  }
413
- } else if (rules.length) {
414
- cssMap.set(className, {
415
- css: rules.join('\n'),
416
- commentTexts: [comment],
417
- })
418
455
  }
419
456
  }
457
+
458
+ let finalExpression: t.Expression | null =
459
+ ternaryClassNameExpr || baseClassNameExpression || null
460
+
461
+ // console.info('attrs', JSON.stringify(attrs, null, 2))
462
+ // console.info('expandedTernaries', JSON.stringify(expandedTernaries, null, 2))
463
+ // console.info('finalExpression', JSON.stringify(finalExpression, null, 2))
464
+ // console.info({ baseClassNameExpression })
465
+
466
+ if (finalExpression) {
467
+ // hoist to global variables
468
+ finalExpression = hoistClassNames(jsxPath, finalExpression)
469
+
470
+ // console.log('finalExpression', finalExpression)
471
+
472
+ const classNameProp = t.jsxAttribute(
473
+ t.jsxIdentifier('className'),
474
+ t.jsxExpressionContainer(finalExpression!)
475
+ )
476
+ finalAttrs.unshift(classNameProp)
477
+ }
478
+
479
+ node.attributes = finalAttrs
420
480
  },
421
481
  })
422
482
 
@@ -466,3 +526,56 @@ export async function extractToClassNames({
466
526
  map: result.map,
467
527
  }
468
528
  }
529
+
530
+ function hoistClassNames(path: NodePath<t.JSXElement>, expr: t.Expression) {
531
+ if (t.isStringLiteral(expr)) {
532
+ return hoistClassName(path, expr.value)
533
+ }
534
+
535
+ if (t.isLogicalExpression(expr)) {
536
+ const left = t.isStringLiteral(expr.left)
537
+ ? hoistClassName(path, expr.left.value)
538
+ : expr.left
539
+ const right = t.isStringLiteral(expr.right)
540
+ ? hoistClassName(path, expr.right.value)
541
+ : hoistClassNames(path, expr.right)
542
+ return t.logicalExpression(expr.operator, left, right)
543
+ }
544
+
545
+ if (t.isConditionalExpression(expr)) {
546
+ const cons = t.isStringLiteral(expr.consequent)
547
+ ? hoistClassName(path, expr.consequent.value)
548
+ : hoistClassNames(path, expr.consequent)
549
+
550
+ const alt = t.isStringLiteral(expr.alternate)
551
+ ? hoistClassName(path, expr.alternate.value)
552
+ : hoistClassNames(path, expr.alternate)
553
+
554
+ return t.conditionalExpression(expr.test, cons, alt)
555
+ }
556
+
557
+ return expr
558
+ }
559
+
560
+ function hoistClassName(path: NodePath<t.JSXElement>, str: string) {
561
+ const uid = path.scope.generateUidIdentifier('cn')
562
+ const parent = path.findParent((path) => path.isProgram())
563
+ if (!parent) throw new Error(`no program?`)
564
+ const variable = t.variableDeclaration('const', [
565
+ t.variableDeclarator(uid, t.stringLiteral(cleanupClassName(str))),
566
+ ])
567
+ // @ts-ignore
568
+ parent.unshiftContainer('body', variable)
569
+ return uid
570
+ }
571
+
572
+ function cleanupClassName(inStr: string) {
573
+ const out = new Set<string>()
574
+ for (const part of inStr.split(' ')) {
575
+ if (part === ' ') continue
576
+ if (part === 'font_') continue
577
+ out.add(part)
578
+ }
579
+ // always a space after for joining
580
+ return [...out].join(' ') + ' '
581
+ }