Skip to content

@nativescript/vite: native dev drops user virtual CSS imports and serves their CSS raw #11551

Open

Description

@aleclarson

Native dev drops import 'virtual:bamboo.css', leaving generated component styles unloaded and the app layout broken.

There are two parts to the failure: the import disappears, and requesting the stylesheet directly returns raw CSS instead of a native CSS registration module.

Reproduction and observed behavior

With the app importing virtual:bamboo.css, compare these dev-server responses:

Request Response
/src/main.ts Includes import "/@id/__x00__virtual:bamboo.css";
/ns/m/src/main.ts Bamboo import is missing; ordinary file CSS imports remain
/ns/m/@id/__x00__virtual:bamboo.css HTTP 200 with raw CSS, including generated rules and @layer xplat.reset, xplat.base, xplat.tokens, xplat.utilities;

These were checked against the running server at http://127.0.0.1:5173.

The installed astNormalizeModuleImportsAndHelpers also reproduces the removal independently:

// Input
import "/@id/__x00__virtual:bamboo.css";
export const mounted = true;

// Output
/* [ast-normalized] */
export const mounted = true;

In ast-normalizer.ts, isViteVirtual matches null/__x00__ IDs except the OXC helper exemption, and the import visitor removes them. The same filtering is present on upstream main by source inspection.

Expected behavior

Preserve user virtual stylesheet imports and pass their CSS through native adaptation/registration. Preserving the import alone will not fix the raw-CSS response. Keep filtering specific to unsupported browser helpers.

A regression test should load a plugin-provided virtual stylesheet that sizes a native view, alongside ordinary file CSS and CSS HMR coverage.

Environment and limits

  • @nativescript/vite 8.0.17, with Octane Xplat's canonical dependency patch
  • Vite 8.3.0; @nativescript/core 9.1.3; Android runtime/CLI 9.1.1
  • Octane Xplat 0.15.0 (e3545c95); Bamboo 1.56.2; macOS; pnpm

Verified in Android HTTP dev and with the installed normalizer. A clean unpatched NativeScript app and iOS HTTP dev have not been tested. No workaround has been applied.

Supporting layout measurements

Android's last laid-out hierarchy had a roughly 698 dp wide bottom bar (declared maximum 353 dp) at the top, with SVGs around 233×82 dp (declared 31×36 and 54×54 dp).

These came from 72 native views via adb shell dumpsys activity <app-id>, at 520 dpi (3.25 px/dp). The activity was backgrounded/stopped, and component mapping was inferred from tree structure/source order. The HTTP responses and normalizer reproduction above are the primary evidence.

A bundled Debug build on iPhone 18 Pro / iOS 27 has the expected 31×36 and 54×54 pt icon sizes. This is a different platform and serving mode; it does not establish that the HTTP-dev failure is Android-specific. Measurements used native bounds without screenshots or text/input values.

Related: #11472 concerns ordinary workspace CSS and __vite__updateStyle; #11461 concerns core cascade layers. Here, the stylesheet fails to reach native registration first.

Activity

  1. aleclarson commented on Oct 19, 2026

    @aleclarson
    ContributorAuthor

    Confirmed with a minimal Vite plugin (no Bamboo compiler or application layout involved), using @nativescript/vite 8.0.17 and Vite 8.3.0. The 8.0.19 source and current main still have the broad isViteVirtual filter.

    There are additional stages to account for beyond the two in the report:

    • processCodeForDevice has a later /@id/ import sanitizer. Exempting stylesheet modules only in astNormalizeModuleImportsAndHelpers still drops them here. Both side-effect and default stylesheet imports need to survive, and rewriteImports must route them through /ns/m.
    • The generic /@id/ early return runs before the native CSS handler. Excluding stylesheets from that shortcut lets them reach native registration. Before calling transformRequest, unwrap /@id/__x00__... to its actual null-prefixed plugin ID. Passing the encoded URL directly to transformRequest(...?inline) can produce ERR_DENIED_ID/a transform miss: Vite's normal HTTP middleware ordinarily does that unwrapping.
    • Transforming only ?inline is insufficient for HMR. The canonical stylesheet must also have its transform/watch edges and self-accepting CSS boundary populated, since plugins reload that node. A plugin can invalidate just the canonical node, leaving an old inline sibling cached. NativeScript's extra transform-result TTL cache can also retain the previous sheet. Our temporary patch refreshes the inline sibling and uses Vite directly for virtual CSS compilation.
    • Vite's reloadModule emitted a js-update for the virtual .css module in this fixture. /ns-hmr currently discards stock update messages. We translate just virtual stylesheet updates into ns:css-updates, fetching adapted text through /ns/m/...?...direct=1 and using the original module URL as the tag. Raw null IDs and /@id/__x00__ paths need the same canonical tag.
    • Empty CSS needs to reach the existing remove/add tagged path to clear previous rules. Returning a harmless comment for an empty sheet avoids the client handler's empty-string skip. The link checker also needs to recognize that the native CSS route synthesizes a default string export.

    The focused regression starts real Vite HTTP serving with registerNsModuleServerRoute, preserves browser-helper filtering, executes the returned registration JavaScript (including the pending-registration fallback), checks ordinary file CSS, and tests virtual reloads while leaving the inline/result caches populated. It then feeds the resulting update into the actual client handleCssUpdates against a host API double to verify tagged replacement and clearing without touching app.css.

    With the framework's existing native CSS adapter enabled, the HTTP registration payload contains .fixed { width: 31dip; height: 36dip; }, with layers/platform blocks adapted. The dev adapter must run after Bamboo's load-time validation; build-time output validation/adaptation ordering remains separate.

    These are HTTP/host-execution findings, not device rendering evidence. No Android/iOS layout smoke is claimed. The temporary patch stays in Octane Xplat's existing dependency-patch distribution; no app-specific workaround is needed.

  2. aleclarson commented on Oct 19, 2026

    @aleclarson
    ContributorAuthor

    Consumer verification after upgrading Opals to octane-xplat v0.16.0 with its canonical NativeScript dependency patches: /ns/m/src/main.ts now preserves the Bamboo import, and /ns/m/@id/__x00__virtual:bamboo.css returns JavaScript calling __NS_REGISTER_CSS__ (or queuing pending CSS), with platform-filtered CSS and DIP units. This verifies the framework-carried mitigation against @nativescript/vite 8.0.17. The framework release is https://github.com/octane-xplat/octane-xplat/releases/tag/v0.16.0 .

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions