Skip to content

Commit 4fdc696

Browse files
cursoragentskovhus
andcommitted
Adjust markerFile logic: allow undefined return, only use for cross-file markers
Two changes to marker file handling: 1. markerFile adapter function can now return undefined, which falls back to the default behavior (local sidecar file next to the source). 2. The adapter's markerFile is only consulted when a file has cross-file marker relations. Internal-only markers (e.g. sibling selectors within the same file) always use a local sidecar file, avoiding pollution of a shared marker file with file-local concerns. Co-authored-by: Kenneth Skovhus <skovhus@users.noreply.github.com>
1 parent 825e25f commit 4fdc696

20 files changed

+74
-20
lines changed

src/__tests__/transform.test.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -406,19 +406,24 @@ describe("transform", () => {
406406
const normalizedExpected = await normalizeCode(output, outputPath);
407407
expect(normalizedResult).toEqual(normalizedExpected);
408408

409-
// Verify sidecar marker content: all marker declarations should be present in the shared markers file
409+
// Verify sidecar marker content
410410
if (diagnostics.sidecarContent) {
411-
const sharedMarkersPath = join(testCasesDir, "markers.stylex.ts");
412-
const sharedMarkers = readFileSync(sharedMarkersPath, "utf-8");
413411
const markerLines = diagnostics.sidecarContent
414412
.split("\n")
415413
.filter((line) => line.startsWith("export const"));
416414
expect(markerLines.length).toBeGreaterThan(0);
417-
for (const line of markerLines) {
418-
expect(sharedMarkers).toContain(line);
415+
416+
if (diagnostics.sidecarFilePath) {
417+
// Cross-file markers: sidecarFilePath points to the shared markers file
418+
const sharedMarkersPath = join(testCasesDir, "markers.stylex.ts");
419+
const sharedMarkers = readFileSync(sharedMarkersPath, "utf-8");
420+
for (const line of markerLines) {
421+
expect(sharedMarkers).toContain(line);
422+
}
423+
expect(diagnostics.sidecarFilePath).toBe(sharedMarkersPath);
419424
}
420-
// Verify sidecarFilePath points to the shared markers file
421-
expect(diagnostics.sidecarFilePath).toBe(sharedMarkersPath);
425+
// Internal-only markers (e.g. sibling selectors): no sidecarFilePath,
426+
// default local sidecar file is used
422427
}
423428
});
424429
});

src/adapter.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,12 @@ export interface Adapter {
752752
*
753753
* When provided, the function receives the source file path and returns an `ImportSource`
754754
* that determines both the import path in the transformed file and the file path where
755-
* markers are written.
755+
* markers are written. Return `undefined` to fall back to the default behavior (local
756+
* sidecar file next to the source).
757+
*
758+
* Only consulted when a file has cross-file marker relations. Files that only
759+
* reference markers internally (e.g., sibling selectors within the same file)
760+
* always use a local sidecar file regardless of this setting.
756761
*
757762
* Example:
758763
* ```typescript
@@ -761,7 +766,7 @@ export interface Adapter {
761766
* }
762767
* ```
763768
*/
764-
markerFile?: (context: MarkerFileContext) => ImportSource;
769+
markerFile?: (context: MarkerFileContext) => ImportSource | undefined;
765770
}
766771

767772
// ────────────────────────────────────────────────────────────────────────────

