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

@beforesemicolon/builder

v1.8.25

Published

Utilities to build npm packages and documentation website

Readme

@beforesemicolon/builder

Utilities to build npm packages and static documentation websites for Before Semicolon projects.

This package provides three public helpers:

  • buildModules() builds TypeScript sources into dist/esm and dist/cjs.
  • buildBrowser() builds a browser bundle, usually for demos or docs.
  • buildDocs() renders a static Markdown documentation site.

The docs builder supports reusable templates, Markdown layout blocks, source-level template extension, generated SEO/AI files, theme variables, page scripts, assets, stylesheets, and custom marked options.

Requirements

  • Node.js >=18.16.0
  • ESM projects are supported directly.
  • CommonJS consumers can use the package require export.

Installation

npm install --save-dev @beforesemicolon/builder

Quick Start

import { buildModules, buildBrowser, buildDocs } from '@beforesemicolon/builder'

await buildModules()
await buildBrowser()
await buildDocs()

Common project script:

import { buildModules, buildBrowser, buildDocs } from '@beforesemicolon/builder'

const docsOptions = {
    template: 'fading-citrus',
    siteUrl: 'https://example.com',
    generatedFiles: {
        netlify: true,
    },
}

const run = async () => {
    await Promise.all([buildModules(), buildBrowser(), buildDocs(docsOptions)])
}

run()

API

buildModules(options?)

buildModules(options?: {
    directoryPath?: string
}): Promise<void>

Builds source files into server-friendly ESM and CommonJS output.

Defaults:

  • directoryPath: process.cwd()/src
  • ESM output: dist/esm
  • CommonJS output: dist/cjs

Behavior:

  • Recursively scans the source directory.
  • Skips files ending in .spec.ts.
  • Skips /client.ts from module builds.
  • Uses esbuild.
  • Minifies output.
  • Keeps symbol names for better stack traces.

buildBrowser(options?)

buildBrowser(options?: {
    entry?: string
    out?: string
}): Promise<void>

Builds a single browser bundle.

Defaults:

  • entry: src/client
  • out: dist/client.js

Behavior:

  • Uses esbuild.
  • Generates sourcemaps.
  • Minifies output.
  • Includes a small internal plugin that removes the Doc export from @beforesemicolon/html-parser when bundling.

buildDocs(options?)

buildDocs(options?: {
    srcDir?: string
    publicDir?: string
    markedOptions?: MarkedExtension
    template?: string
    siteUrl?: string
    generatedFiles?:
        | boolean
        | {
              sitemap?: boolean
              robots?: boolean
              llms?: boolean
              llmsFull?: boolean
              netlify?: boolean
          }
}): Promise<void>

Builds a static documentation site from Markdown.

Defaults:

  • srcDir: process.cwd()/docs
  • publicDir: process.cwd()/website
  • template: no named template, uses the built-in default layout
  • generatedFiles: enabled for sitemap, robots, llms, and llmsFull
  • generatedFiles.netlify: false

Example:

await buildDocs({
    template: 'fading-citrus',
    siteUrl: 'https://docs.example.com',
    generatedFiles: {
        netlify: true,
    },
})

Docs Directory Structure

The default source directory is docs/.

docs/
  index.md
  guide/
    getting-started.md
  assets/
  stylesheets/
  scripts/
  _layouts/
  _template/
    template.config.js
    assets/
    stylesheets/
    scripts/
    layouts/
  robots.txt
  sitemap.xml
  llms.txt
  llms-full.txt
  _redirects
  netlify.toml

Supported folders:

  • assets/: copied to the same relative location in the output directory.
  • stylesheets/: CSS files are minified and copied to output.
  • scripts/: JS files are minified and copied to output.
  • _layouts/: page layout modules. Each file default-exports a page layout function.
  • _template/: source-level extension for the selected template.
  • _template/assets/: copied into publicDir/assets, overriding or extending template assets.
  • _template/stylesheets/: copied into publicDir/stylesheets, overriding or extending template styles.
  • _template/scripts/: copied into publicDir/scripts, overriding or extending template scripts.
  • _template/layouts/: custom page layouts that can override or extend selected template layouts.
  • _template/template.config.js: source-level template config merged with the selected template config.

Files and folders starting with . or _ are skipped during Markdown page discovery. _template and _layouts are used explicitly by the docs builder.

Page Front Matter

Each Markdown page can include front matter:

---
name: Get Started
title: Get Started with Example
description: Learn how to install and use Example.
order: 1
layout: document
---

# Get Started

Common fields:

  • name: label used in the generated site map.
  • title: HTML title and generated metadata title.
  • description: meta description and generated metadata description.
  • order: numeric sort order for site map and generated files.
  • layout: page layout name. Defaults to default.

