@beforesemicolon/builder 1.8.11 → 1.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +839 -142
- package/dist/cjs/docs/run.js +14 -14
- package/dist/cjs/docs/templates/fading-citrus/README.md +770 -0
- package/dist/cjs/docs/templates/fading-citrus/assets/logo.svg +8 -0
- package/dist/cjs/docs/templates/fading-citrus/layouts/_footer.js +7 -4
- package/dist/cjs/docs/templates/fading-citrus/layouts/_header.js +4 -4
- package/dist/cjs/docs/templates/fading-citrus/layouts/document.js +1 -1
- package/dist/cjs/docs/templates/fading-citrus/layouts/landing.js +1 -1
- package/dist/cjs/docs/templates/fading-citrus/stylesheets/common.css +6 -4
- package/dist/cjs/docs/types.js +1 -1
- package/dist/esm/docs/run.js +13 -13
- package/dist/esm/docs/templates/fading-citrus/README.md +770 -0
- package/dist/esm/docs/templates/fading-citrus/assets/logo.svg +8 -0
- package/dist/esm/docs/templates/fading-citrus/layouts/_footer.js +7 -4
- package/dist/esm/docs/templates/fading-citrus/layouts/_header.js +4 -4
- package/dist/esm/docs/templates/fading-citrus/layouts/document.js +1 -1
- package/dist/esm/docs/templates/fading-citrus/layouts/landing.js +1 -1
- package/dist/esm/docs/templates/fading-citrus/stylesheets/common.css +6 -4
- package/dist/types/docs/types.d.ts +3 -2
- package/package.json +1 -1
- package/dist/cjs/docs/templates/fading-citrus/layouts/_logo.js +0 -31
- package/dist/esm/docs/templates/fading-citrus/layouts/_logo.js +0 -31
package/README.md
CHANGED
|
@@ -1,219 +1,916 @@
|
|
|
1
1
|
# @beforesemicolon/builder
|
|
2
2
|
|
|
3
|
-
Utilities to build npm packages and
|
|
3
|
+
Utilities to build npm packages and static documentation websites for Before Semicolon projects.
|
|
4
4
|
|
|
5
|
-
This package provides
|
|
5
|
+
This package provides three public helpers:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
7
|
+
- `buildModules()` builds TypeScript sources into `dist/esm` and `dist/cjs`.
|
|
8
|
+
- `buildBrowser()` builds a browser bundle, usually for demos or docs.
|
|
9
|
+
- `buildDocs()` renders a static Markdown documentation site.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
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.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- Requirements
|
|
15
|
-
- Installation
|
|
16
|
-
- Quick examples
|
|
17
|
-
- Library usage (Node)
|
|
18
|
-
- Building the browser bundle
|
|
19
|
-
- Generating the documentation website
|
|
20
|
-
- API
|
|
21
|
-
- buildModules(options?)
|
|
22
|
-
- buildBrowser(options?)
|
|
23
|
-
- buildDocs(options?)
|
|
24
|
-
- Documentation site layout and front-matter
|
|
25
|
-
- Scripts (in package.json)
|
|
26
|
-
- Development
|
|
27
|
-
- Contributing
|
|
28
|
-
- License
|
|
13
|
+
## Requirements
|
|
29
14
|
|
|
30
|
-
|
|
15
|
+
- Node.js `>=18.16.0`
|
|
16
|
+
- ESM projects are supported directly.
|
|
17
|
+
- CommonJS consumers can use the package `require` export.
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
- Produces a single browser bundle for client-side documentation UI.
|
|
34
|
-
- Static site generator for Markdown: supports layouts, assets, stylesheets, and scripts.
|
|
35
|
-
- Uses marked + highlight.js for Markdown rendering, DOMPurify for sanitization and minifies output CSS/JS/HTML.
|
|
36
|
-
- Simple, zero-config defaults plus options for common customization.
|
|
19
|
+
## Installation
|
|
37
20
|
|
|
38
|
-
|
|
21
|
+
```sh
|
|
22
|
+
npm install --save-dev @beforesemicolon/builder
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { buildModules, buildBrowser, buildDocs } from '@beforesemicolon/builder'
|
|
39
29
|
|
|
40
|
-
|
|
30
|
+
await buildModules()
|
|
31
|
+
await buildBrowser()
|
|
32
|
+
await buildDocs()
|
|
33
|
+
```
|
|
41
34
|
|
|
42
|
-
|
|
35
|
+
Common project script:
|
|
43
36
|
|
|
44
|
-
|
|
37
|
+
```js
|
|
38
|
+
import { buildModules, buildBrowser, buildDocs } from '@beforesemicolon/builder'
|
|
45
39
|
|
|
40
|
+
const docsOptions = {
|
|
41
|
+
template: 'fading-citrus',
|
|
42
|
+
siteUrl: 'https://example.com',
|
|
43
|
+
generatedFiles: {
|
|
44
|
+
netlify: true,
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const run = async () => {
|
|
49
|
+
await Promise.all([buildModules(), buildBrowser(), buildDocs(docsOptions)])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
run()
|
|
46
53
|
```
|
|
47
|
-
# npm
|
|
48
|
-
npm install @beforesemicolon/builder
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
### buildModules(options?)
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
buildModules(options?: {
|
|
61
|
+
directoryPath?: string
|
|
62
|
+
}): Promise<void>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Builds source files into server-friendly ESM and CommonJS output.
|
|
66
|
+
|
|
67
|
+
Defaults:
|
|
68
|
+
|
|
69
|
+
- `directoryPath`: `process.cwd()/src`
|
|
70
|
+
- ESM output: `dist/esm`
|
|
71
|
+
- CommonJS output: `dist/cjs`
|
|
72
|
+
|
|
73
|
+
Behavior:
|
|
74
|
+
|
|
75
|
+
- Recursively scans the source directory.
|
|
76
|
+
- Skips files ending in `.spec.ts`.
|
|
77
|
+
- Skips `/client.ts` from module builds.
|
|
78
|
+
- Uses `esbuild`.
|
|
79
|
+
- Minifies output.
|
|
80
|
+
- Keeps symbol names for better stack traces.
|
|
81
|
+
|
|
82
|
+
### buildBrowser(options?)
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
buildBrowser(options?: {
|
|
86
|
+
entry?: string
|
|
87
|
+
out?: string
|
|
88
|
+
}): Promise<void>
|
|
54
89
|
```
|
|
55
90
|
|
|
56
|
-
|
|
91
|
+
Builds a single browser bundle.
|
|
57
92
|
|
|
58
|
-
|
|
93
|
+
Defaults:
|
|
94
|
+
|
|
95
|
+
- `entry`: `src/client`
|
|
96
|
+
- `out`: `dist/client.js`
|
|
97
|
+
|
|
98
|
+
Behavior:
|
|
99
|
+
|
|
100
|
+
- Uses `esbuild`.
|
|
101
|
+
- Generates sourcemaps.
|
|
102
|
+
- Minifies output.
|
|
103
|
+
- Includes a small internal plugin that removes the `Doc` export from `@beforesemicolon/html-parser` when bundling.
|
|
104
|
+
|
|
105
|
+
### buildDocs(options?)
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
buildDocs(options?: {
|
|
109
|
+
srcDir?: string
|
|
110
|
+
publicDir?: string
|
|
111
|
+
markedOptions?: MarkedExtension
|
|
112
|
+
template?: string
|
|
113
|
+
siteUrl?: string
|
|
114
|
+
generatedFiles?:
|
|
115
|
+
| boolean
|
|
116
|
+
| {
|
|
117
|
+
sitemap?: boolean
|
|
118
|
+
robots?: boolean
|
|
119
|
+
llms?: boolean
|
|
120
|
+
llmsFull?: boolean
|
|
121
|
+
netlify?: boolean
|
|
122
|
+
}
|
|
123
|
+
}): Promise<void>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Builds a static documentation site from Markdown.
|
|
127
|
+
|
|
128
|
+
Defaults:
|
|
129
|
+
|
|
130
|
+
- `srcDir`: `process.cwd()/docs`
|
|
131
|
+
- `publicDir`: `process.cwd()/website`
|
|
132
|
+
- `template`: no named template, uses the built-in `default` layout
|
|
133
|
+
- `generatedFiles`: enabled for `sitemap`, `robots`, `llms`, and `llmsFull`
|
|
134
|
+
- `generatedFiles.netlify`: `false`
|
|
135
|
+
|
|
136
|
+
Example:
|
|
59
137
|
|
|
60
138
|
```js
|
|
61
|
-
|
|
62
|
-
|
|
139
|
+
await buildDocs({
|
|
140
|
+
template: 'fading-citrus',
|
|
141
|
+
siteUrl: 'https://docs.example.com',
|
|
142
|
+
generatedFiles: {
|
|
143
|
+
netlify: true,
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Docs Directory Structure
|
|
149
|
+
|
|
150
|
+
The default source directory is `docs/`.
|
|
151
|
+
|
|
152
|
+
```txt
|
|
153
|
+
docs/
|
|
154
|
+
index.md
|
|
155
|
+
guide/
|
|
156
|
+
getting-started.md
|
|
157
|
+
assets/
|
|
158
|
+
stylesheets/
|
|
159
|
+
scripts/
|
|
160
|
+
_layouts/
|
|
161
|
+
_template/
|
|
162
|
+
template.config.js
|
|
163
|
+
assets/
|
|
164
|
+
stylesheets/
|
|
165
|
+
scripts/
|
|
166
|
+
layouts/
|
|
167
|
+
robots.txt
|
|
168
|
+
sitemap.xml
|
|
169
|
+
llms.txt
|
|
170
|
+
llms-full.txt
|
|
171
|
+
_redirects
|
|
172
|
+
netlify.toml
|
|
173
|
+
```
|
|
63
174
|
|
|
64
|
-
|
|
65
|
-
await buildModules({ directoryPath: 'src' })
|
|
175
|
+
Supported folders:
|
|
66
176
|
|
|
67
|
-
|
|
68
|
-
|
|
177
|
+
- `assets/`: copied to the same relative location in the output directory.
|
|
178
|
+
- `stylesheets/`: CSS files are minified and copied to output.
|
|
179
|
+
- `scripts/`: JS files are minified and copied to output.
|
|
180
|
+
- `_layouts/`: page layout modules. Each file default-exports a page layout function.
|
|
181
|
+
- `_template/`: source-level extension for the selected template.
|
|
182
|
+
- `_template/assets/`: copied into `publicDir/assets`, overriding or extending template assets.
|
|
183
|
+
- `_template/stylesheets/`: copied into `publicDir/stylesheets`, overriding or extending template styles.
|
|
184
|
+
- `_template/scripts/`: copied into `publicDir/scripts`, overriding or extending template scripts.
|
|
185
|
+
- `_template/layouts/`: custom page layouts that can override or extend selected template layouts.
|
|
186
|
+
- `_template/template.config.js`: source-level template config merged with the selected template config.
|
|
69
187
|
|
|
70
|
-
|
|
71
|
-
|
|
188
|
+
Files and folders starting with `.` or `_` are skipped during Markdown page discovery. `_template` and `_layouts` are used explicitly by the docs builder.
|
|
189
|
+
|
|
190
|
+
## Page Front Matter
|
|
191
|
+
|
|
192
|
+
Each Markdown page can include front matter:
|
|
193
|
+
|
|
194
|
+
```md
|
|
195
|
+
---
|
|
196
|
+
name: Get Started
|
|
197
|
+
title: Get Started with Example
|
|
198
|
+
description: Learn how to install and use Example.
|
|
199
|
+
order: 1
|
|
200
|
+
layout: document
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
# Get Started
|
|
72
204
|
```
|
|
73
205
|
|
|
74
|
-
|
|
206
|
+
Common fields:
|
|
207
|
+
|
|
208
|
+
- `name`: label used in the generated site map.
|
|
209
|
+
- `title`: HTML title and generated metadata title.
|
|
210
|
+
- `description`: meta description and generated metadata description.
|
|
211
|
+
- `order`: numeric sort order for site map and generated files.
|
|
212
|
+
- `layout`: page layout name. Defaults to `default`.
|
|
213
|
+
|
|
214
|
+
The final page props include:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
interface PageProps {
|
|
218
|
+
name?: string
|
|
219
|
+
path?: string
|
|
220
|
+
order?: number
|
|
221
|
+
title?: string
|
|
222
|
+
description?: string
|
|
223
|
+
content?: string
|
|
224
|
+
siteMap?: SiteMap
|
|
225
|
+
tableOfContent?: Array<{
|
|
226
|
+
path: string
|
|
227
|
+
label: string
|
|
228
|
+
level: string
|
|
229
|
+
}>
|
|
230
|
+
projectMeta?: {
|
|
231
|
+
name: string
|
|
232
|
+
version: string
|
|
233
|
+
[key: string]: unknown
|
|
234
|
+
}
|
|
235
|
+
renderMarkdown?: (markdown: string) => string
|
|
236
|
+
scripts?: string[]
|
|
237
|
+
themeStylesheet?: string
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Page Layouts
|
|
242
|
+
|
|
243
|
+
Page layouts render complete HTML documents. A layout file must default-export a function that receives `PageProps` and returns an HTML string.
|
|
244
|
+
|
|
245
|
+
Example `docs/_layouts/document.js`:
|
|
75
246
|
|
|
76
247
|
```js
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
248
|
+
export default ({
|
|
249
|
+
title,
|
|
250
|
+
description,
|
|
251
|
+
content,
|
|
252
|
+
scripts = [],
|
|
253
|
+
}) => `<!doctype html>
|
|
254
|
+
<html>
|
|
255
|
+
<head>
|
|
256
|
+
<meta charset="utf-8">
|
|
257
|
+
<meta name="description" content="${description || ''}">
|
|
258
|
+
<title>${title || ''}</title>
|
|
259
|
+
</head>
|
|
260
|
+
<body>
|
|
261
|
+
${content || ''}
|
|
262
|
+
${scripts.join('')}
|
|
263
|
+
</body>
|
|
264
|
+
</html>`
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Layout lookup order:
|
|
268
|
+
|
|
269
|
+
1. Built-in layouts.
|
|
270
|
+
2. Selected template layouts.
|
|
271
|
+
3. `docs/_layouts`.
|
|
272
|
+
4. `docs/_template/layouts`.
|
|
273
|
+
|
|
274
|
+
Later layout files with the same basename override earlier ones.
|
|
82
275
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
276
|
+
## Templates
|
|
277
|
+
|
|
278
|
+
Named templates are loaded from:
|
|
279
|
+
|
|
280
|
+
```txt
|
|
281
|
+
src/docs/templates/<template-name>/
|
|
88
282
|
```
|
|
89
283
|
|
|
90
|
-
|
|
284
|
+
Available templates:
|
|
285
|
+
|
|
286
|
+
- `fading-citrus`: a complete landing and documentation template with Markdown layout handlers, theme variables, assets, and page scripts. See [fading-citrus template README](./src/docs/templates/fading-citrus/README.md).
|
|
91
287
|
|
|
92
|
-
|
|
288
|
+
A template can provide:
|
|
93
289
|
|
|
290
|
+
```txt
|
|
291
|
+
template.config.js
|
|
292
|
+
assets/
|
|
293
|
+
stylesheets/
|
|
294
|
+
scripts/
|
|
295
|
+
layouts/
|
|
94
296
|
```
|
|
95
|
-
|
|
297
|
+
|
|
298
|
+
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.
|
|
299
|
+
|
|
300
|
+
## Template Config
|
|
301
|
+
|
|
302
|
+
A template config exports an object:
|
|
303
|
+
|
|
304
|
+
```js
|
|
305
|
+
export default {
|
|
306
|
+
markedOptions: {},
|
|
307
|
+
markdownLayouts: {},
|
|
308
|
+
scripts: {},
|
|
309
|
+
theme: {
|
|
310
|
+
light: {},
|
|
311
|
+
dark: {},
|
|
312
|
+
},
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Config from `docs/_template/template.config.js` is merged into the selected template config.
|
|
317
|
+
|
|
318
|
+
Merge behavior:
|
|
319
|
+
|
|
320
|
+
- `markdownLayouts` are shallow-merged by layout name.
|
|
321
|
+
- `scripts` are shallow-merged by script name.
|
|
322
|
+
- `theme.light` and `theme.dark` are shallow-merged by CSS variable name.
|
|
323
|
+
- Other top-level config values use the docs source config value when provided.
|
|
324
|
+
|
|
325
|
+
Example docs source extension:
|
|
326
|
+
|
|
327
|
+
```js
|
|
328
|
+
import pricingCards from './layouts/pricing-cards.js'
|
|
329
|
+
|
|
330
|
+
export default {
|
|
331
|
+
markdownLayouts: {
|
|
332
|
+
'pricing-cards': pricingCards,
|
|
333
|
+
},
|
|
334
|
+
theme: {
|
|
335
|
+
light: {
|
|
336
|
+
'--primary': 'oklch(0.62 0.18 250)',
|
|
337
|
+
},
|
|
338
|
+
dark: {
|
|
339
|
+
'--primary': 'oklch(0.76 0.16 250)',
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Markdown Layout Syntax
|
|
346
|
+
|
|
347
|
+
The docs renderer extends `marked` with a custom block syntax:
|
|
348
|
+
|
|
349
|
+
```md
|
|
350
|
+
::: layout <type> [options]
|
|
351
|
+
|
|
352
|
+
=== <name> [options]
|
|
353
|
+
|
|
354
|
+
Markdown content for this part.
|
|
355
|
+
|
|
356
|
+
=== <name> [options]
|
|
357
|
+
|
|
358
|
+
More Markdown content.
|
|
359
|
+
|
|
360
|
+
:::
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Example:
|
|
364
|
+
|
|
365
|
+
```md
|
|
366
|
+
::: layout grid columns=3 gap=lg
|
|
367
|
+
|
|
368
|
+
=== card span=2
|
|
369
|
+
|
|
370
|
+
## First card
|
|
371
|
+
|
|
372
|
+
Markdown content.
|
|
373
|
+
|
|
374
|
+
=== card sticky
|
|
375
|
+
|
|
376
|
+
## Second card
|
|
377
|
+
|
|
378
|
+
More Markdown content.
|
|
379
|
+
|
|
380
|
+
===
|
|
381
|
+
|
|
382
|
+
Unnamed item.
|
|
383
|
+
|
|
384
|
+
:::
|
|
96
385
|
```
|
|
97
386
|
|
|
98
|
-
|
|
387
|
+
Header parsing:
|
|
99
388
|
|
|
100
|
-
|
|
389
|
+
```txt
|
|
390
|
+
::: layout grid columns=3 gap=lg
|
|
391
|
+
```
|
|
101
392
|
|
|
102
|
-
|
|
103
|
-
- Scans the given directory (default: `process.cwd()/src`) recursively for files and builds them into:
|
|
104
|
-
- `dist/esm` (ESM format)
|
|
105
|
-
- `dist/cjs` (CommonJS format)
|
|
106
|
-
- Files ending with `.spec.ts` or `/client.ts` are ignored from the module build set.
|
|
107
|
-
- Uses `esbuild` with minification and keeps symbol names for better stack traces.
|
|
393
|
+
Produces:
|
|
108
394
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
395
|
+
```js
|
|
396
|
+
{
|
|
397
|
+
type: 'grid',
|
|
398
|
+
options: {
|
|
399
|
+
columns: 3,
|
|
400
|
+
gap: 'lg',
|
|
401
|
+
},
|
|
402
|
+
}
|
|
403
|
+
```
|
|
114
404
|
|
|
115
|
-
|
|
116
|
-
- Generates a static documentation website from a Markdown `docs` directory.
|
|
117
|
-
- Defaults: `srcDir` -> `docs`, `publicDir` -> `website`.
|
|
118
|
-
- Supported docs structure (defaults used by the generator):
|
|
119
|
-
- `_layouts/` — custom templates (each module should default-export a function matching `PageProps`)
|
|
120
|
-
- `assets/` — copied to the site output
|
|
121
|
-
- `stylesheets/` — CSS files are minified and copied
|
|
122
|
-
- `scripts/` — JS files are minified and copied
|
|
123
|
-
- `*.md` — Markdown pages with front-matter to control metadata
|
|
124
|
-
- Pages use `marked` with a custom renderer (heading IDs, code blocks, links) and `highlight.js` for syntax highlighting.
|
|
125
|
-
- HTML is sanitized with DOMPurify and minified with `html-minifier`.
|
|
405
|
+
Item header parsing:
|
|
126
406
|
|
|
127
|
-
|
|
407
|
+
```txt
|
|
408
|
+
=== hero span=2 sticky
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Produces:
|
|
128
412
|
|
|
129
|
-
|
|
413
|
+
```js
|
|
414
|
+
{
|
|
415
|
+
name: 'hero',
|
|
416
|
+
options: {
|
|
417
|
+
span: 2,
|
|
418
|
+
sticky: true,
|
|
419
|
+
},
|
|
420
|
+
}
|
|
421
|
+
```
|
|
130
422
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
423
|
+
Unnamed items are supported:
|
|
424
|
+
|
|
425
|
+
```md
|
|
426
|
+
===
|
|
427
|
+
|
|
428
|
+
Content
|
|
429
|
+
```
|
|
135
430
|
|
|
136
|
-
|
|
431
|
+
Produces:
|
|
137
432
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
433
|
+
```js
|
|
434
|
+
{
|
|
435
|
+
name: null,
|
|
436
|
+
options: {},
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Option parsing rules:
|
|
441
|
+
|
|
442
|
+
- `key=value` becomes a keyed option.
|
|
443
|
+
- Bare words become boolean `true`.
|
|
444
|
+
- Numeric values become numbers.
|
|
445
|
+
- `true` and `false` become booleans.
|
|
446
|
+
- Quoted values are supported.
|
|
447
|
+
|
|
448
|
+
Examples:
|
|
449
|
+
|
|
450
|
+
```txt
|
|
451
|
+
columns=3
|
|
452
|
+
gap=lg
|
|
453
|
+
sticky
|
|
454
|
+
label="Get Started"
|
|
455
|
+
enabled=false
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Nested layout blocks are supported. Nested blocks are preserved inside the parent item body and rendered through the same Markdown renderer.
|
|
459
|
+
|
|
460
|
+
## Markdown Layout Handlers
|
|
461
|
+
|
|
462
|
+
Markdown layout handlers are registered through `template.config.js`:
|
|
463
|
+
|
|
464
|
+
```js
|
|
465
|
+
import pricingCards from './layouts/pricing-cards.js'
|
|
466
|
+
|
|
467
|
+
export default {
|
|
468
|
+
markdownLayouts: {
|
|
469
|
+
'pricing-cards': pricingCards,
|
|
470
|
+
},
|
|
471
|
+
}
|
|
472
|
+
```
|
|
146
473
|
|
|
147
|
-
A
|
|
474
|
+
A handler receives parsed layout data and a rendering context:
|
|
148
475
|
|
|
149
476
|
```ts
|
|
150
|
-
|
|
477
|
+
type MarkdownLayoutHandler = (
|
|
478
|
+
layout: {
|
|
479
|
+
type: string
|
|
480
|
+
options: Record<string, string | number | boolean>
|
|
481
|
+
parts: Array<{
|
|
482
|
+
name: string | null
|
|
483
|
+
options: Record<string, string | number | boolean>
|
|
484
|
+
body: string
|
|
485
|
+
html: string
|
|
486
|
+
}>
|
|
487
|
+
raw: string
|
|
488
|
+
},
|
|
489
|
+
context: {
|
|
490
|
+
renderMarkdown(markdown: string): string
|
|
491
|
+
renderDefault(node): string
|
|
492
|
+
renderParts(node): Array<{ html: string }>
|
|
493
|
+
}
|
|
494
|
+
) => string
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Each part body is rendered from Markdown to HTML before the handler receives it. Use `part.html` when injecting content.
|
|
498
|
+
|
|
499
|
+
Example handler:
|
|
500
|
+
|
|
501
|
+
```js
|
|
502
|
+
export default ({ parts, options }) => {
|
|
503
|
+
const tierClass = options.featured ? ' pricing-cards-featured' : ''
|
|
504
|
+
|
|
505
|
+
return `<div class="pricing-cards${tierClass}">
|
|
506
|
+
${parts
|
|
507
|
+
.map(
|
|
508
|
+
(
|
|
509
|
+
part,
|
|
510
|
+
index
|
|
511
|
+
) => `<section class="pricing-card option-${index + 1}">
|
|
512
|
+
${part.html}
|
|
513
|
+
</section>`
|
|
514
|
+
)
|
|
515
|
+
.join('')}
|
|
516
|
+
</div>`
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Markdown:
|
|
521
|
+
|
|
522
|
+
```md
|
|
523
|
+
::: layout pricing-cards featured
|
|
524
|
+
|
|
525
|
+
===
|
|
526
|
+
|
|
527
|
+
## Starter
|
|
528
|
+
|
|
529
|
+
$10/month
|
|
530
|
+
|
|
531
|
+
===
|
|
532
|
+
|
|
533
|
+
## Pro
|
|
534
|
+
|
|
535
|
+
$30/month
|
|
536
|
+
|
|
537
|
+
:::
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
Generated HTML is entirely controlled by the handler.
|
|
541
|
+
|
|
542
|
+
## Default Markdown Layout Rendering
|
|
543
|
+
|
|
544
|
+
If a layout type has no custom handler, builder renders a generic structure:
|
|
545
|
+
|
|
546
|
+
```html
|
|
547
|
+
<div
|
|
548
|
+
class="bfs-layout bfs-layout-grid"
|
|
549
|
+
data-layout="grid"
|
|
550
|
+
style="--columns: 3; --gap: lg;"
|
|
551
|
+
>
|
|
552
|
+
<section class="bfs-layout-item" data-name="card" style="--span: 2;">
|
|
553
|
+
...
|
|
554
|
+
</section>
|
|
555
|
+
</div>
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Boolean options are omitted from inline styles. Non-boolean options are converted to CSS custom properties.
|
|
559
|
+
|
|
560
|
+
## marked Options
|
|
561
|
+
|
|
562
|
+
The docs builder uses `marked`, `marked-highlight`, and a custom renderer for headings, code, and links.
|
|
563
|
+
|
|
564
|
+
You can extend `marked` globally for docs generation:
|
|
565
|
+
|
|
566
|
+
```js
|
|
567
|
+
await buildDocs({
|
|
568
|
+
markedOptions: {
|
|
569
|
+
renderer: {
|
|
570
|
+
codespan({ text }) {
|
|
571
|
+
return `<code data-inline>${text}</code>`
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
})
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Templates can also provide `markedOptions` through `template.config.js`.
|
|
579
|
+
|
|
580
|
+
## Page Scripts
|
|
581
|
+
|
|
582
|
+
Template scripts are declared in `template.config.js`.
|
|
583
|
+
|
|
584
|
+
```js
|
|
585
|
+
import { renderCodeCopyScript } from './layouts/_code-snippet.js'
|
|
586
|
+
|
|
587
|
+
export default {
|
|
588
|
+
scripts: {
|
|
589
|
+
'code-copy': {
|
|
590
|
+
match: 'code-copy-btn',
|
|
591
|
+
render: renderCodeCopyScript,
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
Script definitions:
|
|
598
|
+
|
|
599
|
+
```ts
|
|
600
|
+
type DocsScriptMatcher =
|
|
601
|
+
| string
|
|
602
|
+
| string[]
|
|
603
|
+
| RegExp
|
|
604
|
+
| ((html: string) => boolean)
|
|
605
|
+
|
|
606
|
+
interface DocsScriptDefinition {
|
|
607
|
+
match?: DocsScriptMatcher
|
|
608
|
+
render: () => string
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
type DocsScriptRegistry = Record<
|
|
612
|
+
string,
|
|
613
|
+
false | DocsScriptDefinition | (() => string)
|
|
614
|
+
>
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
Behavior:
|
|
618
|
+
|
|
619
|
+
- Scripts are rendered per page after Markdown has been rendered.
|
|
620
|
+
- If `match` is omitted, the script is included on every page.
|
|
621
|
+
- A string matcher checks `html.includes(match)`.
|
|
622
|
+
- An array matcher checks whether any string is present.
|
|
623
|
+
- A RegExp matcher tests the rendered page HTML.
|
|
624
|
+
- A function matcher receives the rendered page HTML and returns a boolean.
|
|
625
|
+
- A script can be disabled by setting its registry value to `false` in an extending config.
|
|
626
|
+
|
|
627
|
+
Layouts receive scripts through `props.scripts` and must insert them where appropriate, usually before `</body>`.
|
|
628
|
+
|
|
629
|
+
```js
|
|
630
|
+
export default (props) => `
|
|
151
631
|
<!doctype html>
|
|
152
632
|
<html>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
</head>
|
|
158
|
-
<body>
|
|
159
|
-
${content}
|
|
160
|
-
</body>
|
|
633
|
+
<body>
|
|
634
|
+
${props.content}
|
|
635
|
+
${props.scripts?.join('') || ''}
|
|
636
|
+
</body>
|
|
161
637
|
</html>`
|
|
162
638
|
```
|
|
163
639
|
|
|
164
|
-
|
|
640
|
+
## Theme Variables
|
|
165
641
|
|
|
166
|
-
|
|
167
|
-
- `lint` — runs ESLint and Prettier check
|
|
168
|
-
- `format` — runs ESLint autofix and Prettier write
|
|
642
|
+
Templates can define theme variables in `template.config.js`:
|
|
169
643
|
|
|
170
|
-
|
|
644
|
+
```js
|
|
645
|
+
export default {
|
|
646
|
+
theme: {
|
|
647
|
+
light: {
|
|
648
|
+
'--background': 'oklch(0.98 0.006 250)',
|
|
649
|
+
'--foreground': 'oklch(0.18 0.015 250)',
|
|
650
|
+
'--primary': 'oklch(0.66 0.18 45)',
|
|
651
|
+
},
|
|
652
|
+
dark: {
|
|
653
|
+
'--background': 'oklch(0.18 0.015 250)',
|
|
654
|
+
'--foreground': 'oklch(0.96 0.005 250)',
|
|
655
|
+
'--primary': 'oklch(0.74 0.18 45)',
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
}
|
|
659
|
+
```
|
|
171
660
|
|
|
172
|
-
|
|
173
|
-
- Install dependencies:
|
|
661
|
+
Builder converts theme variables into:
|
|
174
662
|
|
|
663
|
+
```txt
|
|
664
|
+
website/stylesheets/theme.css
|
|
175
665
|
```
|
|
176
|
-
|
|
666
|
+
|
|
667
|
+
The generated file includes:
|
|
668
|
+
|
|
669
|
+
- `:root` variables.
|
|
670
|
+
- `@media (prefers-color-scheme: dark)` variables.
|
|
671
|
+
- `[data-theme="light"]` variables.
|
|
672
|
+
- `[data-theme="dark"]` variables.
|
|
673
|
+
|
|
674
|
+
A theme mode can be disabled with `false`:
|
|
675
|
+
|
|
676
|
+
```js
|
|
677
|
+
export default {
|
|
678
|
+
theme: {
|
|
679
|
+
light: false,
|
|
680
|
+
},
|
|
681
|
+
}
|
|
177
682
|
```
|
|
178
683
|
|
|
179
|
-
|
|
684
|
+
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.
|
|
180
685
|
|
|
686
|
+
Page layouts receive:
|
|
687
|
+
|
|
688
|
+
```ts
|
|
689
|
+
themeStylesheet?: string
|
|
181
690
|
```
|
|
182
|
-
|
|
691
|
+
|
|
692
|
+
Templates should include it in `<head>`:
|
|
693
|
+
|
|
694
|
+
```js
|
|
695
|
+
${props.themeStylesheet ? `<link rel="stylesheet" href="${props.themeStylesheet}">` : ''}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
Docs sources can override only variable values by adding `docs/_template/template.config.js`.
|
|
699
|
+
|
|
700
|
+
## Generated Files
|
|
701
|
+
|
|
702
|
+
`buildDocs()` can generate common root-level files into `publicDir`.
|
|
703
|
+
|
|
704
|
+
Defaults:
|
|
705
|
+
|
|
706
|
+
```js
|
|
707
|
+
generatedFiles: {
|
|
708
|
+
sitemap: true,
|
|
709
|
+
robots: true,
|
|
710
|
+
llms: true,
|
|
711
|
+
llmsFull: true,
|
|
712
|
+
netlify: false,
|
|
713
|
+
}
|
|
183
714
|
```
|
|
184
715
|
|
|
185
|
-
|
|
716
|
+
Disable all generated files:
|
|
186
717
|
|
|
718
|
+
```js
|
|
719
|
+
await buildDocs({
|
|
720
|
+
generatedFiles: false,
|
|
721
|
+
})
|
|
187
722
|
```
|
|
188
|
-
|
|
189
|
-
|
|
723
|
+
|
|
724
|
+
Enable Netlify files:
|
|
725
|
+
|
|
726
|
+
```js
|
|
727
|
+
await buildDocs({
|
|
728
|
+
siteUrl: 'https://docs.example.com',
|
|
729
|
+
generatedFiles: {
|
|
730
|
+
netlify: true,
|
|
731
|
+
},
|
|
732
|
+
})
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
Generated files:
|
|
736
|
+
|
|
737
|
+
- `sitemap.xml`: generated from discovered Markdown pages. Requires `siteUrl`.
|
|
738
|
+
- `robots.txt`: generated with `Allow: /` and a sitemap URL when `siteUrl` is provided.
|
|
739
|
+
- `llms.txt`: generated page index for AI tools.
|
|
740
|
+
- `llms-full.txt`: generated expanded page index with source paths, descriptions, and summaries.
|
|
741
|
+
- `_redirects`: generated only when `generatedFiles.netlify` is `true`.
|
|
742
|
+
- `netlify.toml`: generated only when `generatedFiles.netlify` is `true`.
|
|
743
|
+
|
|
744
|
+
Source-first behavior:
|
|
745
|
+
|
|
746
|
+
- If `docs/sitemap.xml` exists, it is copied to `publicDir/sitemap.xml` instead of generated.
|
|
747
|
+
- If `docs/robots.txt` exists, it is copied to `publicDir/robots.txt` instead of generated.
|
|
748
|
+
- If `docs/llms.txt` exists, it is copied to `publicDir/llms.txt` instead of generated.
|
|
749
|
+
- If `docs/llms-full.txt` exists, it is copied to `publicDir/llms-full.txt` instead of generated.
|
|
750
|
+
- If `generatedFiles.netlify` is `true` and `docs/_redirects` exists, it is copied to `publicDir/_redirects` instead of generated.
|
|
751
|
+
- If `generatedFiles.netlify` is `true` and `docs/netlify.toml` exists, it is copied to `publicDir/netlify.toml` instead of generated.
|
|
752
|
+
|
|
753
|
+
Netlify notes:
|
|
754
|
+
|
|
755
|
+
- `_redirects` is treated as Netlify-specific.
|
|
756
|
+
- `netlify.toml` is written to `publicDir`, not the project root.
|
|
757
|
+
- Generated `netlify.toml` defaults to `command = "node build-docs.js"` and `publish = "website"` unless `publicDir` has a different basename.
|
|
758
|
+
|
|
759
|
+
## llms-full.txt Content
|
|
760
|
+
|
|
761
|
+
The generated `llms-full.txt` is derived from discovered Markdown pages.
|
|
762
|
+
|
|
763
|
+
For each page it uses:
|
|
764
|
+
|
|
765
|
+
- `title`: front matter `title`, fallback to `name`, fallback to `Documentation`.
|
|
766
|
+
- `description`: front matter `description`, fallback to stripped Markdown body text.
|
|
767
|
+
- `URL`: file-derived page URL joined with `siteUrl`.
|
|
768
|
+
- `Source`: relative Markdown source path.
|
|
769
|
+
- `summary`: first 320 characters of stripped Markdown body text.
|
|
770
|
+
|
|
771
|
+
The body summary is not the final rendered HTML. It is a lightweight Markdown text extraction used for AI-facing page discovery.
|
|
772
|
+
|
|
773
|
+
## Extension Example
|
|
774
|
+
|
|
775
|
+
Project docs:
|
|
776
|
+
|
|
777
|
+
```txt
|
|
778
|
+
docs/
|
|
779
|
+
index.md
|
|
780
|
+
_template/
|
|
781
|
+
template.config.js
|
|
782
|
+
assets/
|
|
783
|
+
logo.svg
|
|
784
|
+
layouts/
|
|
785
|
+
pricing-cards.js
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
`docs/_template/template.config.js`:
|
|
789
|
+
|
|
790
|
+
```js
|
|
791
|
+
import pricingCards from './layouts/pricing-cards.js'
|
|
792
|
+
|
|
793
|
+
export default {
|
|
794
|
+
markdownLayouts: {
|
|
795
|
+
'pricing-cards': pricingCards,
|
|
796
|
+
},
|
|
797
|
+
scripts: {
|
|
798
|
+
analytics: {
|
|
799
|
+
match: '<main',
|
|
800
|
+
render: () => `<script>console.log('page viewed')</script>`,
|
|
801
|
+
},
|
|
802
|
+
},
|
|
803
|
+
theme: {
|
|
804
|
+
light: {
|
|
805
|
+
'--primary': 'oklch(0.62 0.18 250)',
|
|
806
|
+
},
|
|
807
|
+
dark: {
|
|
808
|
+
'--primary': 'oklch(0.78 0.16 250)',
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
}
|
|
190
812
|
```
|
|
191
813
|
|
|
192
|
-
|
|
814
|
+
`docs/index.md`:
|
|
815
|
+
|
|
816
|
+
```md
|
|
817
|
+
---
|
|
818
|
+
title: Example
|
|
819
|
+
description: Example documentation.
|
|
820
|
+
layout: landing
|
|
821
|
+
---
|
|
822
|
+
|
|
823
|
+
::: layout pricing-cards featured
|
|
824
|
+
|
|
825
|
+
===
|
|
826
|
+
|
|
827
|
+
## Starter
|
|
828
|
+
|
|
829
|
+
For small teams.
|
|
830
|
+
|
|
831
|
+
===
|
|
832
|
+
|
|
833
|
+
## Pro
|
|
193
834
|
|
|
194
|
-
|
|
835
|
+
For growing teams.
|
|
836
|
+
|
|
837
|
+
:::
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
## Build Output
|
|
841
|
+
|
|
842
|
+
Given default options, output is written to:
|
|
843
|
+
|
|
844
|
+
```txt
|
|
845
|
+
website/
|
|
846
|
+
index.html
|
|
847
|
+
guide/
|
|
848
|
+
getting-started.html
|
|
849
|
+
assets/
|
|
850
|
+
stylesheets/
|
|
851
|
+
scripts/
|
|
852
|
+
robots.txt
|
|
853
|
+
sitemap.xml
|
|
854
|
+
llms.txt
|
|
855
|
+
llms-full.txt
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
If `generatedFiles.netlify` is `true`, output also includes:
|
|
859
|
+
|
|
860
|
+
```txt
|
|
861
|
+
website/
|
|
862
|
+
_redirects
|
|
863
|
+
netlify.toml
|
|
864
|
+
```
|
|
195
865
|
|
|
196
|
-
|
|
866
|
+
## Package Scripts
|
|
197
867
|
|
|
198
|
-
|
|
868
|
+
This repo provides:
|
|
199
869
|
|
|
200
|
-
|
|
870
|
+
- `npm run build`: removes `dist`, emits TypeScript declarations, then builds package outputs.
|
|
871
|
+
- `npm run lint`: runs ESLint and Prettier checks.
|
|
872
|
+
- `npm run format`: runs ESLint autofix and Prettier write.
|
|
873
|
+
- `npm test`: runs the Markdown layout parser/renderer tests.
|
|
201
874
|
|
|
202
|
-
|
|
203
|
-
2. Add or modify code in `src/`.
|
|
204
|
-
3. Run `npm run build` to check the build output.
|
|
205
|
-
4. Run `npm run lint` or `npm run format` to keep code style consistent.
|
|
206
|
-
5. Open a pull request describing the change.
|
|
875
|
+
## Development
|
|
207
876
|
|
|
208
|
-
|
|
877
|
+
Install dependencies:
|
|
878
|
+
|
|
879
|
+
```sh
|
|
880
|
+
npm install
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
Run checks:
|
|
884
|
+
|
|
885
|
+
```sh
|
|
886
|
+
npm run lint
|
|
887
|
+
npm test
|
|
888
|
+
npm run build
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
Pack locally:
|
|
892
|
+
|
|
893
|
+
```sh
|
|
894
|
+
npm pack
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
Install the packed artifact into a sibling project:
|
|
898
|
+
|
|
899
|
+
```sh
|
|
900
|
+
npm install ../builder/beforesemicolon-builder-<version>.tgz
|
|
901
|
+
```
|
|
209
902
|
|
|
210
|
-
|
|
903
|
+
## Implementation Notes
|
|
211
904
|
|
|
212
|
-
|
|
905
|
+
- Markdown rendering uses `marked`.
|
|
906
|
+
- Syntax highlighting uses `marked-highlight` and `highlight.js`.
|
|
907
|
+
- Front matter parsing uses `front-matter`.
|
|
908
|
+
- HTML is sanitized with `isomorphic-dompurify`.
|
|
909
|
+
- HTML output is minified with `html-minifier`.
|
|
910
|
+
- CSS output is minified with `clean-css`.
|
|
911
|
+
- JS output copied from docs script folders is minified with `@putout/minify`.
|
|
912
|
+
- Static module and browser builds use `esbuild`.
|
|
213
913
|
|
|
214
|
-
|
|
215
|
-
- Markdown rendering powered by `marked` with a custom renderer and `marked-highlight` plugin using `highlight.js`.
|
|
216
|
-
- Front-matter parsing via `front-matter`.
|
|
217
|
-
- DOM sanitization by `isomorphic-dompurify` and HTML/CSS/JS minification using `html-minifier`, `clean-css`, and `@putout/minify` respectively.
|
|
914
|
+
## License
|
|
218
915
|
|
|
219
|
-
|
|
916
|
+
BSD-3-Clause. See `package.json`.
|