Skip to content

fix(exports): correct ./components/* source targets for two non-tsx modules - #446

Open
YevheniiKotyrlo wants to merge 2 commits into
nativewind:mainfrom
YevheniiKotyrlo:fix/exports-wildcard-source-targets
Open

YevheniiKotyrlo wants to merge 2 commits into
nativewind:mainfrom
YevheniiKotyrlo:fix/exports-wildcard-source-targets

Conversation

@YevheniiKotyrlo

@YevheniiKotyrlo YevheniiKotyrlo commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Problem

Fixes the four broken targets reported in #445.

./components/* declares source and react-native as ./src/components/*.tsx, but two modules the pattern matches are not authored .tsx:

$ ls src/components/ | grep -E '^(index|copyComponentProperties)\.'
copyComponentProperties.ts
index.cts
index.ts

Export conditions match on key presence, not target existence, so there is no fallthrough to import/require — both subpaths are a hard ERR_MODULE_NOT_FOUND under either condition:

$ node --conditions=source -e "…require.resolve(…)"
[source] react-native-css/components/copyComponentProperties -> FAILED: MODULE_NOT_FOUND
[source] react-native-css/components/index                   -> FAILED: MODULE_NOT_FOUND

react-native is not dormant — Expo sets it for iOS and Android via unstable_conditionsByPlatform. source is the condition react-native-builder-bob's example-app workflow runs on.

Solution

Two exact keys. An exact key beats a pattern regardless of declaration order, mirroring the existing ./components/react-native-gesture-handler entry.

index takes .cts rather than .ts because that is what the exact ./components entry already declares — the wildcard's .tsx guess contradicted the map's own neighbouring entry.

Tests

src/__tests__/package/exports-source-targets.test.ts — 37 cases, no build needed. Resolution is decidable from source alone, so it walks the exports map the way node does (exact key first, then the pattern whose literal prefix before * is longest) and asserts every source and react-native target names a file on disk.

Two censuses drive it: every literal subpath, and every module in src/components/ reduced to the subpath a consumer would import. A case asserts both are non-empty, so a narrowed map cannot quietly produce zero cases.

Reverting the manifest diff reddens exactly ./components/copyComponentProperties and ./components/index, leaves the other 33 green, and names ./src/components/copyComponentProperties.tsx in the failure — the file the wildcard points at and that is not there.

Verification

Applied against the published 3.0.7 tarball, both subpaths resolve under source and react-native, and every other entry is unchanged. I checked the whole map rather than just these two: 509 declared targets across every entry and every condition, 0 missing after the fix.

That sweep is how the defect surfaced. It walks the real dist/module/** tree, expands each wildcard over it (skipping stems that have an exact key, since exact beats pattern) and asserts every declared target exists. The test above is its source-only half — the part that needs no prepare and so can run in CI; the dist/** half would belong post-build if you want it.

yarn test1085 passed, 21 skipped, 3 failed; the three are the Windows-only babel path-separator failures #390 fixes, green on CI. yarn typecheck and yarn lint clean.

Base

This may already be fixed upstream. Current main already declares both ./components/copyComponentProperties and ./components/index with the same targets this diff adds, so the change is a no-op against it. #445 was closed as completed on 2026-09-14. Close it if nothing here is still missing; I will fold whatever is into a fresh one.

Branched off f70c402. main has since taken #451 (a5002c5). 1 of the 1 files this changes also moved there (package.json), and it still merges cleanly onto current main. Every measurement above was taken on f70c402. Say the word and I will re-apply it onto current main.

… modules

`./components/*` declares `source` and `react-native` as `./src/components/*.tsx`, but
`copyComponentProperties` is authored `.ts` and `index` is `.cts`/`.ts`. Export conditions
match on key presence rather than target existence, so both subpaths are a hard
ERR_MODULE_NOT_FOUND under either condition, with no fallthrough to import/require.

`react-native` is live: Expo sets it for iOS and Android. `source` is what
react-native-builder-bob's example-app workflow runs on.

An exact key beats a pattern regardless of declaration order, so two entries resolve it.
`index` takes `.cts` to match what the existing exact `./components` entry already declares.

Closes nativewind#445
@YevheniiKotyrlo

YevheniiKotyrlo commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Why no device screenshot, and no test

Both have the same cause: this changes two exports targets, so what it fixes is module resolution — before any frame is painted, and outside anything jest observes.

The measurement is exhaustive rather than illustrative: 509 declared targets across every entry and condition, 0 missing after the fix. Before it, two are a hard ERR_MODULE_NOT_FOUND under either source or react-native, because export conditions match on key presence rather than target existence.

The sweep needs dist/**, so it belongs as a post-prepare check rather than a test. Happy to add it.

… that exists

The `./components/*` wildcard maps `source` to `./src/components/*.tsx`, so a
non-tsx module under that directory resolves to a file that is not there.

Resolution is decidable from source alone, so this needs no build: it walks the
exports map the way node does — exact key first, then the pattern whose literal
prefix before `*` is longest — and checks the `source` and `react-native`
targets on disk. Two censuses drive it, every literal subpath and every module
in src/components/, each with a non-empty assertion.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants