@tamagui/static 3.0.0-beta.831.1 → 3.0.0-beta.889.1
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.
- package/dist/cjs/compiler.cjs +17 -5
- package/dist/cjs/compilerHost.cjs +148 -39
- package/dist/cjs/componentDiscovery.cjs +94 -0
- package/dist/cjs/domStructuralPass.cjs +12 -2
- package/dist/cjs/exports.cjs +4 -1
- package/dist/cjs/extractor/bundleConfig.cjs +23 -0
- package/dist/cjs/zero/theme.cjs +5 -20
- package/dist/esm/compiler.mjs +18 -6
- package/dist/esm/compiler.mjs.map +1 -1
- package/dist/esm/compilerHost.mjs +146 -40
- package/dist/esm/compilerHost.mjs.map +1 -1
- package/dist/esm/componentDiscovery.mjs +69 -0
- package/dist/esm/componentDiscovery.mjs.map +1 -0
- package/dist/esm/domStructuralPass.mjs +12 -2
- package/dist/esm/domStructuralPass.mjs.map +1 -1
- package/dist/esm/exports.mjs +4 -1
- package/dist/esm/extractor/bundleConfig.mjs +23 -1
- package/dist/esm/extractor/bundleConfig.mjs.map +1 -1
- package/dist/esm/zero/theme.mjs +5 -20
- package/dist/esm/zero/theme.mjs.map +1 -1
- package/package.json +17 -17
- package/src/compiler.ts +27 -5
- package/src/compilerHost.ts +262 -74
- package/src/componentDiscovery.ts +105 -0
- package/src/domStructuralPass.ts +22 -2
- package/src/exports.ts +2 -0
- package/src/extractor/bundleConfig.ts +34 -0
- package/src/zero/theme.ts +12 -21
- package/types/compiler.d.ts +13 -0
- package/types/compiler.d.ts.map +1 -1
- package/types/compilerHost.d.ts +13 -0
- package/types/compilerHost.d.ts.map +1 -1
- package/types/componentDiscovery.d.ts +27 -0
- package/types/componentDiscovery.d.ts.map +1 -0
- package/types/domStructuralPass.d.ts.map +1 -1
- package/types/exports.d.ts +2 -0
- package/types/exports.d.ts.map +1 -1
- package/types/extractor/bundleConfig.d.ts +7 -0
- package/types/extractor/bundleConfig.d.ts.map +1 -1
- package/types/zero/theme.d.ts +0 -1
- package/types/zero/theme.d.ts.map +1 -1
package/src/compilerHost.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
StyleObjectRules,
|
|
22
22
|
StyleObjectValue,
|
|
23
23
|
stylePropsAll,
|
|
24
|
+
stylePropsInput,
|
|
24
25
|
stylePropsText,
|
|
25
26
|
validStyles as validStylesView,
|
|
26
27
|
} from '@tamagui/helpers'
|
|
@@ -55,11 +56,38 @@ export interface CompilerComponentModule {
|
|
|
55
56
|
resolvedId: string
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* The components lowering can resolve, keyed the way element provenance names
|
|
61
|
+
* them. The frontend owns one per compile and grows it as it discovers component
|
|
62
|
+
* modules outside the configured `components` list.
|
|
63
|
+
*/
|
|
64
|
+
export interface CompilerComponentRegistry {
|
|
65
|
+
/** host-resolved module id -> the module name its components register under */
|
|
66
|
+
modulesById: Map<string, string>
|
|
67
|
+
componentsByModule: Map<string, LoadedComponents>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createComponentRegistry(
|
|
71
|
+
components: readonly LoadedComponents[],
|
|
72
|
+
componentModules: readonly CompilerComponentModule[]
|
|
73
|
+
): CompilerComponentRegistry {
|
|
74
|
+
return {
|
|
75
|
+
modulesById: new Map(
|
|
76
|
+
componentModules.map((module) => [module.resolvedId, module.moduleName])
|
|
77
|
+
),
|
|
78
|
+
componentsByModule: new Map(
|
|
79
|
+
components.map((component) => [component.moduleName, component])
|
|
80
|
+
),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
58
84
|
export interface TamaguiCompilerHostOptions {
|
|
59
85
|
target: CompilerTarget
|
|
60
86
|
tamaguiConfig: TamaguiInternalConfig
|
|
61
87
|
components: LoadedComponents[]
|
|
62
88
|
componentModules: CompilerComponentModule[]
|
|
89
|
+
/** shared with the frontend so discovery during `prepare` is visible to this host */
|
|
90
|
+
registry?: CompilerComponentRegistry
|
|
63
91
|
/** Keep elements with dynamic style props fully on the runtime path. */
|
|
64
92
|
disablePartialExtraction?: boolean
|
|
65
93
|
/** emit native theme-token mappings for the native style engine */
|
|
@@ -390,6 +418,11 @@ const runtimeAnimationProps = new Set([
|
|
|
390
418
|
'animatedBy',
|
|
391
419
|
])
|
|
392
420
|
|
|
421
|
+
// createComponent reads these and never forwards them to the host element, so a
|
|
422
|
+
// flattened element has to drop them instead of emitting an unknown attribute.
|
|
423
|
+
// only the inert values reach the emit: an active one already bailed.
|
|
424
|
+
const componentOnlyProps = new Set(['asChild', 'disableOptimization', 'themeInverse'])
|
|
425
|
+
|
|
393
426
|
const cssShorthandConflicts: Record<string, readonly string[]> = {
|
|
394
427
|
background: [
|
|
395
428
|
'backgroundAttachment',
|
|
@@ -548,6 +581,12 @@ const cssShorthandConflicts: Record<string, readonly string[]> = {
|
|
|
548
581
|
}
|
|
549
582
|
|
|
550
583
|
const cssConflictFamilies = [
|
|
584
|
+
new Set(['width', 'inlineSize']),
|
|
585
|
+
new Set(['minWidth', 'minInlineSize']),
|
|
586
|
+
new Set(['maxWidth', 'maxInlineSize']),
|
|
587
|
+
new Set(['height', 'blockSize']),
|
|
588
|
+
new Set(['minHeight', 'minBlockSize']),
|
|
589
|
+
new Set(['maxHeight', 'maxBlockSize']),
|
|
551
590
|
new Set([
|
|
552
591
|
'marginInline',
|
|
553
592
|
'marginInlineEnd',
|
|
@@ -943,6 +982,8 @@ export function createTamaguiCompilerHost(
|
|
|
943
982
|
): CompilerLoweringHost {
|
|
944
983
|
const platform = options.target === 'native' ? 'native' : 'web'
|
|
945
984
|
const core = requireTamaguiCore(platform) as any
|
|
985
|
+
const compilerVariantStyleResolver = core.styled(core.View, {}).staticConfig
|
|
986
|
+
.variantStyleResolver
|
|
946
987
|
const firstThemeName = Object.keys(options.tamaguiConfig.themes ?? {})[0] ?? ''
|
|
947
988
|
const firstTheme = options.tamaguiConfig.themes?.[firstThemeName] ?? {}
|
|
948
989
|
const theme = firstTheme
|
|
@@ -968,6 +1009,39 @@ export function createTamaguiCompilerHost(
|
|
|
968
1009
|
}
|
|
969
1010
|
return false
|
|
970
1011
|
}
|
|
1012
|
+
// A native bundle knows one thing about its platform: it is not web. So a
|
|
1013
|
+
// `web:` clause can never match there and a bare `native:` clause always
|
|
1014
|
+
// does, which makes a value built only from those two resolvable at compile
|
|
1015
|
+
// time. Every other modifier is live at render: media and container queries
|
|
1016
|
+
// measure, theme and group clauses read context, state and lifecycle clauses
|
|
1017
|
+
// track the component, and the device platforms (ios, android, tv) are not
|
|
1018
|
+
// known until the bundle runs. One of those anywhere in the value keeps the
|
|
1019
|
+
// whole element on the runtime path.
|
|
1020
|
+
const platformModifiersRegistered =
|
|
1021
|
+
modifierRegistry.get('web') === 'platform' &&
|
|
1022
|
+
modifierRegistry.get('native') === 'platform'
|
|
1023
|
+
const nativeClauseValue = (
|
|
1024
|
+
value: unknown
|
|
1025
|
+
): 'live' | { payload: unknown; matched: boolean } | null => {
|
|
1026
|
+
if (typeof value !== 'string' || !flatClausePattern.test(value)) {
|
|
1027
|
+
return isClauseObjectValue(value) ? 'live' : null
|
|
1028
|
+
}
|
|
1029
|
+
if (!platformModifiersRegistered) return 'live'
|
|
1030
|
+
const parsed = parseValue(value, modifierRegistry)
|
|
1031
|
+
if (!parsed.ok || parsed.value.clauses.length === 0) return 'live'
|
|
1032
|
+
let payload: unknown = parsed.value.base
|
|
1033
|
+
let matched = parsed.value.base !== null
|
|
1034
|
+
for (const clause of parsed.value.clauses) {
|
|
1035
|
+
// a chain containing `web` is dead here whatever else it names
|
|
1036
|
+
if (clause.modifiers.includes('web')) continue
|
|
1037
|
+
if (clause.modifiers.length !== 1 || clause.modifiers[0] !== 'native') return 'live'
|
|
1038
|
+
// platform clauses all rank the same, so a later one wins and any of them
|
|
1039
|
+
// beats the unconditional base
|
|
1040
|
+
payload = clause.payload
|
|
1041
|
+
matched = true
|
|
1042
|
+
}
|
|
1043
|
+
return { payload, matched }
|
|
1044
|
+
}
|
|
971
1045
|
const configuredAnimationDriver = options.tamaguiConfig.animations as
|
|
972
1046
|
| AnimationDriver
|
|
973
1047
|
| undefined
|
|
@@ -1031,12 +1105,9 @@ export function createTamaguiCompilerHost(
|
|
|
1031
1105
|
}
|
|
1032
1106
|
return resolved.join(' ')
|
|
1033
1107
|
}
|
|
1034
|
-
const modulesById
|
|
1035
|
-
options.
|
|
1036
|
-
|
|
1037
|
-
const componentsByModule = new Map(
|
|
1038
|
-
options.components.map((component) => [component.moduleName, component])
|
|
1039
|
-
)
|
|
1108
|
+
const { modulesById, componentsByModule } =
|
|
1109
|
+
options.registry ??
|
|
1110
|
+
createComponentRegistry(options.components, options.componentModules)
|
|
1040
1111
|
|
|
1041
1112
|
// A component's import provenance chose its authoring syntax, and the frontend
|
|
1042
1113
|
// descriptor frozen onto its static config is the only thing that knows how to
|
|
@@ -1122,7 +1193,10 @@ export function createTamaguiCompilerHost(
|
|
|
1122
1193
|
isInput: row.backing === 'textinput',
|
|
1123
1194
|
// CSS text properties may be authored on any element and inherited by
|
|
1124
1195
|
// descendant text, including from a View-backed tag on native.
|
|
1125
|
-
validStyles: {
|
|
1196
|
+
validStyles: {
|
|
1197
|
+
...validStylesView,
|
|
1198
|
+
...(row.backing === 'textinput' ? stylePropsInput : stylePropsText),
|
|
1199
|
+
},
|
|
1126
1200
|
defaultProps: {
|
|
1127
1201
|
...base.defaultProps,
|
|
1128
1202
|
...platformDefaults,
|
|
@@ -1135,6 +1209,13 @@ export function createTamaguiCompilerHost(
|
|
|
1135
1209
|
definition: MaterializedStyledDefinition | null
|
|
1136
1210
|
): { key: string; staticConfig: StaticConfig; displayName?: string } | null => {
|
|
1137
1211
|
if (!definition || definition.options.kind !== 'static') return null
|
|
1212
|
+
const staticConfig = definition.staticConfig
|
|
1213
|
+
if (
|
|
1214
|
+
staticConfig &&
|
|
1215
|
+
(staticConfig.kind !== 'static' || !staticObject(staticConfig.value))
|
|
1216
|
+
) {
|
|
1217
|
+
return null
|
|
1218
|
+
}
|
|
1138
1219
|
const base = directStaticConfig({
|
|
1139
1220
|
kind: 'element',
|
|
1140
1221
|
form: 'jsx',
|
|
@@ -1160,29 +1241,32 @@ export function createTamaguiCompilerHost(
|
|
|
1160
1241
|
typeof definition.baseClassName.value === 'string'
|
|
1161
1242
|
? definition.baseClassName.value
|
|
1162
1243
|
: undefined
|
|
1244
|
+
const localStaticConfig = {
|
|
1245
|
+
...base.staticConfig,
|
|
1246
|
+
...(staticConfig?.value as Record<string, unknown> | undefined),
|
|
1247
|
+
variants: {
|
|
1248
|
+
...base.staticConfig.variants,
|
|
1249
|
+
...(variants as object | undefined),
|
|
1250
|
+
},
|
|
1251
|
+
defaultProps: {
|
|
1252
|
+
...base.staticConfig.defaultProps,
|
|
1253
|
+
...defaultProps,
|
|
1254
|
+
...(defaultVariants as object | undefined),
|
|
1255
|
+
},
|
|
1256
|
+
defaultVariants,
|
|
1257
|
+
baseClassName: [base.staticConfig.baseClassName, baseClassName]
|
|
1258
|
+
.filter(Boolean)
|
|
1259
|
+
.join(' '),
|
|
1260
|
+
context: context ?? base.staticConfig.context,
|
|
1261
|
+
contextProps: context
|
|
1262
|
+
? contextProps
|
|
1263
|
+
: (contextProps ?? base.staticConfig.contextProps),
|
|
1264
|
+
}
|
|
1265
|
+
;(localStaticConfig as any).variantStyleResolver = compilerVariantStyleResolver
|
|
1163
1266
|
return {
|
|
1164
1267
|
key: componentKey(definition.id, definition.name),
|
|
1165
1268
|
displayName: displayName || base.displayName,
|
|
1166
|
-
staticConfig: normalizeStaticConfig(
|
|
1167
|
-
...base.staticConfig,
|
|
1168
|
-
variants: {
|
|
1169
|
-
...base.staticConfig.variants,
|
|
1170
|
-
...(variants as object | undefined),
|
|
1171
|
-
},
|
|
1172
|
-
defaultProps: {
|
|
1173
|
-
...base.staticConfig.defaultProps,
|
|
1174
|
-
...defaultProps,
|
|
1175
|
-
...(defaultVariants as object | undefined),
|
|
1176
|
-
},
|
|
1177
|
-
defaultVariants,
|
|
1178
|
-
baseClassName: [base.staticConfig.baseClassName, baseClassName]
|
|
1179
|
-
.filter(Boolean)
|
|
1180
|
-
.join(' '),
|
|
1181
|
-
context: context ?? base.staticConfig.context,
|
|
1182
|
-
contextProps: context
|
|
1183
|
-
? contextProps
|
|
1184
|
-
: (contextProps ?? base.staticConfig.contextProps),
|
|
1185
|
-
}),
|
|
1269
|
+
staticConfig: normalizeStaticConfig(localStaticConfig),
|
|
1186
1270
|
}
|
|
1187
1271
|
}
|
|
1188
1272
|
|
|
@@ -1191,8 +1275,11 @@ export function createTamaguiCompilerHost(
|
|
|
1191
1275
|
styledDefinition: MaterializedStyledDefinition | null
|
|
1192
1276
|
): TamaguiLoweringComponent | null => {
|
|
1193
1277
|
const dom = domStaticConfig(element)
|
|
1278
|
+
// a config the host evaluated is the component's real static config. The
|
|
1279
|
+
// one derived from a visible `styled()` literal only stands in for
|
|
1280
|
+
// definitions nothing evaluated, such as the app's own
|
|
1194
1281
|
const resolved =
|
|
1195
|
-
dom ??
|
|
1282
|
+
dom ?? directStaticConfig(element) ?? styledStaticConfig(styledDefinition)
|
|
1196
1283
|
if (!resolved) return null
|
|
1197
1284
|
const defaultProps = resolved.staticConfig.defaultProps ?? {}
|
|
1198
1285
|
return {
|
|
@@ -1254,10 +1341,12 @@ export function createTamaguiCompilerHost(
|
|
|
1254
1341
|
const staticConfig = component.staticConfig as StaticConfig
|
|
1255
1342
|
const validStyles =
|
|
1256
1343
|
staticConfig.validStyles ||
|
|
1257
|
-
(staticConfig.
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1344
|
+
(staticConfig.isInput
|
|
1345
|
+
? stylePropsInput
|
|
1346
|
+
: staticConfig.isText
|
|
1347
|
+
? stylePropsText
|
|
1348
|
+
: validStylesView)
|
|
1349
|
+
return name in stylePropsAll && !isValidStyleKey(name, validStyles)
|
|
1261
1350
|
}
|
|
1262
1351
|
|
|
1263
1352
|
const directStyleName = (name: string, component: LoweringComponent): string | null => {
|
|
@@ -1292,6 +1381,7 @@ export function createTamaguiCompilerHost(
|
|
|
1292
1381
|
firstThemeName,
|
|
1293
1382
|
componentState,
|
|
1294
1383
|
{
|
|
1384
|
+
isStatic: true,
|
|
1295
1385
|
resolveValues: platform === 'native' ? 'except-theme' : 'variable',
|
|
1296
1386
|
noClass: platform === 'native',
|
|
1297
1387
|
isAnimated: false,
|
|
@@ -1501,6 +1591,12 @@ export function createTamaguiCompilerHost(
|
|
|
1501
1591
|
) {
|
|
1502
1592
|
return true
|
|
1503
1593
|
}
|
|
1594
|
+
// A component resolver receives the complete props object. Without a
|
|
1595
|
+
// compiler-derived read set, resolving an opaque dynamic callsite would
|
|
1596
|
+
// execute the real resolver with an incomplete props snapshot.
|
|
1597
|
+
if ((component.staticConfig as StaticConfig).resolvers?.length) {
|
|
1598
|
+
return false
|
|
1599
|
+
}
|
|
1504
1600
|
return (
|
|
1505
1601
|
!options.disablePartialExtraction &&
|
|
1506
1602
|
((platform === 'web' && !!directStyleName(name, component)) ||
|
|
@@ -1556,15 +1652,56 @@ export function createTamaguiCompilerHost(
|
|
|
1556
1652
|
props[entry.name] = entry.value.value
|
|
1557
1653
|
}
|
|
1558
1654
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1655
|
+
if (component.staticConfig.resolvers?.length) {
|
|
1656
|
+
const unresolvedProps = input.element.entries.filter(
|
|
1657
|
+
(entry) => entry.kind === 'prop' && entry.value.kind !== 'static'
|
|
1658
|
+
)
|
|
1659
|
+
// One conditional with static leaves is still a complete prop set:
|
|
1660
|
+
// lowerCandidate evaluates the full resolver chain once per branch.
|
|
1661
|
+
// Multiple unknown props would require a cross-product/read set, so
|
|
1662
|
+
// they remain on the runtime path.
|
|
1663
|
+
const onlyStaticBranchConditional =
|
|
1664
|
+
unresolvedProps.length === 1 &&
|
|
1665
|
+
unresolvedProps[0]?.kind === 'prop' &&
|
|
1666
|
+
unresolvedProps[0].value.kind === 'conditional' &&
|
|
1667
|
+
canLowerConditionalStyleProp(unresolvedProps[0].name, component)
|
|
1668
|
+
const unresolvedProp = onlyStaticBranchConditional
|
|
1669
|
+
? undefined
|
|
1670
|
+
: unresolvedProps[0]
|
|
1671
|
+
if (unresolvedProp?.kind === 'prop') {
|
|
1672
|
+
return bailout(
|
|
1673
|
+
input,
|
|
1674
|
+
'local/dynamic-style-value',
|
|
1675
|
+
`Component resolver prop ${unresolvedProp.name} could not be evaluated`,
|
|
1676
|
+
unresolvedProp.span,
|
|
1677
|
+
undefined,
|
|
1678
|
+
unresolvedProp.name
|
|
1679
|
+
)
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
// asChild, disableOptimization and themeInverse each asked only whether the
|
|
1683
|
+
// prop was PRESENT, so a false value retained a component that requested
|
|
1684
|
+
// none of the runtime behavior the branch exists for. read the materialized
|
|
1685
|
+
// value instead, and treat a prop the compiler could not evaluate as active.
|
|
1686
|
+
// a later duplicate wins, so the last entry decides and any unevaluated
|
|
1687
|
+
// entry for that name makes the answer unknown.
|
|
1688
|
+
const componentOnlyProp = (name: string) => {
|
|
1689
|
+
let span: MaterializedElement['entries'][number]['span'] | undefined
|
|
1690
|
+
let unknown = false
|
|
1691
|
+
for (const entry of input.element.entries) {
|
|
1692
|
+
if (entry.kind !== 'prop' || entry.name !== name) continue
|
|
1693
|
+
span = entry.span
|
|
1694
|
+
if (entry.value.kind !== 'static') unknown = true
|
|
1695
|
+
}
|
|
1696
|
+
return { unknown, span }
|
|
1697
|
+
}
|
|
1698
|
+
const disableOptimization = componentOnlyProp('disableOptimization')
|
|
1699
|
+
if (disableOptimization.unknown || props.disableOptimization) {
|
|
1563
1700
|
return bailout(
|
|
1564
1701
|
input,
|
|
1565
1702
|
'local/unsupported-target',
|
|
1566
1703
|
'disableOptimization keeps the component on the runtime path',
|
|
1567
|
-
|
|
1704
|
+
disableOptimization.span
|
|
1568
1705
|
)
|
|
1569
1706
|
}
|
|
1570
1707
|
if (component.domTag && props.hidden) props.display = 'none'
|
|
@@ -1761,11 +1898,7 @@ export function createTamaguiCompilerHost(
|
|
|
1761
1898
|
name === 'opacity'
|
|
1762
1899
|
? `opacity: (${expression})`
|
|
1763
1900
|
: `transform: "scale(" + (${expression}) + ")"`
|
|
1764
|
-
} else if (
|
|
1765
|
-
entry.value.kind === 'bailout' &&
|
|
1766
|
-
owners.size === 1 &&
|
|
1767
|
-
owners.has(name)
|
|
1768
|
-
) {
|
|
1901
|
+
} else if (entry.value.kind === 'bailout' && owners.has(name)) {
|
|
1769
1902
|
const dynamic = entry.value.dynamic
|
|
1770
1903
|
if (dynamic?.type === 'number') {
|
|
1771
1904
|
property = `${JSON.stringify(name)}: (${expression})`
|
|
@@ -1831,8 +1964,13 @@ export function createTamaguiCompilerHost(
|
|
|
1831
1964
|
entry.value.kind === 'conditional' &&
|
|
1832
1965
|
canLowerConditionalStyleProp(entry.name, component)
|
|
1833
1966
|
)
|
|
1834
|
-
|
|
1835
|
-
|
|
1967
|
+
// a theme name of any value selects a theme, while themeInverse only opens a
|
|
1968
|
+
// boundary when it is on
|
|
1969
|
+
const themeEntry = componentOnlyProp('theme')
|
|
1970
|
+
const themeInverseEntry = componentOnlyProp('themeInverse')
|
|
1971
|
+
const themeBoundary = themeEntry.unknown || 'theme' in props
|
|
1972
|
+
if (themeBoundary || themeInverseEntry.unknown || props.themeInverse) {
|
|
1973
|
+
const themeProp = themeBoundary ? 'theme' : 'themeInverse'
|
|
1836
1974
|
return bailout(
|
|
1837
1975
|
input,
|
|
1838
1976
|
'local/unsupported-target',
|
|
@@ -1848,15 +1986,13 @@ export function createTamaguiCompilerHost(
|
|
|
1848
1986
|
// the single child and emits no element of its own. a flattened host view
|
|
1849
1987
|
// would add a wrapper the runtime never renders, and the child would
|
|
1850
1988
|
// never receive the merged props. both platforms.
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
)
|
|
1854
|
-
if (asChildEntry || 'asChild' in props) {
|
|
1989
|
+
const asChild = componentOnlyProp('asChild')
|
|
1990
|
+
if (asChild.unknown || props.asChild) {
|
|
1855
1991
|
return bailout(
|
|
1856
1992
|
input,
|
|
1857
1993
|
'local/unsupported-target',
|
|
1858
1994
|
'asChild renders a Slot, not a host view',
|
|
1859
|
-
|
|
1995
|
+
asChild.span
|
|
1860
1996
|
)
|
|
1861
1997
|
}
|
|
1862
1998
|
// group and container props publish separate keys in the same context:
|
|
@@ -2047,7 +2183,11 @@ export function createTamaguiCompilerHost(
|
|
|
2047
2183
|
entry.span
|
|
2048
2184
|
)
|
|
2049
2185
|
}
|
|
2050
|
-
const
|
|
2186
|
+
const resolvedStyleStaticConfig = core.getStyleStaticConfig(
|
|
2187
|
+
component.staticConfig,
|
|
2188
|
+
core.getConfig()
|
|
2189
|
+
)
|
|
2190
|
+
const staticDefaultProps = resolvedStyleStaticConfig.defaultProps ?? {}
|
|
2051
2191
|
const defaultProps =
|
|
2052
2192
|
platform === 'web' &&
|
|
2053
2193
|
!component.staticConfig.isText &&
|
|
@@ -2062,6 +2202,19 @@ export function createTamaguiCompilerHost(
|
|
|
2062
2202
|
props
|
|
2063
2203
|
)
|
|
2064
2204
|
}
|
|
2205
|
+
const domStyleProgram = input.element.entries.find(
|
|
2206
|
+
(entry) =>
|
|
2207
|
+
entry.kind === 'prop' &&
|
|
2208
|
+
entry.name === 'style' &&
|
|
2209
|
+
entry.value.kind === 'dom-style'
|
|
2210
|
+
)
|
|
2211
|
+
if (platform === 'native' && !component.domTag && domStyleProgram) {
|
|
2212
|
+
return bailout(
|
|
2213
|
+
input,
|
|
2214
|
+
'local/unsupported-target',
|
|
2215
|
+
'Native style() pieces remain on the runtime path'
|
|
2216
|
+
)
|
|
2217
|
+
}
|
|
2065
2218
|
const propsForConditional = (
|
|
2066
2219
|
target: Extract<MaterializedElement['entries'][number], { kind: 'prop' }>,
|
|
2067
2220
|
value: unknown
|
|
@@ -2099,22 +2252,35 @@ export function createTamaguiCompilerHost(
|
|
|
2099
2252
|
branchProps
|
|
2100
2253
|
)
|
|
2101
2254
|
: core.mergeProps(defaultProps, branchProps)
|
|
2255
|
+
// the same platform-clause reduction the unconditional props get: a
|
|
2256
|
+
// branch resolves through the full pipeline, so a clause left standing
|
|
2257
|
+
// here would be decided by the build machine instead of the bundle
|
|
2258
|
+
if (platform === 'native') {
|
|
2259
|
+
for (const [name, value] of Object.entries(branchCompleteProps)) {
|
|
2260
|
+
const clause = isStyleProp(name, component) ? nativeClauseValue(value) : null
|
|
2261
|
+
if (clause === null || clause === 'live') continue
|
|
2262
|
+
if (clause.matched) branchCompleteProps[name] = clause.payload
|
|
2263
|
+
else delete branchCompleteProps[name]
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2102
2266
|
return { branchProps, branchCompleteProps }
|
|
2103
2267
|
}
|
|
2104
|
-
//
|
|
2105
|
-
//
|
|
2106
|
-
//
|
|
2268
|
+
// native resolution evaluates clauses against the build machine's current
|
|
2269
|
+
// state, so folding a call-site, styled base, or variant clause would freeze
|
|
2270
|
+
// that state into the bundle.
|
|
2107
2271
|
if (platform === 'native') {
|
|
2108
2272
|
const isClauseValue = (name: string, value: unknown) =>
|
|
2109
2273
|
isStyleProp(name, component) &&
|
|
2110
2274
|
((typeof value === 'string' && flatClausePattern.test(value)) ||
|
|
2111
2275
|
isClauseObjectValue(value))
|
|
2112
2276
|
// a clause can also sit inside a variant definition, where it never
|
|
2113
|
-
// appears as a prop: variants: { big: { true: { width: 'gt-lg:999px' } } }
|
|
2277
|
+
// appears as a prop: variants: { big: { true: { width: 'gt-lg:999px' } } }.
|
|
2278
|
+
// those live in the styled definition, which this element cannot rewrite,
|
|
2279
|
+
// so a clause there retains whatever kind it is.
|
|
2114
2280
|
const defaultVariants = component.staticConfig.defaultVariants ?? {}
|
|
2115
|
-
const
|
|
2116
|
-
Object.entries(
|
|
2117
|
-
isClauseValue(name, value)
|
|
2281
|
+
const definitionCarriesClause =
|
|
2282
|
+
Object.entries(resolvedStyleStaticConfig.baseStyle ?? {}).some(
|
|
2283
|
+
([name, value]) => isClauseValue(name, value)
|
|
2118
2284
|
) ||
|
|
2119
2285
|
Object.entries(component.staticConfig.variants ?? {}).some(
|
|
2120
2286
|
([variantName, definitions]) =>
|
|
@@ -2129,13 +2295,43 @@ export function createTamaguiCompilerHost(
|
|
|
2129
2295
|
)
|
|
2130
2296
|
)
|
|
2131
2297
|
)
|
|
2132
|
-
if (
|
|
2298
|
+
if (definitionCarriesClause) {
|
|
2133
2299
|
return bailout(
|
|
2134
2300
|
input,
|
|
2135
2301
|
'local/unsupported-target',
|
|
2136
2302
|
'Native conditional value programs remain on the runtime path'
|
|
2137
2303
|
)
|
|
2138
2304
|
}
|
|
2305
|
+
const reducedProps: Record<string, unknown> = {}
|
|
2306
|
+
for (const [name, value] of Object.entries(completeProps)) {
|
|
2307
|
+
const clause = isStyleProp(name, component) ? nativeClauseValue(value) : null
|
|
2308
|
+
if (clause === 'live') {
|
|
2309
|
+
return bailout(
|
|
2310
|
+
input,
|
|
2311
|
+
'local/unsupported-target',
|
|
2312
|
+
'Native conditional value programs remain on the runtime path'
|
|
2313
|
+
)
|
|
2314
|
+
}
|
|
2315
|
+
if (clause === null) {
|
|
2316
|
+
reducedProps[name] = value
|
|
2317
|
+
continue
|
|
2318
|
+
}
|
|
2319
|
+
if (clause.matched) reducedProps[name] = clause.payload
|
|
2320
|
+
}
|
|
2321
|
+
completeProps = reducedProps
|
|
2322
|
+
// a conditional leaf is a clause program the same way a direct prop is,
|
|
2323
|
+
// and propsForConditional reduces the ones that fold
|
|
2324
|
+
for (const entry of dynamicStyleEntries) {
|
|
2325
|
+
if (entry.value.kind !== 'conditional') continue
|
|
2326
|
+
for (const leaf of collectLeaves(entry.value.tree)) {
|
|
2327
|
+
if (nativeClauseValue(leaf.value) !== 'live') continue
|
|
2328
|
+
return bailout(
|
|
2329
|
+
input,
|
|
2330
|
+
'local/unsupported-target',
|
|
2331
|
+
'Native conditional value programs remain on the runtime path'
|
|
2332
|
+
)
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2139
2335
|
}
|
|
2140
2336
|
const split = resolveSplitStyles(
|
|
2141
2337
|
completeProps,
|
|
@@ -2167,13 +2363,6 @@ export function createTamaguiCompilerHost(
|
|
|
2167
2363
|
}
|
|
2168
2364
|
)
|
|
2169
2365
|
}
|
|
2170
|
-
const domStyleProgram = input.element.entries.find(
|
|
2171
|
-
(entry) =>
|
|
2172
|
-
entry.kind === 'prop' &&
|
|
2173
|
-
entry.name === 'style' &&
|
|
2174
|
-
entry.value.kind === 'dom-style'
|
|
2175
|
-
)
|
|
2176
|
-
|
|
2177
2366
|
const flatTag =
|
|
2178
2367
|
component.domTag ??
|
|
2179
2368
|
(typeof props.render === 'string'
|
|
@@ -2193,7 +2382,9 @@ export function createTamaguiCompilerHost(
|
|
|
2193
2382
|
}))
|
|
2194
2383
|
|
|
2195
2384
|
const isPropIgnored = (name: string) =>
|
|
2196
|
-
isStyleProp(name, component) ||
|
|
2385
|
+
isStyleProp(name, component) ||
|
|
2386
|
+
isInvalidHostStyleProp(name, component) ||
|
|
2387
|
+
componentOnlyProps.has(name)
|
|
2197
2388
|
const spreadReplacement = (
|
|
2198
2389
|
form: MaterializedElement['form'],
|
|
2199
2390
|
entry: MaterializedElement['entries'][number]
|
|
@@ -2210,16 +2401,11 @@ export function createTamaguiCompilerHost(
|
|
|
2210
2401
|
|
|
2211
2402
|
let styleEntries = input.element.entries.filter(
|
|
2212
2403
|
(entry) =>
|
|
2213
|
-
(entry.kind === 'prop' &&
|
|
2214
|
-
(isStyleProp(entry.name, component) ||
|
|
2215
|
-
isInvalidHostStyleProp(entry.name, component))) ||
|
|
2404
|
+
(entry.kind === 'prop' && isPropIgnored(entry.name)) ||
|
|
2216
2405
|
(entry.kind === 'spread' &&
|
|
2217
2406
|
entry.value.kind === 'static' &&
|
|
2218
2407
|
staticObject(entry.value.value) &&
|
|
2219
|
-
Object.keys(entry.value.value).some(
|
|
2220
|
-
(name) =>
|
|
2221
|
-
isStyleProp(name, component) || isInvalidHostStyleProp(name, component)
|
|
2222
|
-
))
|
|
2408
|
+
Object.keys(entry.value.value).some(isPropIgnored))
|
|
2223
2409
|
)
|
|
2224
2410
|
let invalidHostStyle:
|
|
2225
2411
|
| { entry: MaterializedElement['entries'][number]; name: string }
|
|
@@ -3361,7 +3547,8 @@ function bailout(
|
|
|
3361
3547
|
| 'local/unsupported-child',
|
|
3362
3548
|
message: string,
|
|
3363
3549
|
span = input.element.span,
|
|
3364
|
-
zero?: { rule: ZeroRule; message?: string }
|
|
3550
|
+
zero?: { rule: ZeroRule; message?: string },
|
|
3551
|
+
prop?: string
|
|
3365
3552
|
): LoweringCandidateResult {
|
|
3366
3553
|
return {
|
|
3367
3554
|
ok: false,
|
|
@@ -3371,6 +3558,7 @@ function bailout(
|
|
|
3371
3558
|
message,
|
|
3372
3559
|
span,
|
|
3373
3560
|
component: input.element.component.name,
|
|
3561
|
+
...(prop && { prop }),
|
|
3374
3562
|
...(zero && { zeroRule: zero.rule, zeroMessage: zero.message }),
|
|
3375
3563
|
},
|
|
3376
3564
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { MaterializedModule } from '@tamagui/compiler-core'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import type { CompilerComponentRegistry } from './compilerHost'
|
|
5
|
+
import {
|
|
6
|
+
getComponentStaticConfigByName,
|
|
7
|
+
type LoadedComponents,
|
|
8
|
+
} from './extractor/bundleConfig'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Evaluate a host-resolved module and return its exports, or null when the
|
|
12
|
+
* host cannot run it. Frontends supply this from their bundler's module runner
|
|
13
|
+
* (Vite) or the static-evaluation require hooks (`evaluateComponentModule`).
|
|
14
|
+
*/
|
|
15
|
+
export type ComponentModuleEvaluator = (module: {
|
|
16
|
+
id: string
|
|
17
|
+
specifier: string
|
|
18
|
+
}) => Promise<Record<string, unknown> | null>
|
|
19
|
+
|
|
20
|
+
// `pkg`, `@scope/pkg`, and their subpaths. `@/x` and `~/x` are path aliases
|
|
21
|
+
const packageSpecifier = /^(?:@[^/@\s]+\/)?[^./~@\s][^/\s]*(?:\/|$)/
|
|
22
|
+
|
|
23
|
+
function isPackageSpecifier(specifier: string): boolean {
|
|
24
|
+
return packageSpecifier.test(specifier)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Finds the component modules a file uses that the configured `components`
|
|
29
|
+
* list does not cover, evaluates each once, and registers what it exports so
|
|
30
|
+
* lowering resolves them like configured ones. Owned by a frontend for the
|
|
31
|
+
* life of a project generation; `prepare` runs between materialize and lower.
|
|
32
|
+
*/
|
|
33
|
+
export class ComponentDiscovery {
|
|
34
|
+
/** null records a module that had no components or could not evaluate */
|
|
35
|
+
readonly #discovered = new Map<string, LoadedComponents | null>()
|
|
36
|
+
|
|
37
|
+
clear(): void {
|
|
38
|
+
this.#discovered.clear()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** host-resolved ids of every module discovery found components in */
|
|
42
|
+
ids(): string[] {
|
|
43
|
+
return [...this.#discovered]
|
|
44
|
+
.filter(([, loaded]) => loaded !== null)
|
|
45
|
+
.map(([id]) => id)
|
|
46
|
+
.sort()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** re-register everything found so far into a freshly built registry */
|
|
50
|
+
seed(registry: CompilerComponentRegistry): void {
|
|
51
|
+
for (const [id, loaded] of this.#discovered) {
|
|
52
|
+
if (!loaded) continue
|
|
53
|
+
registry.modulesById.set(id, id)
|
|
54
|
+
registry.componentsByModule.set(id, loaded)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async prepare(
|
|
59
|
+
module: MaterializedModule,
|
|
60
|
+
registry: CompilerComponentRegistry,
|
|
61
|
+
evaluate: ComponentModuleEvaluator | undefined
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
if (!evaluate) return
|
|
64
|
+
const seen = new Set<string>()
|
|
65
|
+
const provenances = [
|
|
66
|
+
...module.elements.map((element) => element.component.provenance),
|
|
67
|
+
...module.styledDefinitions.map((definition) => definition.base.provenance),
|
|
68
|
+
]
|
|
69
|
+
for (const provenance of provenances) {
|
|
70
|
+
// a relative or aliased import is the app's own module: the graph
|
|
71
|
+
// already models its styled definitions, and evaluating app code at
|
|
72
|
+
// build time runs its side effects
|
|
73
|
+
if (!provenance || !isPackageSpecifier(provenance.specifier)) continue
|
|
74
|
+
const id = provenance.resolvedId.split(/[?#]/, 1)[0]
|
|
75
|
+
if (seen.has(id) || registry.modulesById.has(id) || this.#discovered.has(id)) {
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
seen.add(id)
|
|
79
|
+
if (!path.isAbsolute(id)) {
|
|
80
|
+
this.#discovered.set(id, null)
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
let loaded: LoadedComponents | null = null
|
|
84
|
+
try {
|
|
85
|
+
const exports = await evaluate({ id, specifier: provenance.specifier })
|
|
86
|
+
if (exports) {
|
|
87
|
+
const nameToInfo = getComponentStaticConfigByName(
|
|
88
|
+
id,
|
|
89
|
+
(exports as { default?: unknown }).default ?? exports
|
|
90
|
+
)
|
|
91
|
+
if (Object.keys(nameToInfo).length) loaded = { moduleName: id, nameToInfo }
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (process.env.DEBUG === 'tamagui') {
|
|
95
|
+
console.info(`[tamagui] component discovery skipped ${id}:`, error)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
this.#discovered.set(id, loaded)
|
|
99
|
+
if (loaded) {
|
|
100
|
+
registry.modulesById.set(id, id)
|
|
101
|
+
registry.componentsByModule.set(id, loaded)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|