All notes
Tooling

?raw is empty under the test runner

A ?raw CSS import inlined fine in the app build but resolved to an empty string under the test runner, so tests exercised a rendering engine with no styles. Bundler-specific import mechanisms aren't guaranteed under your test runner — emit a real module the whole toolchain understands.

A new product in early scaffolding had a rendering engine that inlined its own CSS into an iframe. The stylesheet reached the runtime through a bundler ?raw import — the file’s text, imported as a string, injected into the iframe’s srcdoc. It worked in the app build. Under the test runner it resolved to an empty string, so every test drove the engine with no styles at all: green, and meaningless. The fix was to stop importing the asset and start generating it — the CSS emitted as a first-class TypeScript module the whole toolchain reads the same way.

A loader plugin is not a language feature

?raw looks like a language feature. It isn’t — it’s a bundler convention, resolved by a loader plugin that the app build happens to have configured and the test runner does not. The two toolchains agreed on the import syntax and disagreed on what it meant. Nothing errored, because there’s nothing wrong with importing an empty string. The same class of trap applies to import attributes, asset URLs, and any other mechanism whose meaning lives in the bundler config rather than the module graph.

Green-but-meaningless is worse than red

A failing test tells you where to look. A test that passes against a styleless engine tells you nothing while looking like coverage. The render path was fully exercised, the assertions ran, and the one thing that mattered — that the engine actually carried its styles — was silently absent. A test that can’t distinguish “styled” from “empty” is not testing the thing it was written to test.

Generate the module the whole toolchain understands

The resolution was to deliver the CSS as iframe-engine-css.generated.ts — a generated module exporting the stylesheet as a plain string constant, injected into the iframe exactly as before. App build and test runner now read an identical, ordinary import: no loader plugin in the path, no environment where it degrades. When an asset must be byte-identical in production and in test, don’t route it through a bundler affordance. Emit a real module and let every tool in the chain agree on what it is.