The final page props include:

interface PageProps {
    name?: string
    path?: string
    order?: number
    title?: string
    description?: string
    content?: string
    siteMap?: SiteMap
    tableOfContent?: Array<{
        path: string
        label: string
        level: string
    }>
    projectMeta?: {
        name: string
        version: string
        [key: string]: unknown
    }
    renderMarkdown?: (markdown: string) => string
    scripts?: string[]
    themeStylesheet?: string
}

Page Layouts

Page layouts render complete HTML documents. A layout file must default-export a function that receives PageProps and returns an HTML string.

Example docs/_layouts/document.js:

export default ({
    title,
    description,
    content,
    scripts = [],
}) => `<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="description" content="${description || ''}">
        <title>${title || ''}</title>
    </head>
    <body>
        ${content || ''}
        ${scripts.join('')}
    </body>
</html>`

Layout lookup order:

  1. Built-in layouts.
  2. Selected template layouts.
  3. docs/_layouts.
  4. docs/_template/layouts.

Later layout files with the same basename override earlier ones.

Templates

Named templates are loaded from:

src/docs/templates/<template-name>/

Available templates:

  • fading-citrus: a complete landing and documentation template with Markdown layout handlers, theme variables, assets, and page scripts. See fading-citrus template README.

A template can provide:

template.config.js
assets/
stylesheets/
scripts/
layouts/

The selected template is a complete out-of-the-box docs site shell. A docs source can extend it through docs/_template. Template-specific layouts, assets, options, and assumptions should be documented by each template.

Template Config

A template config exports an object:

export default {
    meta: {},
    site: {},
    markedOptions: {},
    markdownLayouts: {},
    headScripts: {},
    scripts: {},
    theme: {
        light: {},
        dark: {},
    },
}

Config from docs/_template/template.config.js is merged into the selected template config.

Merge behavior:

  • markdownLayouts are shallow-merged by layout name.
  • headScripts and scripts are shallow-merged by script name.
  • meta is shallow-merged by metadata field.
  • site is shallow-merged by site field.
  • theme.light and theme.dark are shallow-merged by CSS variable name.
  • Other top-level config values use the docs source config value when provided.

Example docs source extension:

import pricingCards from './layouts/pricing-cards.js'

export default {
    meta: {
        siteName: 'Example',
        title: 'Example Docs',
        description: 'Documentation for Example.',
        image: '/assets/site-image.jpg',
    },
    site: {
        name: 'Example',
        packageName: '@example/docs',
        repositoryUrl: 'https://github.com/example/docs',
        repositoryLabel: 'Example GitHub repository',
        docsEditUrl: 'https://github.com/example/docs/tree/main/docs',
        footerDescription: 'Documentation for Example.',
        footerGroups: [
            {
                title: 'Learning Resources',
                links: [{ label: 'Documentation', href: '/documentation' }],
            },
        ],
    },
    markdownLayouts: {
        'pricing-cards': pricingCards,
    },
    theme: {
        light: {
            '--primary': 'oklch(0.62 0.18 250)',
        },
        dark: {
            '--primary': 'oklch(0.76 0.16 250)',
        },
    },
}

Common site fields:

  • name: display name used by shared layouts.
  • packageName: package name used by templates that render install commands.
  • repositoryUrl and repositoryLabel: repository link and accessible label.
  • docsEditUrl: base URL for edit links, usually a repository docs folder URL.
  • navLinks and actionLinks: landing header links.
  • footerDescription, footerGroups, socialLinks, and copyright: footer content.
  • landingHeroVersionHref and landingHeroSecondaryLabel: optional landing hero overrides.

Markdown Layout Syntax

The docs renderer extends marked with a custom block syntax:

::: layout <type> [options]

=== <name> [options]

Markdown content for this part.

=== <name> [options]

More Markdown content.

:::

Example:

::: layout grid columns=3 gap=lg

=== card span=2

## First card

Markdown content.

=== card sticky

## Second card

More Markdown content.

===

Unnamed item.

:::

Header parsing:

::: layout grid columns=3 gap=lg

Produces:

{
    type: 'grid',
    options: {
        columns: 3,
        gap: 'lg',
    },
}

Item header parsing:

=== hero span=2 sticky

Produces:

{
    name: 'hero',
    options: {
        span: 2,
        sticky: true,
    },
}

Unnamed items are supported:

===

Content

Produces:

{
    name: null,
    options: {},
}

Option parsing rules:

  • key=value becomes a keyed option.
  • Bare words become boolean true.
  • Numeric values become numbers.
  • true and false become booleans.
  • Quoted values are supported.

Examples:

columns=3
gap=lg
sticky
label="Get Started"
enabled=false

Nested layout blocks are supported. Nested blocks are preserved inside the parent item body and rendered through the same Markdown renderer.

Markdown Layout Handlers

Markdown layout handlers are registered through template.config.js:

import pricingCards from './layouts/pricing-cards.js'

export default {
    markdownLayouts: {
        'pricing-cards': pricingCards,
    },
}

A handler receives parsed layout data and a rendering context:

type MarkdownLayoutHandler = (
    layout: {
        type: string
        options: Record<string, string | number | boolean>
        parts: Array<{
            name: string | null
            options: Record<string, string | number | boolean>
            body: string
            html: string
        }>
        raw: string
    },
    context: {
        renderMarkdown(markdown: string): string
        renderDefault(node): string
        renderParts(node): Array<{ html: string }>
    }
) => string

Each part body is rendered from Markdown to HTML before the handler receives it. Use part.html when injecting content.

Example handler:

export default ({ parts, options }) => {
    const tierClass = options.featured ? ' pricing-cards-featured' : ''

    return `<div class="pricing-cards${tierClass}">
        ${parts
            .map(
                (
                    part,
                    index
                ) => `<section class="pricing-card option-${index + 1}">
                    ${part.html}
                </section>`
            )
            .join('')}
    </div>`
}

Markdown:

::: layout pricing-cards featured

===

## Starter

$10/month

===

## Pro

$30/month

:::

Generated HTML is entirely controlled by the handler.

Default Markdown Layout Rendering

If a layout type has no custom handler, builder renders a generic structure:

<div
    class="bfs-layout bfs-layout-grid"
    data-layout="grid"
    style="--columns: 3; --gap: lg;"
>
    <section class="bfs-layout-item" data-name="card" style="--span: 2;">
        ...
    </section>
</div>

Boolean options are omitted from inline styles. Non-boolean options are converted to CSS custom properties.

marked Options

The docs builder uses marked, marked-highlight, and a custom renderer for headings, code, and links.

You can extend marked globally for docs generation:

await buildDocs({
    markedOptions: {
        renderer: {
            codespan({ text }) {
                return `<code data-inline>${text}</code>`
            },
        },
    },
})

Templates can also provide markedOptions through template.config.js.

Page Scripts

Template scripts are declared in template.config.js.

import { renderCodeCopyScript } from './layouts/_code-snippet.js'

export default {
    scripts: {
        'code-copy': {
            match: 'code-copy-btn',
            render: renderCodeCopyScript,
        },
    },
}

Use headScripts for scripts that must render in <head>, such as analytics bootstrap tags. Use scripts for scripts that can render near </body>, such as interaction handlers.

Script definitions:

type DocsScriptMatcher =
    | string
    | string[]
    | RegExp
    | ((html: string) => boolean)

interface DocsScriptDefinition {
    match?: DocsScriptMatcher
    render: () => string
}

type DocsScriptRegistry = Record<
    string,
    false | DocsScriptDefinition | (() => string)
>

Behavior:

  • Scripts are rendered per page after Markdown has been rendered.
  • If match is omitted, the script is included on every page.
  • A string matcher checks html.includes(match).
  • An array matcher checks whether any string is present.
  • A RegExp matcher tests the rendered page HTML.
  • A function matcher receives the rendered page HTML and returns a boolean.
  • A script can be disabled by setting its registry value to false in an extending config.

Layouts receive scripts through props.headScripts and props.scripts. Template layouts must insert them where appropriate.

export default (props) => `
<!doctype html>
<html>
    <head>
        ${props.headScripts?.join('') || ''}
    </head>
    <body>
        ${props.content}
        ${props.scripts?.join('') || ''}
    </body>
</html>`

Theme Variables

Templates can define theme variables in template.config.js:

export default {
    theme: {
        light: {
            '--background': 'oklch(0.98 0.006 250)',
            '--foreground': 'oklch(0.18 0.015 250)',
            '--primary': 'oklch(0.66 0.18 45)',
        },
        dark: {
            '--background': 'oklch(0.18 0.015 250)',
            '--foreground': 'oklch(0.96 0.005 250)',
            '--primary': 'oklch(0.74 0.18 45)',
        },
    },
}

Builder converts theme variables into:

website/stylesheets/theme.css

The generated file includes:

  • :root variables.
  • @media (prefers-color-scheme: dark) variables.
  • [data-theme="light"] variables.
  • [data-theme="dark"] variables.

A theme mode can be disabled with false:

export default {
    theme: {
        light: false,
    },
}

When one mode is disabled, the remaining mode is emitted as :root. For example, light: false makes the dark theme the default theme and skips light-mode selectors and prefers-color-scheme switching.

Page layouts receive:

themeStylesheet?: string

Templates should include it in <head>:

