feat(unstable): CSS module imports (with { type: "css" })#35093
feat(unstable): CSS module imports (with { type: "css" })#35093bartlomieju wants to merge 8 commits into
Conversation
Implements a minimal prototype of CSS module scripts behind
--unstable-raw-imports. Importing a stylesheet with
'with { type: "css" }' (statically or dynamically) evaluates to a
CSSStyleSheet whose default export contains the source text.
The CSSStyleSheet implementation is intentionally minimal: it is backed
by the raw CSS text, supports replace()/replaceSync(), and cssRules
performs a naive top-level rule split (no real CSS parsing) so rules can
be serialized back out (e.g. for SSR). The global is only installed when
the raw-imports unstable feature is enabled.
Plumbing: the import attribute is validated in the runtime when raw
imports are enabled, deno_core's custom_module_evaluation_cb creates the
stylesheet for ModuleType::Other("css"), and the CLI loader treats the
requested type like a text asset. Since deno_graph only knows text/bytes
asset imports, the module analyzer remaps a 'css' attribute to 'text'
(gated on the flag) so graph building records an asset edge; a real
implementation should make asset attribute types pluggable in
deno_graph.
Replaces the module analyzer hack that remapped 'css' import attributes to 'text' with deno_graph's new unstable_css_imports option. deno_doc is temporarily pulled from a git branch that uses deno_graph 0.109 (denoland/deno_doc#817); the patch will be removed once a new deno_doc version is released.
deno_doc 0.200.0 is released with deno_graph 0.109, so the temporary [patch.crates-io] entry is no longer needed.
bartlomieju
left a comment
There was a problem hiding this comment.
Looks good to land — CI is green, scoped behind `--unstable-raw-imports`, and test coverage is thorough. Two minor non-blocking notes:
-
CSSRuleis declared as a runtime global inlib.deno.unstable.d.tsbut never installed at runtime.98_global_scope_shared.jsonly addsCSSStyleSheettounstableForWindowOrWorkerGlobalScope[rawImports], sox instanceof CSSRuletype-checks but throwsReferenceErrorat runtime. Browsers do exposeCSSRuleglobally. Either expose it too, or drop thedeclare var CSSRulevalue declaration and keep only the interface. The tests do not catch this because they only useCSSRuleas a type. -
replace()diverges from spec — it does not strip@importrules and resolves synchronously. Fine for a minimal prototype (and documented in the source comment), but worth a follow-up since the real API has observable@import-stripping behavior.
Neither is a reason to hold the PR; #1 is a one-liner you could fold in or chase later.
- Install CSSRule as a global alongside CSSStyleSheet under the raw-imports unstable scope; previously lib.deno.unstable.d.ts declared it as a runtime value but it was never exposed, so "x instanceof CSSRule" type-checked but threw ReferenceError at runtime. - Constructed style sheets disallow @import, so replace()/replaceSync() now drop top-level @import rules (matching the CSSOM spec) instead of storing them verbatim. The CSS module import path is unchanged and still preserves @import for SSR serialization.
|
One small WebIDL nit on The argument-conversion error path is handled correctly — a bad first argument However, a zero-argument call ( sheet.replace("ok"); // resolves ✅
sheet.replace(badArg); // rejects ✅ (correct)
sheet.replace(); // throws synchronously ❌ (spec wants a rejected promise)Minor and arguably fine for an unstable minimal implementation, but it'd be |
`replace` returns a promise, so per WebIDL a "not enough arguments" TypeError must be turned into a rejected promise rather than thrown synchronously. It previously used `#[required(1)]`, which throws synchronously before the op body runs, so `sheet.replace()` threw instead of rejecting. Drop `#[required(1)]` and check the argument count manually, rejecting with the same message `#[required(1)]` would have produced. `replaceSync` keeps `#[required(1)]` since it is a synchronous operation. Adds a spec test pinning the resolve, invalid argument, and missing argument paths.
Prototype of CSS module scripts behind
--unstable-raw-imports, towards#11961. Importing a stylesheet with
with { type: "css" }(statically ordynamically) evaluates to a
CSSStyleSheetcontaining the source text,matching what Chrome and Firefox ship. The main use case is running
unmodified browser module graphs in Deno (SSR and testing of web
components), where a CSS import currently kills module loading before any
code runs.
The
CSSStyleSheetimplementation is intentionally minimal: it is backedby the raw CSS text, supports
replace()/replaceSync(), andcssRulesperforms a naive top-level rule split (no real CSS parsing) so rules can
be serialized back out for SSR. The global is only installed when the
raw-imports unstable feature is enabled. On the loader side the requested
module type rides the same asset path as text/bytes imports, and deno_core's
custom_module_evaluation_cbcreates the stylesheet. Graph building recordsthe
cssasset edge directly now that deno_graph acceptscssassetattributes (gated on the unstable flag via
unstable_css_imports); thisbumps
deno_graphto 0.109.0 anddeno_docto 0.200.0.