Nanoss Architecture

Build pipeline

  1. Scan content_dir for markdown and assets.
  2. For markdown files:
    • Use nanoss-query (Salsa) to compute a content hash.
    • Skip rendering when hash and output path match the build cache.
    • Execute plugin hooks:
      • transform_markdown
      • on_page_ir
      • on_post_render
  1. For assets:
    • Sass -> CSS with grass, then optimize with LightningCSS.
    • CSS optimize with LightningCSS.
    • JS/TS via backend abstraction (passthrough or esbuild).
  1. Optional post steps:
    • External link checking.
    • Semantic index generation.
    • Content organization outputs (posts pagination, tags, categories).
    • SEO outputs (sitemap.xml, rss.xml).
  2. Persist build cache to public/.nanoss-cache.json (including image metadata and variants).

Key crates

Product infrastructure

Runtime outputs

Current dependency map

flowchart LR
  cli[nanoss-cli] --> core[nanoss-core]
  core --> query[nanoss-query]
  core --> host[nanoss-plugin-host]
  host --> api[nanoss-plugin-api]

Current runtime call sequence

sequenceDiagram
  participant user as CLI
  participant core as BuildCore
  participant plugin as PluginHost
  participant io as FS/Process/HTTP
  user->>core: run_build_with_scope()
  core->>plugin: init(config-json)
  core->>io: scan/read content
  core->>plugin: transform_markdown()
  core->>plugin: on_page_ir()
  core->>plugin: on_post_render()
  core->>io: write page/assets/cache
  core->>plugin: shutdown()

Target decoupled architecture

flowchart LR
  cli[nanoss-cli] --> app[BuildApplication]
  app --> ports[Ports]
  ports --> fs[FileSystemPort]
  ports --> http[HttpPort]
  ports --> proc[ProcessPort]
  app --> boundary[PluginBoundary]
  boundary --> host[PluginHost]
  host --> wit[PluginWIT]
  app --> query[nanoss-query]

Islands Minimal Example

Write a island node in markdown:

<island name="counter" props='{"start": 3}'></island>

Register the corresponding handler in the template or page script:

<script type="module">
  window.NanossIslands.register("counter", (node, props) => {
    let count = Number(props.start ?? 0);
    const button = document.createElement("button");
    button.textContent = `Count: ${count}`;
    button.addEventListener("click", () => {
      count += 1;
      button.textContent = `Count: ${count}`;
    });
    node.replaceChildren(button);
  });
</script>

Illustrate: