Skip to content

fix(build): resolve app imports of astryx to source in withAstryx - #5932

Open
PRIEYAN wants to merge 1 commit into
facebook:mainfrom
PRIEYAN:fix/withastryx-source-condition-by-request
Open

fix(build): resolve app imports of astryx to source in withAstryx#5932
PRIEYAN wants to merge 1 commit into
facebook:mainfrom
PRIEYAN:fix/withastryx-source-condition-by-request

Conversation

@PRIEYAN

@PRIEYAN PRIEYAN commented Sep 2, 2026

Copy link
Copy Markdown

withAstryx() installs a rule that carries the source condition on Rule.resolve, scoped
by test to node_modules/@astryxdesign/. But Rule.test matches the module being
processed and Rule.resolve governs the requests that module makes, so the rule only
covers astryx-to-astryx imports. An app's own @astryxdesign/* imports are issued from its
sources, outside node_modules, so they never match and resolve through default to dist.

Dist's runtime emits x-prefixed atomic class names while the PostCSS pass compiles the
library from source and emits astryx-prefixed rules. The two sets share no class names, so
the build exits 0, the route prerenders, a full-size stylesheet is served, and the page
renders unstyled with nothing logged. apps/example-nextjs-source uses withAstryx({}) as
written, so a team following it ships an unstyled site.

Widening the global conditionNames does fix resolution, but it breaks React's JSX
resolution and mis-resolves third-party packages that also publish a source condition
(lexical) — which is exactly what the scoped rule exists to prevent. Webpack rules cannot
key on the request string, so there is no rule shape that expresses "requests for
@astryxdesign/* from app code". Instead this reads the source targets out of each
installed astryx package's export map and sets them as resolve.alias entries: resolution
follows the astryx packages themselves and the global conditions stay as Next resolved them.
Subpaths get an entry each, since @astryxdesign/core/AlertDialog is as much a documented
entry point as the root — aliasing only the root would leave 119 subpaths on dist and produce
a mixed bundle. A user-supplied alias still wins, and packages that are absent or ship no
source condition keep normal resolution.

Note that require.resolve('@astryxdesign/core/package.json') throws
ERR_PACKAGE_PATH_NOT_EXPORTED — core's exports has no ./package.json key — so the
manifest lookup walks node_modules the way Node resolves a bare specifier.

On verification, being precise about what I did and did not run. I added
packages/build/src/next.test.mjs, 9 assertions over the config withAstryx() emits against
a fixture package laid out the way npm installs one. It fails on 5 of them before this change
and passes all 9 after. I could not run it through the repo's own vitest — pnpm install
pulls the full monorepo toolchain and I deliberately kept build tooling off this machine — so
I executed the same assertions directly against the module under Node; CI will be the first
run through vitest proper. check:changesets, check:cli-structure, check:portable-scripts,
check:executable-bits and check:use-client pass locally.

I have not reproduced the unstyled-page symptom end to end. There is no next build here, so
the class-prefix mismatch itself is verified from the resolution semantics and the reporter's
measurements rather than observed. The reporter has a harness that automates this across every
documented setup and both bundlers; pointing it at this branch would be a stronger signal than
anything I can produce locally.

Turbopack is untouched — withAstryx() writes nextConfig.webpack, which Turbopack ignores,
and that is filed separately as #5921.

Fixes #5920

@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Sep 2, 2026 7:23pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Sep 2, 2026
The scoped rule withAstryx installs carries the `source` condition on
`Rule.resolve`, but `Rule.test` matches the module being processed and
`Rule.resolve` governs the requests that module makes. An app's own
`@astryxdesign/*` imports are issued from its sources, outside
node_modules, so they never match the rule and resolve through `default`
to dist.

The dist runtime emits `x`-prefixed atomic class names while the PostCSS
pass compiles the library from source and emits `astryx`-prefixed rules.
The two sets are disjoint, so the build exits 0, the route prerenders, a
full stylesheet is served, and the page renders unstyled with nothing
logged. apps/example-nextjs-source uses `withAstryx({})` as written, so
following it ships an unstyled site.

Widening the global conditionNames fixes resolution but breaks React's
JSX resolution and mis-resolves third-party packages that also publish a
`source` condition, which is what the scoped rule exists to avoid.
Webpack rules cannot key on the request string, so map the packages'
`source` export targets to aliases instead: resolution follows the astryx
packages themselves and the global conditions stay as Next resolved them.
Subpaths get an entry each, since `@astryxdesign/core/AlertDialog` is as
much a documented entry point as the root.

Fixes facebook#5920
@github-actions github-actions Bot added community Authored by a community contributor (not on the eng/design team) needs:code-review High-risk change (new package/component/API) — needs human code review before merge labels Sep 2, 2026
@PRIEYAN
PRIEYAN force-pushed the fix/withastryx-source-condition-by-request branch from d924b3b to c018a31 Compare September 2, 2026 19:17

@cixzhang cixzhang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing the silent unstyled build. One compatibility issue still blocks this: an ordinary caller alias such as {'@astryxdesign/core': customPath} loses to the generated exact aliases, so composed Next configs silently load Astryx source instead of the caller's implementation. Please preserve both exact and prefix caller aliases; a resolver-level regression test would cover the real behavior.

Also please run Prettier on the two changed source files.

[Reviewed by Robohands]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. community Authored by a community contributor (not on the eng/design team) needs:code-review High-risk change (new package/component/API) — needs human code review before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Infra] withAstryx() resolves @astryxdesign/core to dist while PostCSS compiles it from source, producing a silently unstyled app

2 participants