Skip to content

Commit 7903746

Browse files
committed
fix: only emit defaultMarker() when parent has non-scoped overrides
Parents that only have scoped marker overrides (sibling selectors, no-pseudo ancestor with defineMarker) don't need defaultMarker() — it would be unnecessary overhead. Only emit defaultMarker() when the parent has at least one override without a markerVarName, which means it uses stylex.when.ancestor(':pseudo') with no marker argument. Introduces parentsNeedingDefaultMarker set computed in lower-rules.ts and plumbed through the pipeline to both JSX emission paths (rewrite-jsx.ts for inlined components, style-merger.ts for wrappers). https://claude.ai/code/session_01X1Gxfz4reNWtojK37AJpsT
1 parent b623f7f commit 7903746

16 files changed

Lines changed: 51 additions & 18 deletions

src/internal/emit-wrappers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export function emitWrappers(args: {
2626
ancestorSelectorParents?: Set<string>;
2727
crossFileMarkers?: Map<string, string>;
2828
siblingMarkerKeys?: Set<string>;
29+
parentsNeedingDefaultMarker?: Set<string>;
2930
useSxProp: boolean;
3031
}): void {
3132
const {
@@ -43,6 +44,7 @@ export function emitWrappers(args: {
4344
ancestorSelectorParents,
4445
crossFileMarkers,
4546
siblingMarkerKeys,
47+
parentsNeedingDefaultMarker,
4648
useSxProp,
4749
} = args;
4850

@@ -66,6 +68,7 @@ export function emitWrappers(args: {
6668
ancestorSelectorParents,
6769
crossFileMarkers,
6870
siblingMarkerKeys,
71+
parentsNeedingDefaultMarker,
6972
useSxProp,
7073
});
7174

src/internal/emit-wrappers/style-merger.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export function emitStyleMerging(args: {
7272
| "emptyStyleKeys"
7373
| "ancestorSelectorParents"
7474
| "crossFileMarkers"
75+
| "parentsNeedingDefaultMarker"
7576
| "emitTypes"
7677
| "useSxProp"
7778
>;
@@ -107,6 +108,7 @@ export function emitStyleMerging(args: {
107108
stylesIdentifier,
108109
ancestorSelectorParents,
109110
crossFileMarkers,
111+
parentsNeedingDefaultMarker,
110112
emitTypes,
111113
} = emitter;
112114

@@ -133,7 +135,12 @@ export function emitStyleMerging(args: {
133135
if (markerVarName) {
134136
pendingMarkers.push(j.identifier(markerVarName));
135137
}
136-
needsDefaultMarker = true;
138+
// Only emit defaultMarker() when this parent has at least one override
139+
// without a scoped marker. Pure sibling/no-pseudo cases only need
140+
// their scoped marker.
141+
if (!markerVarName || parentsNeedingDefaultMarker.has(key)) {
142+
needsDefaultMarker = true;
143+
}
137144
}
138145
styleArgs.push(...pendingMarkers);
139146
if (needsDefaultMarker) {

src/internal/emit-wrappers/wrapper-emitter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ type WrapperEmitterArgs = {
5050
crossFileMarkers?: Map<string, string>;
5151
/** Style keys that use sibling markers (scoped marker replaces defaultMarker) */
5252
siblingMarkerKeys?: Set<string>;
53+
/** Parent style keys that need defaultMarker() (have at least one override without a scoped marker) */
54+
parentsNeedingDefaultMarker?: Set<string>;
5355
useSxProp: boolean;
5456
};
5557

@@ -68,6 +70,7 @@ export class WrapperEmitter {
6870
readonly ancestorSelectorParents: Set<string>;
6971
readonly crossFileMarkers: Map<string, string>;
7072
readonly siblingMarkerKeys: Set<string>;
73+
readonly parentsNeedingDefaultMarker: Set<string>;
7174
readonly useSxProp: boolean;
7275

7376
// For plain JS/JSX and Flow transforms, skip emitting TS syntax entirely for now.
@@ -95,6 +98,7 @@ export class WrapperEmitter {
9598
this.ancestorSelectorParents = args.ancestorSelectorParents ?? new Set<string>();
9699
this.crossFileMarkers = args.crossFileMarkers ?? new Map<string, string>();
97100
this.siblingMarkerKeys = args.siblingMarkerKeys ?? new Set<string>();
101+
this.parentsNeedingDefaultMarker = args.parentsNeedingDefaultMarker ?? new Set<string>();
98102
this.useSxProp = args.useSxProp;
99103
this.emitTypes = this.filePath.endsWith(".ts") || this.filePath.endsWith(".tsx");
100104
}

src/internal/lower-rules.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export function lowerRules(ctx: TransformContext): {
2323
usedCssHelperFunctions: Set<string>;
2424
crossFileMarkers: Map<string, string>;
2525
siblingMarkerKeys: Set<string>;
26+
parentsNeedingDefaultMarker: Set<string>;
2627
bail: boolean;
2728
} {
2829
const state = createLowerRulesState(ctx);
@@ -139,13 +140,25 @@ export function lowerRules(ctx: TransformContext): {
139140
}
140141
}
141142

143+
// Parents that have at least one override WITHOUT a scoped marker need
144+
// defaultMarker() so that `stylex.when.ancestor(':pseudo')` (no marker arg) can match.
145+
// Parents whose overrides ALL use scoped markers (e.g. pure sibling selectors)
146+
// only need their scoped marker — defaultMarker() would be unnecessary overhead.
147+
const parentsNeedingDefaultMarker = new Set<string>();
148+
for (const o of state.relationOverrides) {
149+
if (!o.markerVarName && parentsNeedingMarker.has(o.parentStyleKey)) {
150+
parentsNeedingDefaultMarker.add(o.parentStyleKey);
151+
}
152+
}
153+
142154
return {
143155
resolvedStyleObjects: state.resolvedStyleObjects,
144156
relationOverrides: state.relationOverrides,
145157
ancestorSelectorParents: filteredAncestorParents,
146158
usedCssHelperFunctions: state.usedCssHelperFunctions,
147159
crossFileMarkers,
148160
siblingMarkerKeys: new Set(state.siblingMarkerNames.keys()),
161+
parentsNeedingDefaultMarker,
149162
bail: state.bail,
150163
};
151164
}

src/internal/rewrite-jsx.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export function postProcessTransformedAst(args: {
2626
stylesIdentifier?: string;
2727
/** Cross-file marker variables: parentStyleKey → markerVarName */
2828
crossFileMarkers?: Map<string, string>;
29+
/** Parent style keys that need defaultMarker() (have at least one override without a scoped marker) */
30+
parentsNeedingDefaultMarker?: Set<string>;
2931
}): { changed: boolean; needsReactImport: boolean } {
3032
const {
3133
root,
@@ -39,6 +41,7 @@ export function postProcessTransformedAst(args: {
3941
newImportSourcesByLocal,
4042
stylesIdentifier = "styles",
4143
crossFileMarkers,
44+
parentsNeedingDefaultMarker,
4245
} = args;
4346
let changed = false;
4447

@@ -237,10 +240,10 @@ export function postProcessTransformedAst(args: {
237240
changed = true;
238241
}
239242
}
240-
// Always add defaultMarker() for ancestor selector parents — scoped markers
241-
// and defaultMarker() coexist: scoped enables targeted matching while
242-
// defaultMarker() enables regular pseudo-reverse selectors.
243-
if (!hasDefaultMarker(call)) {
243+
// Only add defaultMarker() when this parent has at least one override
244+
// without a scoped marker. Pure sibling/no-pseudo cases only need
245+
// their scoped marker — defaultMarker() would be unnecessary overhead.
246+
if (parentsNeedingDefaultMarker?.has(parentKey) && !hasDefaultMarker(call)) {
244247
call.arguments = [...(call.arguments ?? []), makeDefaultMarkerCall()];
245248
changed = true;
246249
}
@@ -258,8 +261,8 @@ export function postProcessTransformedAst(args: {
258261
changed = true;
259262
}
260263
}
261-
// Always add defaultMarker() — see comment in stylex.props() branch above.
262-
if (!hasDefaultMarkerInSxArgs(sxAttr)) {
264+
// Only add defaultMarker() when needed — see comment in stylex.props() branch above.
265+
if (parentsNeedingDefaultMarker?.has(parentKey) && !hasDefaultMarkerInSxArgs(sxAttr)) {
263266
addArgsToSxAttr(sxAttr, [makeDefaultMarkerCall()]);
264267
changed = true;
265268
}

src/internal/transform-context.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ export class TransformContext {
100100
crossFileMarkers?: Map<string, string>;
101101
/** Style keys that use sibling markers (scoped marker replaces defaultMarker) */
102102
siblingMarkerKeys?: Set<string>;
103+
/** Parent style keys that need defaultMarker() (have at least one override without a scoped marker) */
104+
parentsNeedingDefaultMarker?: Set<string>;
103105
/** Content for the sidecar .stylex.ts file (defineMarker declarations), populated by emitStylesStep */
104106
sidecarStylexContent?: string;
105107
/** Bridge components emitted for unconverted consumer selectors. */

src/internal/transform-steps/emit-wrappers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export function emitWrappersStep(ctx: TransformContext): StepResult {
3131
ancestorSelectorParents: ctx.ancestorSelectorParents,
3232
crossFileMarkers: ctx.crossFileMarkers,
3333
siblingMarkerKeys: ctx.siblingMarkerKeys,
34+
parentsNeedingDefaultMarker: ctx.parentsNeedingDefaultMarker,
3435
useSxProp: ctx.adapter.useSxProp,
3536
});
3637

src/internal/transform-steps/lower-rules.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export function lowerRulesStep(ctx: TransformContext): StepResult {
2222
ctx.ancestorSelectorParents = lowered.ancestorSelectorParents;
2323
ctx.crossFileMarkers = lowered.crossFileMarkers;
2424
ctx.siblingMarkerKeys = lowered.siblingMarkerKeys;
25+
ctx.parentsNeedingDefaultMarker = lowered.parentsNeedingDefaultMarker;
2526

2627
if (lowered.bail || ctx.resolveValueBailRef.value) {
2728
return returnResult({ code: null, warnings: ctx.warnings }, "bail");

src/internal/transform-steps/post-process.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export function postProcessStep(ctx: TransformContext): StepResult {
7171
newImportSourcesByLocal,
7272
stylesIdentifier: ctx.stylesIdentifier,
7373
crossFileMarkers: ctx.crossFileMarkers,
74+
parentsNeedingDefaultMarker: ctx.parentsNeedingDefaultMarker,
7475
});
7576
if (post.changed) {
7677
ctx.markChanged();

test-cases/selector-crossFileBridgeForward.output.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { StyledCollapseButtonMarker } from "./selector-crossFileBridgeForward.in
77
export function StyledCollapseButton(props: Pick<React.ComponentProps<"div">, "ref" | "children">) {
88
const { children, ...rest } = props;
99
return (
10-
<div {...rest} sx={[styles.collapseButton, StyledCollapseButtonMarker, stylex.defaultMarker()]}>
10+
<div {...rest} sx={[styles.collapseButton, StyledCollapseButtonMarker]}>
1111
{children}
1212
</div>
1313
);

0 commit comments

Comments
 (0)