${props.themeStylesheet ? `<link rel="stylesheet" href="${props.themeStylesheet}">` : ''}

Docs sources can override only variable values by adding docs/_template/template.config.js.

Generated Files

buildDocs() can generate common root-level files into publicDir.

Defaults:

generatedFiles: {
    sitemap: true,
    robots: true,
    llms: true,
    llmsFull: true,
    netlify: false,
}

Disable all generated files:

await buildDocs({
    generatedFiles: false,
})

Enable Netlify files:

await buildDocs({
    siteUrl: 'https://docs.example.com',
    generatedFiles: {
        netlify: true,
    },
})

Generated files:

  • sitemap.xml: generated from discovered Markdown pages. Requires siteUrl.
  • robots.txt: generated with Allow: / and a sitemap URL when siteUrl is provided.
  • llms.txt: generated page index for AI tools.
  • llms-full.txt: generated expanded page index with source paths, descriptions, and summaries.
  • _redirects: generated only when generatedFiles.netlify is true.
  • netlify.toml: generated only when generatedFiles.netlify is true.

Source-first behavior:

  • If docs/sitemap.xml exists, it is copied to publicDir/sitemap.xml instead of generated.
  • If docs/robots.txt exists, it is copied to publicDir/robots.txt instead of generated.
  • If docs/llms.txt exists, it is copied to publicDir/llms.txt instead of generated.
  • If docs/llms-full.txt exists, it is copied to publicDir/llms-full.txt instead of generated.
  • If generatedFiles.netlify is true and docs/_redirects exists, it is copied to publicDir/_redirects instead of generated.
  • If generatedFiles.netlify is true and docs/netlify.toml exists, it is copied to publicDir/netlify.toml instead of generated.

Netlify notes:

  • _redirects is treated as Netlify-specific.
  • netlify.toml is written to publicDir, not the project root.
  • Generated netlify.toml defaults to command = "node build-docs.js" and publish = "website" unless publicDir has a different basename.

llms-full.txt Content

The generated llms-full.txt is derived from discovered Markdown pages.

For each page it uses:

  • title: front matter title, fallback to name, fallback to Documentation.
  • description: front matter description, fallback to stripped Markdown body text.
  • URL: file-derived page URL joined with siteUrl.
  • Source: relative Markdown source path.
  • summary: first 320 characters of stripped Markdown body text.

The body summary is not the final rendered HTML. It is a lightweight Markdown text extraction used for AI-facing page discovery.

Extension Example

Project docs:

docs/
  index.md
  _template/
    template.config.js
    assets/
      logo.svg
    layouts/
      pricing-cards.js

docs/_template/template.config.js:

import pricingCards from './layouts/pricing-cards.js'

export default {
    markdownLayouts: {
        'pricing-cards': pricingCards,
    },
    headScripts: {
        analytics: () =>
            `<script async src="https://example.com/analytics.js"></script>`,
    },
    scripts: {
        pageView: {
            match: '<main',
            render: () => `<script>console.log('page viewed')</script>`,
        },
    },
    theme: {
        light: {
            '--primary': 'oklch(0.62 0.18 250)',
        },
        dark: {
            '--primary': 'oklch(0.78 0.16 250)',
        },
    },
}

docs/index.md:

---
title: Example
description: Example documentation.
layout: landing
---

::: layout pricing-cards featured

===

## Starter

For small teams.

===

## Pro

For growing teams.

:::

Build Output

Given default options, output is written to:

website/
  index.html
  guide/
    getting-started.html
  assets/
  stylesheets/
  scripts/
  robots.txt
  sitemap.xml
  llms.txt
  llms-full.txt

If generatedFiles.netlify is true, output also includes:

website/
  _redirects
  netlify.toml

Package Scripts

This repo provides:

  • npm run build: removes dist, emits TypeScript declarations, then builds package outputs.
  • npm run lint: runs ESLint and Prettier checks.
  • npm run format: runs ESLint autofix and Prettier write.
  • npm test: runs the Markdown layout parser/renderer tests.

Development

Install dependencies:

npm install

Run checks:

npm run lint
npm test
npm run build

Pack locally:

npm pack

Install the packed artifact into a sibling project:

npm install ../builder/beforesemicolon-builder-<version>.tgz

Implementation Notes

  • Markdown rendering uses marked.
  • Syntax highlighting uses marked-highlight and highlight.js.
  • Front matter parsing uses front-matter.
  • HTML is sanitized with isomorphic-dompurify.
  • HTML output is minified with html-minifier.
  • CSS output is minified with clean-css.
  • JS output copied from docs script folders is minified with @putout/minify.
  • Static module and browser builds use esbuild.

License

BSD-3-Clause. See package.json.