npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nabilabs/builder

v0.2.0

Published

Deterministic static HTML component builder

Readme

Nabi Builder

@nabilabs/builder is a deterministic static HTML builder for landing pages and multi-page websites. Components are resolved during the build, so the output is plain HTML, CSS, JavaScript, and assets with no client component runtime.

Create a project

bun create @nabilabs/builder my-site
cd my-site
bun run dev

This command runs the dedicated @nabilabs/create-builder package and creates a ready-to-use Nabi project.

Migrate from 0.1

Version 0.2 removes nabi init; create new projects with bun create @nabilabs/builder instead.

Global component refs must now be namespaced. Move each old component and update its ref:

src/shared/components/button/index.html -> src/ui/button/index.html
<use ref="button" />                    -> <use ref="ui/button" />

Page-local components now use @ directories and refs:

src/pages/jobs/components/card/index.html -> src/pages/jobs/@card/index.html
<use ref="card" />                       -> <use ref="@card" />

The build() result no longer includes routes; use pages. The dev.open option was removed, and sharedDir must remain inside srcDir.

Add Builder to an existing project

bun add -d @nabilabs/builder

Add the project commands to package.json:

{
  "scripts": {
    "build": "nabi build",
    "dev": "nabi dev"
  }
}

Commands

nabi build [--mode split|inline|body]
nabi dev [--port <port>]
nabi clean

The build command defaults to split mode. The dev server starts on port 2111 unless a port is configured or passed with --port.

Project structure

src/
  data/
    posts.json
  pages/
    index.html
    about/
      index.html
    blog/
      [post]/
        _route.json
        index.html
        404.html
      @card/
        index.html
  ui/
    button/
      index.html
      style.css
  modules/
    hero/
      index.html
  shared/
    assets/
      img/
        logo.svg
    js/
      site.js
    styles/
      base.css
nabi.config.js

Pages are stored in src/pages. Global components can live in any other top-level namespace under src, except shared and pages. Shared styles, scripts, and assets are opt-in resources.

Components

Use the HTML-only use element:

<use ref="ui/button" variant="primary" size="fill" href="/jobs">Open jobs</use>

Global component refs are namespaced. The source file src/ui/button/index.html is referenced as ui/button. A component can also be a file, for example src/modules/hero.html becomes modules/hero.

---
variant: primary | filled
size: hug | fill
---

<button class="button {{class}}" data-variant="{{variant}}" data-size="{{size}}" {{...props}}>
  <slot />
</button>

The frontmatter declares optional prop values for editor completion. The variants key declares values for variant; every other key declares values for a prop with the same name. The ref attribute is not emitted. Other attributes become props, and {{...props}} forwards attributes that were not consumed by a placeholder.

Components support default and named slots:

<use ref="ui/card" title="Pro">
  <p slot="header">Recommended</p>
  <p>Default content</p>
</use>
<article>
  <header>
    <slot name="header" />
  </header>
  <h2>{{title}}</h2>
  <main>
    <slot />
  </main>
</article>

Use self-closing syntax when no content is projected:

<use ref="ui/button" href="/jobs" />

Components whose root element is head contribute their contents to the page head.

Local components

A directory beginning with @ inside pages defines components local to that page subtree. The nearest matching component wins:

src/pages/
  @header/index.html              -> @header
  partners/
    @header/index.html            -> @header for partners pages
    index.html
<use ref="@header" />

Local components are private to their owner directory and its descendants. Use a global namespaced component when it must be available outside that subtree.

CSS and JavaScript resources

Page resources are colocated with a page:

pages/jobs/index.html
pages/jobs/style.css
pages/jobs/script.js

pages/about.html
pages/about.css
pages/about.js

Component resources follow the same convention:

ui/card/index.html
ui/card/style.css
ui/card/script.js

Only resources for components used by a page are emitted. Their order follows component resolution.

CSS Modules

Put module.css or a file ending in .module.css next to a page or component:

pages/jobs/index.html
pages/jobs/module.css
ui/card/index.html
ui/card/card.module.css

Class names declared in those files are automatically scoped and rewritten in the corresponding page or component HTML. Module CSS is emitted only for pages that use it.

Shared resources

Shared files are explicit dependencies rather than global injections:

<link use="base.css" />
<link use="core/normalize.css" media="screen" />

<script use="site.js" defer></script>
<script use="core/utm.js" type="module"></script>

They resolve relative to src/shared/styles and src/shared/js:

shared/styles/core/normalize.css
shared/js/core/utm.js

The build preserves other attributes and writes normal resource URLs in split mode. The use attribute cannot be combined with href on link or src on script.

Assets

Place binary assets in src/shared/assets and reference them with @assets:

<img src="@assets/img/logo.svg" alt="Nabi" />

Assets are copied to dist/assets. A CDN base URL is optional:

export default {
  assets: {
    baseUrl: "https://cdn.example.com/site-assets",
    mode: "copy",
  },
};

Static routes

Routes are generated from page files:

| Source file | URL | Output file | | ------------------------------ | -------------- | ----------------------------- | | pages/index.html | / | dist/index.html | | pages/about.html | /about | dist/about/index.html | | pages/jobs/index.html | /jobs | dist/jobs/index.html | | pages/jobs/students/index.html | /jobs/students | dist/jobs/students/index.html |

Both /about and /about/ resolve to the same page. A route collision stops the build and identifies every source that generated it.

Base route

Mount the project below a path:

export default {
  baseRoute: "/partner/jobs",
};

Root-relative internal links are rewritten automatically:

<nav>
  <a href="/">Home</a>
  <a href="/students">Students</a>
</nav>

With the configuration above, the generated URLs become /partner/jobs and /partner/jobs/students. Relative links such as ./students and ../ resolve from the current route. External URLs, hashes, mailto, tel, and CDN URLs are unchanged.

Dynamic routes

Use a bracketed directory or filename segment for a dynamic route. Every dynamic segment needs a JSON route file named _route.json by default.

src/
  data/
    cities.json
  pages/
    jobs/
      [city]/
        _route.json
        index.html
{
  "@data": "cities.json",
  "remote": {
    "name": "Remote"
  }
}
{
  "almaty": {
    "name": "Almaty",
    "seo": {
      "title": "Jobs in Almaty"
    }
  },
  "astana": {
    "name": "Astana"
  }
}

The route file above generates /jobs/almaty, /jobs/astana, and /jobs/remote. Local records supplement or override records loaded through @data. A route file can also be a simple array of slugs.

Use route data in an HTML page or component attribute with {{:segment.property}}:

<h1>{{:city.name}}</h1>
<meta property="og:title" content="{{:city.seo.title}}" />

Route data and component props

The colon selects a dynamic route value. Use {{:...}} in a page when passing route data to a component:

<use ref="ui/features" active="{{:page.features.combo}}"> Combo meals </use>

Inside the component, omit the colon. {{active}} is the component prop received from the use element:

<article data-active="{{active}}">
  <slot />
</article>

For nested dynamic segments, use @when to limit a record to parent values:

{
  "students": {
    "@when": {
      "city": ["almaty", "astana"]
    }
  }
}

An optional _route.js beside the JSON file may export a default function. It receives route and, when present, props. Return an object to merge more page data or null to omit that route.

export default ({ props, route }) => {
  if (!props?.enabled) return null;

  return {
    title: props.title ?? route.city,
  };
};

The Language Server provides completion, diagnostics, document links, and route-data suggestions for dynamic routes.

Conditional content

<if> keeps one branch at build time and removes the directive from the output. Its required when attribute must resolve to true or false; an empty or missing route value also selects the false branch. An optional <else> supplies that branch.

<if when="{{:page.features.combo}}">
  <article id="feature-combo">Combo meals</article>
  <else>
    <article id="feature-default">Available benefits</article>
  </else>
</if>

To use route data inside a component, pass it as a prop from the page:

<use ref="ui/features" active="{{:page.features.combo}}" />

src/ui/features/index.html:

<if when="{{active}}">
  <article>Combo meals</article>
</if>

The selected branch is compiled before its components and resources are resolved. The Language Server suggests route paths in when="{{:...}}" and reports paths that are not present in route data.

Error pages

Place a file named 404.html in pages or in a nested page directory:

pages/
  404.html
  jobs/
    404.html

The build emits error pages without registering them as ordinary routes. The dev server selects the closest applicable error page for a missing request. Dynamic error pages receive the same route data as the matching dynamic page.

Configure another file name when needed:

export default {
  errorPageFileName: "missing",
};

Build modes

Split is the default mode. It writes complete page HTML plus generated style.css and script.js files, copied shared dependencies, assets, and manifest.json.

nabi build

Inline embeds page, component, and declared shared styles and scripts in a complete HTML document.

nabi build --mode inline

Body emits a wrapper-free HTML fragment. It inlines styles, retains JSON script elements in the fragment, and writes executable scripts as separate files.

nabi build --mode body

Set defaultBuildMode to select a mode without passing --mode.

Development server

nabi dev --port 2111

The dev server uses the production routing and component pipeline, serves assets directly from source, and injects live reload. CSS changes swap stylesheets without a full reload. It tracks source dependencies, rebuilds active affected pages eagerly, and rebuilds inactive pages when requested. It remains available after an initial build error and recovers when the source is corrected.

When developing a linked Builder checkout, run bun run dev. It rebuilds dist after source changes and restarts the supervised Nabi dev worker.

Configuration

All configuration fields are optional:

export default {
  assets: {
    baseUrl: "",
    mode: "copy",
  },
  baseRoute: "",
  dataDir: "data",
  defaultBuildMode: "split",
  dev: {
    port: 2111,
  },
  errorPageFileName: "404",
  images: {
    optimize: false,
  },
  minify: {
    css: true,
    html: false,
    js: false,
  },
  outDir: "dist",
  pagesDir: "pages",
  routeFileName: "_route",
  sharedDir: "shared",
  srcDir: "src",
};

All configured directory paths must stay within the project. Source and output directories cannot overlap.

Language Server

@nabilabs/builder includes a standard Language Server Protocol server:

nabi-language-server --stdio

It provides completion, definitions, diagnostics, document links, component prop values, named slots, shared resources, and dynamic-route metadata. The Nabi VS Code extension finds the nearest local Builder package and starts its matching server.

Limits

Nabi intentionally has no client component runtime, hydration, JSX, template expressions, loops, implicit JavaScript execution in templates, SCSS or PostCSS pipeline, filename hashing, or automatic image optimization.