Skip to content

compile().warnings() is unreachable from a real build: no Metro consumer #424

Closed

Description

@YevheniiKotyrlo

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:

  1. metro-transformer.ts reads .warnings() after .stylesheet() and logs a summary through Metro's reporter.
  2. The same, off by default behind a reactNativeCSS option, so an existing project does not gain a wall of output.
  3. 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 found

Steps to reproduce

  1. Clone the reproducer branch, which is main at 2421ea7 with two files added in src/__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
  2. 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

  1. github-actions commented on Aug 15, 2026

    @github-actions

    🤖 Auto-triage

    Status: CONFIRMED
    Type: feature
    Version: v5

    Findings

    Verified the claim end-to-end. compile().warnings() is defined at src/compiler/compiler.ts:202 and the only non-test consumer is src/jest/index.ts:46, which is gated behind REACT_NATIVE_CSS_TEST_DEBUG + --inspect. src/metro/metro-transformer.ts:39-43 calls .stylesheet() and never touches .warnings(), so the 50 addWarning(...) call sites in src/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 report
    • rg for addWarning|\.warnings\(\)|getWarnings across src/ — confirmed the 50 producer sites and the single gated consumer
    • Inspected src/metro/metro-transformer.ts — confirmed only .stylesheet() is called on the compile result
    • Inspected src/compiler/compiler.ts:202 and src/compiler/stylesheet.ts:200-230 — confirmed the API shape

    Next steps

    • Product decision needed on the reporter's three options: (1) always log via Metro reporter, (2) opt-in flag on reactNativeCSS config, (3) declare the channel tooling-only. Reporter has offered to PR option 1 or 2.
    • If accepting a log surface, decide on aggregation (dedupe per file? per class?) to avoid noise from Tailwind-scale stylesheets.
    • Coordinate with fix(compiler): scope ::selection / ::placeholder declarations to the pseudo-element #411 which introduced additional warnings that also went unreachable.

    This is an automated triage. See auto-triage.yml.

  2. added
    auto-triagedIssue has been automatically triaged by the auto-triage workflow
    confirmedBug reproduced and confirmed by triage
    on Aug 15, 2026
  3. YevheniiKotyrlo commented on Aug 15, 2026

    @YevheniiKotyrlo
    ContributorAuthor

    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:

    • Aggregation. Compiling the Tailwind corpus already in the repo (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.
    • fix(compiler): scope ::selection / ::placeholder declarations to the pseudo-element #411. The warnings that PR adds become reachable through the same path, with no coordination needed.

    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.

  4. changed the title [-]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[/+] on Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    auto-triagedIssue has been automatically triaged by the auto-triage workflowconfirmedBug reproduced and confirmed by triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions