Repository navigation
compile().warnings() is unreachable from a real build: no Metro consumer #424
Description
Before submitting a new issue
- I tested using the latest version of the library, as the bug might be already fixed.
- I tested using a supported version of react native.
- I checked for possible duplicate issues, with possible answers.
Bug summary
StylesheetBuilder.addWarning records each declaration the compiler cannot convert, an unsupported property or a value with no React Native equivalent, and compile(css).warnings() returns them. Nothing in a real build reads them. The transformer withReactNativeCSS installs, src/metro/metro-transformer.ts, calls .stylesheet() alone:
const productionJS = compile(css, {
...config.reactNativeCSS,
filename: filePath,
projectRoot: projectRoot,
}).stylesheet();and the one other reader, src/jest/index.ts, prints them only when REACT_NATIVE_CSS_TEST_DEBUG is set and NODE_OPTIONS carries --inspect. So a declaration with no React Native equivalent is dropped with nothing printed: the class name is present, the rule compiled, and one declaration did nothing, which is the class of problem a developer cannot diagnose from the outside.
This is how the channel has always worked, so it is not a regression. The shapes I see:
metro-transformer.tsreads.warnings()after.stylesheet()and logs a summary through Metro's reporter.- The same, off by default behind a
reactNativeCSSoption, so an existing project does not gain a wall of output. - Nothing, if the channel is meant only as a compiler API for tooling, in which case it is worth saying so, because the call sites read as user-facing diagnostics.
#430 does (1) with (2)'s option to silence it.
Library version
main at 2421ea7 (0.0.0-nightly.2421ea7)
Environment info
System:
OS: Windows 11 10.0.26300
CPU: "(32) x64 AMD Ryzen 9 9950X3D 16-Core Processor "
Memory: 155.26 GB / 255.67 GB
Binaries:
Node:
version: 26.12.1
path: C:\Program Files\nodejs\node.EXE
Yarn:
version: 4.9.2
path: C:\Users\<user>\AppData\Roaming\npm\yarn.CMD
npm:
version: 12.2.1
path: C:\Program Files\nodejs\npm.CMD
Watchman: Not Found
SDKs:
Android SDK:
API Levels:
- "33"
- "35"
- "35"
- "35"
- "36"
- "37"
Build Tools:
- 35.0.0
- 36.0.0
- 36.1.0
- 37.0.0
System Images:
- android-26 | Google APIs Intel x86_64 Atom
- android-28 | Google APIs Intel x86_64 Atom
- android-29 | Google APIs Intel x86_64 Atom
- android-30 | Google APIs Intel x86_64 Atom
- android-31 | Google APIs Intel x86_64 Atom
- android-32 | Google APIs Intel x86_64 Atom
- android-33 | Google APIs Intel x86_64 Atom
- android-34 | Google APIs Intel x86_64 Atom
- android-35 | Google APIs Intel x86_64 Atom
- android-36.1 | Google Play Intel x86_64 Atom
- android-36.1 | Pre-Release 16 KB Page Size Google Play Intel x86_64 Atom
- android-37.0 | Google APIs Intel x86_64 Atom
Android NDK: Not Found
Windows SDK: Not Found
IDEs:
Android Studio: AI-262.9437.185.2621.16467767
Visual Studio: Not Found
Languages:
Java:
version: 17.0.20.1
path: C:\Program Files\Microsoft\jdk-17.0.20.101-hotspot\bin\javac.EXE
Ruby: Not Found
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 19.2.3
wanted: 19.2.3
react-native:
installed: 0.86.3
wanted: 0.86.3
react-native-windows: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: Not found
newArchEnabled: Not found
iOS:
hermesEnabled: Not found
newArchEnabled: Not foundSteps to reproduce
- Clone the reproducer branch, which is
mainat 2421ea7 with two files added insrc/__tests__/metro: the test, and a stand-in for Expo's transform worker that hands a stylesheet back as CSS and anything else as code, so the test runs the transformer with no app around it. Install:git clone --branch repro/424-compiler-warnings-unreachable https://github.com/YevheniiKotyrlo/react-native-css cd react-native-css yarn install - Run
yarn jest src/__tests__/metro/compiler-warnings-reach-the-build.test.ts.
src/__tests__/metro/compiler-warnings-reach-the-build.test.ts:
// A declaration the compiler drops is reported where a developer reads a build: the terminal running Metro
import { join } from "path";
import type { JsTransformOptions } from "metro-transform-worker";
import { compile } from "../../compiler";
import { transform } from "../../metro/metro-transformer";
jest.mock("@expo/metro-config", () => ({
unstable_transformerPath: require.resolve("./_transform-worker"),
}));
type TransformerConfig = Parameters<typeof transform>[0];
const css = `.a { float: left; }`;
afterEach(() => {
jest.restoreAllMocks();
});
test("the compiler records the dropped declaration", () => {
expect(compile(css).warnings()).toStrictEqual({ properties: ["float"] });
});
test("the transformer Metro runs for a native stylesheet reports it", async () => {
const written: string[] = [];
for (const method of ["log", "info", "warn", "error"] as const) {
jest.spyOn(console, method).mockImplementation((...args: unknown[]) => {
written.push(args.map((argument) => String(argument)).join(" "));
});
}
await transform(
{} as TransformerConfig,
process.cwd(),
join(process.cwd(), "global.css"),
Buffer.from(css),
{ type: "module", platform: "ios" } as JsTransformOptions,
);
expect(written.join("\n")).toContain("float");
});src/__tests__/metro/_transform-worker.ts:
import type { JsTransformOptions } from "metro-transform-worker";
// Stands in for Expo's transform worker: on web a stylesheet comes back as CSS, and anything else as code
export function transform(
_config: unknown,
_projectRoot: string,
filename: string,
data: Buffer,
options: JsTransformOptions,
) {
return Promise.resolve(
options.platform === "web" && filename.endsWith(".css")
? { output: [{ data: { css: { code: data } } }] }
: { output: [{ data: { code: data.toString() } }] },
);
}Expected: transforming a native stylesheet with .a { float: left; } reports the dropped float where the bundler's output is read, as compile().warnings() records it.
Actual: compile().warnings() returns { properties: ["float"] }, and the transformer writes nothing to the console.
The run's output on main at 2421ea7, Windows 11, Node 26.12.1
FAIL src/__tests__/metro/compiler-warnings-reach-the-build.test.ts (7.134 s)
√ the compiler records the dropped declaration (11 ms)
× the transformer Metro runs for a native stylesheet reports it (2 ms)
● the transformer Metro runs for a native stylesheet reports it
expect(received).toContain(expected) // indexOf
Expected substring: "float"
Received string: ""
39 | );
40 |
> 41 | expect(written.join("\n")).toContain("float");
| ^
42 | });
43 |
at Object.toContain (src/__tests__/metro/compiler-warnings-reach-the-build.test.ts:41:30)
at asyncGeneratorStep (node_modules/@babel/runtime/helpers/asyncToGenerator.js:3:17)
at _next (node_modules/@babel/runtime/helpers/asyncToGenerator.js:17:9)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 8.177 s
Ran all test suites matching /src\\__tests__\\metro\\compiler-warnings-reach-the-build.test.ts/i.
Reproducible example repository
https://github.com/YevheniiKotyrlo/react-native-css/tree/repro/424-compiler-warnings-unreachable
Activity
github-actions commentedon Aug 15, 2026
🤖 Auto-triage
Status: CONFIRMED
Type: feature
Version: v5
Findings
Verified the claim end-to-end.
compile().warnings()is defined atsrc/compiler/compiler.ts:202and the only non-test consumer issrc/jest/index.ts:46, which is gated behindREACT_NATIVE_CSS_TEST_DEBUG+--inspect.src/metro/metro-transformer.ts:39-43calls.stylesheet()and never touches.warnings(), so the 50addWarning(...)call sites insrc/compiler/declarations.ts(47"value"+ 3"property") never surface in a real build. Treated as an enhancement — no functional output is wrong, but a build-time diagnostic channel is going nowhere.What I tested
gh issue view 424— read the reportrgforaddWarning|\.warnings\(\)|getWarningsacrosssrc/— confirmed the 50 producer sites and the single gated consumersrc/metro/metro-transformer.ts— confirmed only.stylesheet()is called on the compile resultsrc/compiler/compiler.ts:202andsrc/compiler/stylesheet.ts:200-230— confirmed the API shapeNext steps
reactNativeCSSconfig, (3) declare the channel tooling-only. Reporter has offered to PR option 1 or 2.This is an automated triage. See auto-triage.yml.
YevheniiKotyrlo commentedon Aug 15, 2026
Opened #430 for this.
I went with option 1 plus option 2's escape hatch — on by default, one capped block per stylesheet,
warnings: "none"to silence — on the reasoning that an opt-in flag leaves the channel unreachable for everyone who does not read the docs, which is what the issue is about. If you would rather it defaulted to"none", that is a one-word change and I will make it.The two questions your triage raised are both answered in the PR with a measurement rather than a preference:
src/__tests__/vendor/tailwind) has 387 of its 759 cases asserting a warning, over 87 distinct properties, so a summary caps at ten entries per channel and counts the rest. A block is also keyed by file and by the message it would print, so it appears when a file's warnings change rather than on every save — otherwise a Tailwind rebuild would reprint it on every keystroke.Native only, and web carries no path rather than being skipped: the transformer returns to the stock Expo transformer before the compiler is reached, so a web bundle produces no warnings at all. There is a test asserting exactly that, including that the CSS was still processed whole.
[-]compile().warnings() is unreachable from a real build: 50 addWarning call sites, no Metro consumer[/-][+]compile().warnings() is unreachable from a real build: no Metro consumer[/+]