src/internal/lower-rules.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export function lowerRules(ctx: TransformContext): {
2222
ancestorSelectorParents: Set<string>;
2323
usedCssHelperFunctions: Set<string>;
2424
crossFileMarkers: Map<string, string>;
25+
/** True when at least one marker originates from a cross-file relation (not just internal sibling selectors) */
26+
hasCrossFileMarkerRelations: boolean;
2527
siblingMarkerKeys: Set<string>;
2628
parentsNeedingDefaultMarker: Set<string>;
2729
/** Maps style key → set of CSS attribute selector strings used in ancestor attribute conditions */
@@ -121,9 +123,11 @@ export function lowerRules(ctx: TransformContext): {
121123
// Cross-file overrides use markers for parents that need them;
122124
// sibling selectors use per-component markers for scoped matching.
123125
const crossFileMarkers = new Map<string, string>();
126+
let hasCrossFileMarkerRelations = false;
124127
for (const o of state.relationOverrides) {
125128
if (o.crossFile && o.markerVarName && parentsNeedingMarker.has(o.parentStyleKey)) {
126129
crossFileMarkers.set(o.parentStyleKey, o.markerVarName);
130+
hasCrossFileMarkerRelations = true;
127131
}
128132
}
129133
for (const [styleKey, markerName] of state.siblingMarkerNames) {
@@ -159,6 +163,7 @@ export function lowerRules(ctx: TransformContext): {
159163
ancestorSelectorParents: filteredAncestorParents,
160164
usedCssHelperFunctions: state.usedCssHelperFunctions,
161165
crossFileMarkers,
166+
hasCrossFileMarkerRelations,
162167
siblingMarkerKeys: new Set(state.siblingMarkerNames.keys()),
163168
parentsNeedingDefaultMarker,
164169
ancestorAttrsByStyleKey: state.ancestorAttrsByStyleKey,

src/internal/transform-context.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ export class TransformContext {
9999
bridgeComponentNames?: Set<string>;
100100
/** Marker variable names generated for cross-file parent components and sibling selectors (parentStyleKey → markerName) */
101101
crossFileMarkers?: Map<string, string>;
102+
/** True when at least one marker originates from a cross-file relation (not just internal sibling selectors) */
103+
hasCrossFileMarkerRelations?: boolean;
102104
/** Style keys that use sibling markers (scoped marker replaces defaultMarker) */
103105
siblingMarkerKeys?: Set<string>;
104106
/** Parent style keys that need defaultMarker() (have at least one override without a scoped marker) */

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,13 @@ function emitDefineMarkerDeclarations(
116116
.join("\n\n");
117117
ctx.sidecarStylexContent = `import * as stylex from "@stylexjs/stylex";\n\n${markerDecls}\n`;
118118

119-
// Determine sidecar import path — use adapter.markerFile if provided, otherwise derive from basename
119+
// Use adapter.markerFile only when at least one marker is truly cross-file.
120+
// Internal-only markers (sibling selectors within the same file) use the default
121+
// local sidecar so they don't pollute a shared marker file.
120122
let sidecarImportPath: string;
121-
const adapterMarkerFile = ctx.adapter.markerFile;
122-
if (adapterMarkerFile) {
123-
const importSource = adapterMarkerFile({ filePath: ctx.file.path });
123+
const adapterMarkerFile = ctx.hasCrossFileMarkerRelations ? ctx.adapter.markerFile : undefined;
124+
const importSource = adapterMarkerFile?.({ filePath: ctx.file.path });
125+
if (importSource) {
124126
sidecarImportPath = importSourceToModuleSpecifier(importSource, ctx.file.path);
125127
ctx.sidecarFilePath = importSourceToAbsolutePath(importSource, ctx.file.path);
126128
} else {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export function lowerRulesStep(ctx: TransformContext): StepResult {
2121
ctx.relationOverrides = lowered.relationOverrides;
2222
ctx.ancestorSelectorParents = lowered.ancestorSelectorParents;
2323
ctx.crossFileMarkers = lowered.crossFileMarkers;
24+
ctx.hasCrossFileMarkerRelations = lowered.hasCrossFileMarkerRelations;
2425
ctx.siblingMarkerKeys = lowered.siblingMarkerKeys;
2526
ctx.parentsNeedingDefaultMarker = lowered.parentsNeedingDefaultMarker;
2627
ctx.ancestorAttrsByStyleKey = lowered.ancestorAttrsByStyleKey;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import * as stylex from "@stylexjs/stylex";
2+
3+
/** Custom marker for Link */
4+
export const LinkMarker = stylex.defineMarker();

test-cases/selector-componentSiblingCombinator.output.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react";
22
import * as stylex from "@stylexjs/stylex";
3-
import { LinkMarker } from "./markers.stylex";
3+
import { LinkMarker } from "./selector-componentSiblingCombinator.input.stylex";
44

55
function Link(props: React.ComponentProps<"a">) {
66
const { children, ...rest } = props;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import * as stylex from "@stylexjs/stylex";
2+
3+
/** Custom marker for Thing */
4+
export const ThingMarker = stylex.defineMarker();

test-cases/selector-generalSibling.output.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react";
22
import * as stylex from "@stylexjs/stylex";
3-
import { ThingMarker } from "./markers.stylex";
3+
import { ThingMarker } from "./selector-generalSibling.input.stylex";
44

55
function Thing(props: React.PropsWithChildren<{}>) {
66
return <div sx={[styles.thing, ThingMarker]}>{props.children}</div>;

0 commit comments

Comments
 (0)