///|
/// Walk the `@import`/`@reference` graph of `css` and collect every transitively
/// imported stylesheet as a `path -> content` map.
///
/// The keys are the exact lexical paths `MemoryStylesheetLoader` uses for lookup
/// (`resolve_path(base, id)`), with the key chain seeded at `""`. The map is
/// therefore directly usable as the `imports` field of a compile request (e.g.
/// `compile_css_json` / the JS `compile({ input, imports })` wrapper) whenever the
/// entry `css` is compiled with the default `base = ""` — resolution replays the
/// same paths and finds every file.
///
/// Content is read through `loader`. `base~` is only the entry's on-disk resolution
/// base (the directory of the entry file) used for filesystem reads; it does not
/// affect the emitted keys. This lets a real filesystem loader resolve against actual
/// directories no matter where the entry lives, while the keys stay portable
/// (entry-relative, no absolute prefixes).
pub async fn collect_imports(
css : String,
loader : &StylesheetLoader,
base? : String = "",
) -> Map[String, String] raise CompileError {
let files : Map[String, String] = Map([])
collect_imports_from(parse_css(css), loader, "", base, [], files)
files
}
///|
/// Recursive worker for `collect_imports`.
///
/// Mirrors the loading behaviour of `resolve_imports` but accumulates source files
/// instead of building an AST. Two bases advance in lockstep, both driven by the
/// same `spec.id` sequence:
///
/// - `key_base` — lexical, seeds the map keys (`resolve_path(key_base, id)`).
/// - `fs_base` — passed to `loader.load` so the loader resolves real files.
async fn collect_imports_from(
nodes : ArrayView[CssNode],
loader : &StylesheetLoader,
key_base : String,
fs_base : String,
stack : Array[String],
files : Map[String, String],
) -> Unit raise CompileError {
for node in nodes {
match node {
AtRule(name~, params~, nodes=None, ..) =>
if is_import_at_rule(name) {
match parse_import_spec(params) {
None => ()
Some(spec) => {
let key = resolve_path(key_base, spec.id)
if stack.contains(key) {
let cycle = stack.copy()
cycle.push(key)
raise ImportCycle(cycle)
}
// Already collected (diamond import): its subtree is covered too.
if files.contains(key) {
continue
}
let loaded = loader.load(spec.id, fs_base)
files[key] = loaded.content
let next_stack = stack.copy()
next_stack.push(key)
collect_imports_from(
parse_css(loaded.content),
loader,
dirname(key),
loaded.base,
next_stack,
files,
)
}
}
}
Rule(nodes~, ..) | AtRule(nodes=Some(nodes), ..) =>
collect_imports_from(nodes, loader, key_base, fs_base, stack, files)
_ => ()
}
}
}