@lark.js/docs
v0.0.7
Published
Documentation site generator for @lark.js/mvc
Downloads
1,363
Maintainers
Readme
@lark.js/docs
Documentation site generator for @lark.js/mvc.
If @lark.js/mvc is to React or Vue, then @lark.js/docs is to Docusaurus or VitePress -- providing an out-of-the-box documentation site experience built on top of the Lark MVC framework.
Features
- File-based routing: recursively scans a
docs/directory and generates SPA routes - Dual routing modes: supports
@lark.js/mvcRouter in bothhistoryandhashmodes - Markdown compilation pipeline:
markdown-itwith four custom plugins (anchors, TOC, containers, code blocks) - YAML frontmatter: metadata extraction via
js-yamlfor page titles, descriptions, sidebar positioning, and draft control - Code syntax highlighting: Shiki-powered highlighting with lazy WASM initialization and singleton caching
- Admonition containers:
::: tip,::: warning,::: danger,::: detailsrendered as DaisyUI alert components - Auto-generated sidebar: directory-structure-based navigation with
sidebarPositionandsidebarLabelfrontmatter overrides - Three search providers: MiniSearch-powered local modal (same engine as VitePress), Algolia DocSearch UI with local index (no account required), or disabled
- Table of contents: per-page heading outline with smooth-scroll navigation
- Three-column responsive layout: Tailwind CSS v4 + DaisyUI v5 with sticky navbar, frosted glass effect, and mobile-responsive sidebars
- Three bundler integrations: Vite, Webpack, and Rspack / Rsbuild
- Zero-config boot:
defineConfig()auto-generates routes, sidebars, and search index into.lark-docs/generated/ - Single-call theme registration:
registerThemeViews()registers all theme components at once - Dual-format library build: ships ESM + CJS with full TypeScript declarations
Architecture
@lark.js/docs operates in three phases:
Phase 1 -- Configuration (build startup). defineConfig() scans the docs directory, extracts frontmatter and headings from every .md file, auto-generates sidebar trees per path prefix, and writes a generated module to .lark-docs/generated/index.js. This module provides dynamic content loaders, a route map, runtime site configuration, and a lazy search index builder.
Phase 2 -- Compilation (bundler plugin). Each .md import is intercepted by the bundler plugin (larkDocsPlugin for Vite, LarkDocsPlugin for Webpack/Rspack) and compiled through compileMarkdown(). The pipeline extracts YAML frontmatter, initializes the Shiki highlighter on first call (async singleton), parses the markdown body with markdown-it plus four custom plugins, renders to HTML, builds page metadata, and emits a JS module that exports pageData and contentHtml.
Phase 3 -- Runtime (browser). The @lark.js/mvc Framework boots with the generated routes. The layout view stays mounted across navigation and asynchronously loads page content via loadContent(). Four theme Views (layout, sidebar, TOC, search) render the documentation UI. Search is lazily initialized on first query.
lark-docs.config.ts Bundler Plugin Browser Runtime
| | |
defineConfig() compileMarkdown() Framework.boot()
| | |
scanDocsDir() extractFrontmatter registerThemeViews()
generateSidebar() createParser() routes + loadContent
| getHighlighter() from generated module
| | |
.lark-docs/generated/ JS module string 4 theme Views
index.js ({pageData, render the docs UI
contentHtml})Quick Start
1. Install
pnpm add @lark.js/docs @lark.js/mvc tailwindcss daisyui @tailwindcss/typographyThe theme templates use Tailwind CSS utility classes, DaisyUI components, and the Typography plugin for prose styling. All are peer dependencies -- your project must have them installed and configured in your CSS entry:
@import "tailwindcss";
@plugin "daisyui";
@plugin "@tailwindcss/typography";2. Configure
Create lark-docs.config.ts:
import { defineConfig } from "@lark.js/docs/vite";
export default defineConfig({
docs: "docs",
baseUrl: "/docs/",
routeMode: "history",
title: "My Library",
description: "Documentation for My Library",
nav: [
{ text: "Guide", link: "/docs/guide/" },
{ text: "API", link: "/docs/api/" },
],
sidebar: {
"/docs/guide/": "auto",
"/docs/api/": "auto",
},
highlight: {
theme: "github-dark",
languages: ["typescript", "javascript", "html", "css", "bash", "json"],
},
search: { provider: "local" },
});defineConfig() is an identity function that also triggers route generation. It scans the docs directory, generates sidebar trees, and writes the generated module -- all at configuration load time.
3. Configure Your Bundler
Vite:
import { defineConfig } from "vite";
import { larkDocsPlugin } from "@lark.js/docs/vite";
import { larkMvcPlugin7 } from "@lark.js/mvc/vite";
import tailwindcss from "@tailwindcss/vite";
import docsConfig from "./lark-docs.config";
import { resolve } from "node:path";
const PKG_DIR = import.meta.dirname;
export default defineConfig({
root: resolve(PKG_DIR, "app"),
plugins: [
larkDocsPlugin({ config: docsConfig }),
larkMvcPlugin7({ debug: true, vdom: true }),
tailwindcss(),
],
resolve: {
alias: {
"@lark-docs/generated": resolve(PKG_DIR, ".lark-docs/generated"),
},
},
});Webpack:
import { LarkDocsPlugin } from "@lark.js/docs/webpack";
import docsConfig from "./lark-docs.config";
export default {
plugins: [new LarkDocsPlugin({ config: docsConfig })],
};Rspack:
import { LarkDocsPlugin } from "@lark.js/docs/rspack";
import docsConfig from "./lark-docs.config";
export default {
plugins: [new LarkDocsPlugin({ config: docsConfig })],
};4. Boot
Create app/boot.ts:
import { Framework, State } from ".js/mvc";
import type { FrameworkConfig } from "@lark.js/mvc";
// Auto-generated by defineConfig()
import {
routes,
docsConfig,
loadContent,
getSearchIndex,
} from "@lark-docs/generated";
// Theme views (layout, sidebar, toc, search) -- registered in one call.
// The framework imports and compiles the .html templates internally,
// so consumers don't need to import .html files or call registerViewClass
// for each theme component.
import { registerThemeViews } from "@lark.js/docs/theme";
import "./main.css";
// === Register theme views ===
registerThemeViews();
// === Inject site data + content loader into State ===
State.set({ docsConfig, loadContent, getSearchIndex });
// === Boot ===
const config: FrameworkConfig = {
rootId: "app",
routeMode: "history",
defaultPath: "/docs/",
// All /docs/* routes map to "theme/docs-layout" (see generated routes).
// The layout stays mounted across navigation; observeLocation triggers
// an async render that loads the matching .md content via loadContent.
defaultView: "theme/docs-layout",
routes,
unmatchedView: "theme/docs-layout",
};
Framework.boot(config);5. TypeScript Setup
Create shims.d.ts in your project root:
/// <reference types="@lark.js/docs/client" />
/// <reference types="vite/client" />The /// <reference types="@lark.js/docs/client" /> directive loads ambient module declarations for @lark-docs/generated (routes, docsConfig, loadContent, getSearchIndex, SearchEntry) and *.html template imports.
Add a paths mapping in tsconfig.json to help the IDE resolve the generated module:
{
"compilerOptions": {
"paths": {
"@lark-docs/generated/*": ["./.lark-docs/generated/*"]
}
}
}Important: Use
/// <reference types="..." />(not/// <reference path="..." />) for referencing type declarations insidenode_modules. Thetypesdirective uses TypeScript's full module resolution algorithm, which correctly resolves pnpm workspace symlinks and packageexportsfields. Thepathdirective performs raw filesystem path lookup and does not understand package structure or symlink resolution.
6. Write Markdown
---
title: Getting Started
description: Learn how to use the framework
sidebar_position: 1
---
# Getting Started
Welcome to the documentation.
## Installation
Install via npm:
```bash
pnpm add @lark.js/mvc
```
::: tip
Always call `registerThemeViews` before `Framework.boot()`.
:::Configuration Reference
The DocsConfig interface defines all configuration options:
| Field | Type | Default | Description |
| ------------- | ------------------------------- | ----------------------- | ----------------------------------------------- |
| docs | string | "docs" | Docs source directory, relative to project root |
| baseUrl | string | "/docs/" | Base URL prefix for all generated routes |
| routeMode | "history" \| "hash" | "history" | Routing mode, maps to @lark.js/mvc Router |
| title | string | (required) | Site title displayed in the navbar |
| description | string | "" | Site description for meta tags |
| lang | string | "en-US" | Language code |
| nav | NavItem[] | [] | Top navigation items |
| sidebar | Record<string, SidebarConfig> | {} | Sidebar config per path prefix |
| markdown | MarkdownOptions | {} | Markdown processing options |
| highlight | HighlightOptions | undefined | Shiki code highlighting options |
| search | SearchOptions | { provider: "local" } | Search provider configuration |
NavItem
interface NavItem {
text: string; // Display text
link: string; // Link URL (internal or external)
items?: NavItem[]; // Nested dropdown items
}Sidebar Configuration
Each sidebar prefix maps to either "auto" (filesystem-based generation) or an explicit SidebarItem[] array.
sidebar: {
"/docs/guide/": "auto", // auto-generate from directory structure
"/docs/api/": [ // explicit items
{ text: "Overview", link: "/docs/api/" },
{ text: "Classes", link: "/docs/api/classes" },
],
}Auto-generated sidebars group routes by subdirectory, sort by sidebarPosition frontmatter (then alphabetically), and use sidebarLabel frontmatter for display text when provided.
MarkdownOptions
| Field | Type | Default | Description |
| ------------------ | ----------------------------------- | ---------- | ------------------------------ |
| anchor.permalink | boolean | true | Add permalink anchors to h1-h3 |
| containers | Record<string, { label: string }> | (built-in) | Custom container labels |
HighlightOptions
| Field | Type | Default | Description |
| ----------- | ---------- | ------------------ | ----------------- |
| theme | string | "github-dark" | Shiki theme name |
| languages | string[] | (common web langs) | Languages to load |
When highlight is configured, the Shiki highlighter is initialized as a lazy singleton on the first .md compilation. The WASM and TextMate grammars are loaded once and cached for all subsequent files. Languages not in the loaded list fall back to the "text" grammar.
SearchOptions
| Provider | Description |
| ------------- | ------------------------------------------------------------------------------------------ |
| "local" | Built-in search modal with substring matching and weighted scoring |
| "docsearch" | Algolia DocSearch UI widget backed by the local search index (no Algolia account required) |
| "none" | Disable search entirely |
Frontmatter
Each .md file can include YAML frontmatter delimited by ---:
---
title: Page Title
description: Page description for SEO and search
sidebar_position: 1
sidebar_label: Custom Label
draft: false
---| Field | Type | Description |
| ------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| title | string | Page title. Falls back to first # heading, then filename-derived title |
| description | string | Page description for meta tags and search index. Falls back to filename-derived title |
| sidebar_position | number | Sort order in auto-generated sidebar (lower = higher). Uses all-or-nothing rule: if any page in a group lacks this field, all pages sort by filename only |
| sidebar_label | string | Override sidebar display text |
| draft | boolean | When true, excluded from production builds via excludeDrafts option |
Title Resolution Chain
The page title is resolved in this priority order:
titlefield in frontmatter- First
# headingin the markdown body (excluding headings inside fenced code blocks) - Filename-derived title:
index.mduses the parent directory name (title-cased), other files use the stem with dashes replaced by spaces
The root index.md (at the docs directory root) falls back to "Home".
Markdown Extensions
Heading Anchors
All h1, h2, and h3 headings automatically receive:
- An
idattribute derived from the heading text viaslugify()(lowercase, strip non-word chars, dashes for spaces) - A
#permalink link injected as a child anchor element (whenmarkdown.anchor.permalinkis notfalse) - A
scroll-mt-20CSS class to offset the sticky navbar height during scroll-to-anchor - Slug deduplication: if two headings produce the same slug, the second gets a
-1suffix, the third-2, etc.
Internal Links
Links starting with / or # are automatically tagged with data-lark-nav="true" for SPA navigation interception by the lark-mvc Router. External links receive target="_blank" and rel="noopener noreferrer".
Table of Contents
Insert [[toc]] anywhere in your markdown to render a table of contents inline. The [[toc]] marker is compiled to <div v-lark="theme/toc"></div>, which mounts the TOC theme View at that position.
Admonition Containers
Four container types are supported via the ::: fenced syntax:
::: tip
Useful advice displayed in an info-styled alert.
:::
::: warning
Cautionary note displayed in a warning-styled alert.
:::
::: danger
Critical warning displayed in an error-styled alert.
:::
::: details Click to expand
Hidden content revealed on click.
:::Containers are rendered as DaisyUI alert components:
tipmaps toalert-infowarningmaps toalert-warningdangermaps toalert-errordetailsmaps to a<details>element with<summary>, styled asalert-neutral
Container labels can be customized via markdown.containers config.
Code Blocks
Fenced code blocks with a language identifier are syntax-highlighted when Shiki is configured:
```typescript
const x: number = 42;
```Without Shiki, code blocks fall back to a styled <pre> with bg-neutral text-neutral-content rounded-box p-4 overflow-x-auto classes.
Theme System
The theme consists of four View factories, each paired with an HTML template. The registerThemeViews() convenience function registers all of them in a single call.
View Factories
| Factory | View ID | Purpose |
| -------------------------------- | ------------------- | ----------------------------------------------------- |
| createDocsLayoutView(template) | theme/docs-layout | Root layout: navbar, three-column body, prev/next nav |
| createSidebarView(template) | theme/sidebar | Left sidebar navigation tree |
| createTocView(template) | theme/toc | Right-side heading outline with smooth scroll |
| createSearchView(template) | theme/search | Search modal (local provider only) |
registerThemeViews
The recommended way to set up the theme:
// No need to import View — registerThemeViews uses defineView internally
import { registerThemeViews } from "@lark.js/docs/theme";
registerThemeViews();This function imports all .html templates internally (compiled by larkMvcPlugin at build time), creates the view classes, and calls registerViewClass() for each. Consumers never need to import .html files or call registerViewClass manually.
Layout Structure
docs-layout (root)
+-- Navbar (sticky top, backdrop-blur)
| +-- Site title
| +-- Nav items (horizontal menu, hidden on mobile)
| +-- Search (local button or DocSearch container)
+-- Flex container (max-w-7xl, centered)
| +-- Sidebar (w-64, left, visible on lg+)
| +-- Content (flex-1, prose max-w-none)
| +-- TOC (w-56, right, visible on xl+)
+-- Prev/Next navigation (bottom of content)
+-- Search modal (conditional, local provider only)The layout view stays mounted across all /docs/* routes. When the user navigates, observeLocation triggers an async render() that calls loadContent(path) to fetch the new page's compiled markdown, then updates the view data. The compiled markdown HTML is rendered inline via contentHtml.
Responsive Behavior
- Below
lgbreakpoint (1024px): sidebar is hidden - Below
xlbreakpoint (1280px): TOC is hidden - Nav items hidden on small screens, visible from
mdbreakpoint - Search button always visible; DocSearch provides its own keyboard shortcut (Ctrl+K)
Icons
Theme views use lucide-static for SVG icons, imported as raw strings via Vite's ?raw suffix. Icons are centralized in src/theme/icons.ts and set in init() (not assign()) since they are static data. Templates render icons with the raw output operator:
<span class="h-5 w-5 [&>svg]:h-full [&>svg]:w-full"> {{!icons.search}} </span>The wrapper <span> controls sizing. Tailwind utilities [&>svg]:w-full [&>svg]:h-full force the child <svg> to fill the container. Icons inherit currentColor from their parent, so color is controlled via standard CSS utilities (e.g., text-primary).
Search System
Local Search (provider: "local")
The built-in search is powered by MiniSearch (the same engine used by VitePress). It provides a modal dialog with:
- Prefix matching: typing "conf" matches "configuration"
- Fuzzy matching: tolerates typos (fuzzy factor 0.2)
- Field-weighted scoring: title matches boosted 2x, headings 1.5x, excerpt 1x
- Highlighted results: matched terms wrapped in
<mark>in both title and excerpt - Lazy index construction: the MiniSearch instance is built on first query from the build-time
searchIndex, then reused to subsequent searches - Open/close state driven by
State.searchOpenso the navbar button can toggle the modal without a direct view reference
DocSearch Integration (provider: "docsearch")
The DocSearch provider renders Algolia's styled search button and modal UI, but queries the local search index instead of Algolia's hosted API. No Algolia account or credentials are required.
Implementation: createLocalSearchClient(index) returns an Algolia-compatible search client with a search(requests) method. The client is injected into the DocSearch widget via transformSearchClient, replacing the default Algolia API call. The search client converts SearchEntry[] results into DocSearch's expected hit format with hierarchy.lvl0 (page title), hierarchy.lvl1 (first heading), url, _highlightResult, and _snippetResult fields.
The DocSearch widget provides:
- Styled search button in the navbar
- Modal with keyboard shortcut (Ctrl+K / Cmd+K)
- Recent searches (stored in localStorage)
- Result highlighting
Bundler Plugins
Vite Plugin
import { larkDocsPlugin } from "@lark.js/docs/vite";
export default defineConfig({
plugins: [larkDocsPlugin({ config: docsConfig, debug: false })],
});The plugin runs in the pre enforcement phase. Its resolveId hook appends a ?lark-docs suffix to .md imports so Vite does not treat them as static assets. Its load hook reads the raw markdown, compiles it through compileMarkdown(), and returns the JS module string.
Options: { config: DocsConfig, debug?: boolean }.
Webpack Plugin + Loader
import { LarkDocsPlugin } from "@lark.js/docs/webpack";
export default {
plugins: [new LarkDocsPlugin({ config: docsConfig })],
};LarkDocsPlugin pushes a loader rule onto compiler.options.module.rules at the compilation hook. The loader (larkDocsLoader) uses Webpack 5's this.callback() pattern for async result delivery. It self-references via __filename to resolve the loader path.
Options: { config: DocsConfig, test?: RegExp, exclude?: RegExp }. Defaults: test: /\.md$/, exclude: /node_modules/.
Rspack Plugin + Loader
import { LarkDocsPlugin } from "@lark.js/docs/rspack";
export default {
plugins: [new LarkDocsPlugin({ config: docsConfig })],
};Same API as Webpack, but the loader returns Promise<string> directly (Rspack async loader convention, no this.callback()).
Package Exports
| Sub-path | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------- |
| @lark.js/docs | Main barrel: all types, scanner, sidebar, markdown, compiler, runtime, theme factories |
| @lark.js/docs/compiler | compileMarkdown() + CompileMarkdownOptions type |
| @lark.js/docs/vite | larkDocsPlugin() Vite plugin + build-time utility re-exports |
| @lark.js/docs/webpack | LarkDocsPlugin class + larkDocsLoader() function |
| @lark.js/docs/rspack | LarkDocsPlugin class + larkDocsLoader() async function |
| @lark.js/docs/runtime | slugify() (browser-safe, no build deps) |
| @lark.js/docs/theme | registerThemeViews() + 4 view factories + createLocalSearchClient + icons |
| @lark.js/docs/client | Types-only: ambient module declarations for @lark-docs/generated and *.html (for /// <reference types>) |
The /vite, /webpack, and /rspack sub-paths re-export build-time utilities (scanDocsDir, generateSidebar, defineConfig) to avoid pulling in the main entry's lucide-static SVG ?raw imports, which are not valid in Node.js contexts.
The /client sub-path is types-only (no runtime code). It ships client.d.ts which provides declare module "@lark-docs/generated" and declare module "*.html" ambient declarations. Consumer projects reference it via /// <reference types="@lark.js/docs/client" /> in their shims.d.ts.
API Reference
defineConfig(config: DocsConfig, projectRoot?: string): DocsConfig
Type-safe configuration helper. Returns the config unchanged while triggering route generation. The optional projectRoot parameter controls path resolution for the docs directory and the generated output. Defaults to process.cwd().
registerThemeViews(options?: RegisterThemeViewsOptions): void
Registers all four theme views (layout, sidebar, TOC, search) with the lark-mvc view registry. Imports the .html templates internally so consumers don't need to handle them.
scanDocsDir(docsDir: string, baseUrl: string, options?: { excludeDrafts?: boolean }): DocsRoute[]
Recursively scans a docs directory and returns route entries. Skips entries starting with _ or ., plus node_modules, __tests__, __fixtures__, .git, .vitepress, .lark-docs, and dist. index.md maps to the directory root without trailing /.
generateSidebar(routes: DocsRoute[], prefix: string): SidebarItem[]
Auto-generates sidebar items for routes under a given prefix. Groups by subdirectory, sorts by sidebarPosition then title, produces a SidebarItem[] tree.
compileMarkdown(source: string, options: CompileMarkdownOptions): Promise<string>
Compiles a .md source string into a JS module string that exports pageData and contentHtml. The pipeline: extract frontmatter, create parser, optionally initialize Shiki, parse and render to HTML, build page metadata, emit JS module.
slugify(text: string): string
Converts text to a URL-safe slug: lowercase, strip non-word chars (except spaces and dashes), replace whitespace with dashes, collapse consecutive dashes.
Theme View Factories
createDocsLayoutView(template); // root layout
createSidebarView(template); // sidebar navigation
createTocView(template); // heading outline
createSearchView(template); // search modal
createLocalSearchClient(index); // Algolia-compatible search clientType Definitions
All types are exported from the main entry and available for import:
import type {
DocsConfig,
NavItem,
SidebarConfig,
SidebarItem,
MarkdownOptions,
HighlightOptions,
SearchOptions,
PageData,
HeadingInfo,
DocsRoute,
SearchEntry,
FrontmatterResult,
CompileMarkdownOptions,
} from "@lark.js/docs";Generated Output
defineConfig() writes a generated module to .lark-docs/generated/index.js (a dot directory at project root, similar to VitePress's .vitepress/ and Docusaurus's .docusaurus/). This directory should be added to .gitignore.
The generated module exports:
loadContent(path)-- dynamically imports the compiled.mdmodule for a given route path, returns{ pageData, contentHtml }ornullroutes: Record<string, string>-- maps every docs path to the layout view"theme/docs-layout"docsConfig-- the runtime site configuration (title, description, lang, nav, sidebar)getSearchIndex()-- lazily builds the search index by loading all non-virtual.mdmodules on first call (filtering through_searchablePathsto exclude virtual index routes), returnsSearchEntry[]
// vite.config.ts
resolve: {
alias: {
"@lark-docs/generated": resolve(PKG_DIR, ".lark-docs/generated"),
},
}
// boot.ts
import { routes, docsConfig, loadContent, getSearchIndex } from "@lark-docs/generated";Type declarations for @lark-docs/generated are provided by the @lark.js/docs/client package export via /// <reference types> directive -- no generated .d.ts file is needed.
Dependencies
Runtime:
@docsearch/css^4.6.3 -- DocSearch widget styles (dynamic import, only for"docsearch"provider)@docsearch/js^4.6.3 -- DocSearch widget (dynamic import, only for"docsearch"provider)@lark.js/mvc^0.0.17 -- MVC framework (re-exported by@lark.js/docsso consumers do not need to install it separately)ejs^3.1.10 -- Template engine for generated module outputjs-yaml^5.2.0 -- YAML frontmatter parsinglucide-static^1.21.0 -- SVG icons via?rawimportmarkdown-it^14.2.0 -- Markdown parsermarkdown-it-container^4.0.0 -- Admonition container syntaxminisearch^7.2.0 -- Full-text search engine (same as VitePress)shiki^4.3.0 -- Code syntax highlighting (dynamic import, lazy singleton)zod^4.4.3 -- Runtime schema validation for State-injected values
Peer:
@tailwindcss/typography^0.5.0 --proseclass for markdown contentdaisyui^5.0.0 -- UI component classestailwindcss^4.0.0 -- Utility-first CSS
License
MIT
