islands-build 0.1.3

Layout-agnostic build pipeline for islands.rs apps: WASM bundling, the V8 module-namespace patch, per-page CSS, and content-hash manifests. Composed by a thin xtask in any workspace.
Documentation
//! The inputs to the build pipeline.
//!
//! A [`BuildPlan`] fully describes a build with **no knowledge of any particular
//! repository layout** — the `static/` shape, the `@source` globs, the host
//! package, and the runtime package are all caller-supplied. A thin `xtask` (this
//! repo's, or a downstream consumer's) constructs the plan from discovery or an
//! `islands.toml` and hands it to [`crate::build`].

use std::path::PathBuf;

/// One page crate to compile to WASM and (optionally) generate CSS for.
#[derive(Debug, Clone)]
pub struct PageBuild {
    /// Cargo package name of the page crate, e.g. `"page-home"`.
    pub crate_name: String,
    /// Manifest key **and** `static/` subdirectory for this page's bundle, e.g.
    /// `"counter/page-home"`. The emitted JS/WASM/CSS land under
    /// `<out_dir>/<bundle_key>/` and the manifest is keyed by this string.
    pub bundle_key: String,
    /// `@source "...";` glob lines (paths relative to the generated per-page CSS
    /// input at `<assets_css_dir>/page-<short>.css`) the Tailwind scan reads for
    /// this page. Empty means the page contributes no page-specific utilities.
    pub css_sources: Vec<String>,
}

impl PageBuild {
    /// Short name: the crate name without the leading `page-` (e.g. `"home"`).
    /// Used for the generated per-page CSS input filename.
    pub fn short_name(&self) -> &str {
        self.crate_name
            .strip_prefix("page-")
            .unwrap_or(&self.crate_name)
    }

    /// Underscored crate name used for emitted filenames (e.g. `"page_home"`).
    /// wasm-bindgen and the Cargo artifact both use underscores.
    pub fn underscore_name(&self) -> String {
        self.crate_name.replace('-', "_")
    }
}

/// The shared runtime crate to compile into `islands_core.{js,_bg.wasm}`.
#[derive(Debug, Clone)]
pub struct RuntimeBuild {
    /// Cargo package name of the runtime crate. Default `"islands-runtime"`.
    pub package: String,
    /// Build the runtime with its `nav` feature. When `false`, the runtime is
    /// built `--no-default-features` for a leaner, navigation-free core.
    pub nav: bool,
}

impl Default for RuntimeBuild {
    fn default() -> Self {
        Self {
            package: "islands-runtime".to_owned(),
            nav: true,
        }
    }
}

/// CSS pipeline configuration. All paths are absolute (the caller joins the
/// workspace root). Omit it from a [`BuildPlan`] to skip CSS entirely.
#[derive(Debug, Clone)]
pub struct CssConfig {
    /// The checked-in base stylesheet (preflight + `@theme` tokens). Built once
    /// to `<out_dir>/css/base.css`.
    pub base_css: PathBuf,
    /// Directory holding `base.css`, the generated `page-<name>.css` inputs, and
    /// (when enabled) `basecoat.css`.
    pub assets_css_dir: PathBuf,
    /// The `@theme reference { ... }` block prepended to each per-page CSS input
    /// so utilities resolve without re-emitting the variable definitions.
    pub theme_reference: String,
    /// Materialize `basecoat.css` from the `basecoat-css` crate. Requires the
    /// `islands-build` crate's `basecoat` feature; otherwise this is an error.
    pub basecoat: bool,
}

/// A complete, layout-agnostic description of one build.
#[derive(Debug, Clone)]
pub struct BuildPlan {
    /// Workspace root. Used to locate `Cargo.lock` (for the wasm-bindgen version)
    /// and the `target/` directory, and as the working directory for `cargo`.
    pub workspace_root: PathBuf,
    /// Output root for emitted assets (typically `<workspace_root>/static`).
    pub out_dir: PathBuf,
    /// Compile WASM with `--release` (opt-level=z, fat LTO). Slower, smaller.
    pub release: bool,
    /// The shared runtime bundle to build.
    pub runtime: RuntimeBuild,
    /// The page crates to build.
    pub pages: Vec<PageBuild>,
    /// CSS pipeline config; `None` skips CSS.
    pub css: Option<CssConfig>,
    /// Run the content-hash pass and write `manifest.json`. When `false`, assets
    /// keep logical filenames and `page_shell`'s logical-path fallback resolves
    /// them (the dev story).
    pub manifest: bool,
}

impl BuildPlan {
    /// `target/wasm32-unknown-unknown/<profile>` directory for the active profile.
    pub(crate) fn wasm_artifact_dir(&self) -> PathBuf {
        let profile = if self.release { "release" } else { "debug" };
        self.workspace_root
            .join("target")
            .join("wasm32-unknown-unknown")
            .join(profile)
    }

    /// `<out_dir>/islands-core` — where the shared runtime bundle is emitted.
    pub(crate) fn runtime_out_dir(&self) -> PathBuf {
        self.out_dir.join("islands-core")
    }

    /// `<out_dir>/<bundle_key>` — where one page's bundle is emitted.
    pub(crate) fn page_out_dir(&self, page: &PageBuild) -> PathBuf {
        self.out_dir.join(&page.bundle_key)
    }
}