Skip to content

Commit c2b3a82

Browse files
committed
docs: add @amamo/mdx documentation site with @amamo/doctrine
Bootstrap apps/docs with @amamo/doctrine to ship a public MDX documentation site. Eight pages in English and 简体中文 cover installation, configuration, the compiler API, the Vite 8 and Next 16 adapters, the security model, and the supported native targets. - apps/docs: doctrine-based site, build wired through pnpm filter - root package.json: add docs:dev / docs:build and check:docs, the latter appended to the existing check pipeline - pnpm-workspace.yaml: include apps/* alongside the existing allowBuilds - .github/workflows/docs.yml: workflow_dispatch deploy to GitHub Pages, mirroring ../verso (configure-pages -> build -> deploy-pages) - ignore **/dist/**, **/.doctrine/**, **/node_modules/**, **/.DS_Store, and docs/superpowers/ in git, oxfmt, and oxlint so build artifacts and internal plans stay out of the repo
1 parent 6b6eb82 commit c2b3a82

26 files changed

Lines changed: 2508 additions & 10 deletions

.github/workflows/docs.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Docs
2+
3+
on:
4+
workflow_dispatch:
5+
6+
permissions:
7+
contents: read
8+
pages: write
9+
id-token: write
10+
11+
concurrency:
12+
group: pages
13+
cancel-in-progress: true
14+
15+
jobs:
16+
build:
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 15
19+
20+
steps:
21+
- uses: actions/checkout@v7
22+
23+
- uses: pnpm/action-setup@v6
24+
25+
- uses: actions/setup-node@v7
26+
with:
27+
node-version: 24
28+
cache: pnpm
29+
30+
- run: pnpm install --frozen-lockfile
31+
32+
- name: Configure GitHub Pages
33+
id: pages
34+
uses: actions/configure-pages@v6
35+
36+
- name: Build static documentation
37+
run: pnpm --filter @amamo/mdx-docs build
38+
env:
39+
DOCS_SITE_URL: ${{ format('{0}/', steps.pages.outputs.base_url) }}
40+
41+
- name: Upload documentation
42+
uses: actions/upload-pages-artifact@v5
43+
with:
44+
path: apps/docs/dist
45+
46+
deploy:
47+
runs-on: ubuntu-latest
48+
needs: build
49+
timeout-minutes: 10
50+
51+
steps:
52+
- name: Deploy to GitHub Pages
53+
id: deployment
54+
uses: actions/deploy-pages@v5

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
/dist/
22
/node_modules/
3+
**/node_modules/
34
/target/
45
**/.amamo-mdx/
56
**/.next/
7+
**/.doctrine/
8+
**/.DS_Store
9+
**/dist/
10+
docs/superpowers/
611
fixtures/next/out/
712
/*.node
813
/native.d.ts

.oxfmtrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "./node_modules/oxfmt/configuration_schema.json",
3-
"ignorePatterns": ["Cargo.lock", "pnpm-lock.yaml"],
3+
"ignorePatterns": ["Cargo.lock", "pnpm-lock.yaml", "**/dist/**", "**/.doctrine/**"],
44
"semi": false,
55
"singleQuote": true
66
}

apps/docs/docs/compiler-api.mdx

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
title: Compiler API
3+
description: Drive the compiler directly with createCompiler, build, transform, remove, and dispose.
4+
order: 4
5+
---
6+
7+
The Vite and Next adapters both run on top of the same `ICompiler`. The root API exposes that
8+
interface so a host application can drive the compiler directly — for example, a build script
9+
that runs before Vite, a test harness, or a static site generator.
10+
11+
## Construction
12+
13+
```ts
14+
import { createCompiler } from '@amamo/mdx'
15+
import config from './amamo.config.js'
16+
17+
const compiler = await createCompiler(config)
18+
```
19+
20+
`createCompiler` is async because it loads the native binding and the configured highlighter. The
21+
config is normalized on construction; later edits are not picked up.
22+
23+
The returned object implements `ICompiler`:
24+
25+
```ts
26+
interface ICompiler {
27+
build(): Promise<IBuildResult>
28+
dispose(): Promise<void>
29+
remove(file: string): Promise<number>
30+
transform(file: string): Promise<ITransformResult>
31+
}
32+
```
33+
34+
## build
35+
36+
```ts
37+
const result = await compiler.build()
38+
```
39+
40+
Discovers every collection, parses and validates every record, renders code blocks, prunes the
41+
cache, and writes the generated module, the private loader index, and every configured manifest.
42+
The compiler coalesces concurrent calls, so calling `build()` twice in quick succession is safe.
43+
44+
```ts
45+
interface IBuildResult {
46+
cached: number
47+
compiled: number
48+
discovered: number
49+
outputsWritten: number
50+
}
51+
```
52+
53+
- `discovered` is the number of MDX files found across all collections.
54+
- `compiled` is the number of files that produced fresh cache records.
55+
- `cached` is `discovered - compiled`.
56+
- `outputsWritten` is the number of generated files actually written; the rest were byte-for-byte
57+
identical to the existing output and left untouched.
58+
59+
`build()` is a superset of the work the adapters do at startup. Calling it explicitly is useful
60+
when the generated module is consumed by code that is not the Vite or Next adapter.
61+
62+
## transform
63+
64+
```ts
65+
const result = await compiler.transform('content/posts/hello.mdx')
66+
```
67+
68+
Re-parses a single file, validates its frontmatter, and writes any new generated output that
69+
depends on it. The result is the freshly compiled record and a `cached` flag that mirrors the
70+
cache behavior of the underlying batch.
71+
72+
```ts
73+
interface ITransformResult {
74+
cached: boolean
75+
code: string
76+
map: null
77+
outputsWritten: number
78+
record: IDocumentRecord
79+
}
80+
```
81+
82+
`code` is the JavaScript source the host application should treat as the document's module body.
83+
`map` is currently always `null`; the compiler reserves the field for a future source map. The
84+
full `record` exposes the parsed frontmatter, the derived fields, the diagnostics, the cache key,
85+
and the SHA-256 hash of the input bytes.
86+
87+
`transform` rejects with an `AmamoMdxError` whose `diagnostics` field lists every problem the
88+
compiler found. Frontmatter schema violations, missing media, and unknown code-block languages all
89+
surface as diagnostics.
90+
91+
## remove
92+
93+
```ts
94+
const removed = await compiler.remove('content/posts/deleted.mdx')
95+
```
96+
97+
Removes a file from the cache and rewrites any generated output that referenced it. Returns the
98+
number of generated files actually rewritten. Calling `remove` on a path that is not part of any
99+
collection is a no-op and returns `0`.
100+
101+
`remove` is the delete-side counterpart of `transform`. The Vite and Next adapters wire it to the
102+
underlying file system watcher.
103+
104+
## dispose
105+
106+
```ts
107+
await compiler.dispose()
108+
```
109+
110+
Releases the highlighter and any open file handles. After `dispose`, the compiler is unusable;
111+
calling any other method rejects. The compiler is also a process-wide singleton from the adapter's
112+
point of view, so do not share a disposed instance across requests.
113+
114+
## Errors and diagnostics
115+
116+
Most failures are not thrown as JavaScript exceptions; they are returned as `IDiagnostic[]`
117+
inside an `AmamoMdxError`:
118+
119+
```ts
120+
class AmamoMdxError extends Error {
121+
readonly diagnostics: IDiagnostic[]
122+
}
123+
124+
interface IDiagnostic {
125+
code: string
126+
file?: string
127+
hint?: string
128+
message: string
129+
range?: { start; end }
130+
severity: 'error' | 'warning'
131+
}
132+
```
133+
134+
The compiler's own plumbing rejects the surrounding `Promise`; it is the caller's job to decide
135+
whether to surface a single message, a list of file:line diagnostics, or to fail the build.
136+
137+
## Generated module
138+
139+
After `build()` the generated module lives at `<generatedDirectory>/collections.mjs`. It exports
140+
a tree of named records that the host application can `import` directly. The companion
141+
`collections.d.mts` provides the same shape to TypeScript.
142+
143+
The private loader index at `<generatedDirectory>/index.json` exposes the same records to the Vite
144+
and Next adapters without forcing them through the public module — they need a key lookup, not
145+
the full bundle.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
---
2+
title: 编译器 API
3+
description: 用 createCompiler、build、transform、remove、dispose 直接驱动编译器。
4+
order: 4
5+
---
6+
7+
Vite 和 Next 适配器都跑在同一份 `ICompiler` 之上。根 API 把这个接口直接暴露出来,让宿主
8+
应用可以自己驱动编译器 —— 比如在 Vite 之前先跑一遍的构建脚本、测试夹具,或者一个静态站点
9+
生成器。
10+
11+
## 构造
12+
13+
```ts
14+
import { createCompiler } from '@amamo/mdx'
15+
import config from './amamo.config.js'
16+
17+
const compiler = await createCompiler(config)
18+
```
19+
20+
`createCompiler` 是异步的,因为要加载原生绑定和配置的高亮器。配置在构造时被归一化,之后再
21+
改不会被感知。
22+
23+
返回的对象实现 `ICompiler`:
24+
25+
```ts
26+
interface ICompiler {
27+
build(): Promise<IBuildResult>
28+
dispose(): Promise<void>
29+
remove(file: string): Promise<number>
30+
transform(file: string): Promise<ITransformResult>
31+
}
32+
```
33+
34+
## build
35+
36+
```ts
37+
const result = await compiler.build()
38+
```
39+
40+
发现所有集合,解析并校验每条记录,渲染代码块,清理缓存,然后写入生成模块、私有加载器索引
41+
以及每个配置的 manifest。编译器会合并并发调用,所以快速连调 `build()` 两次是安全的。
42+
43+
```ts
44+
interface IBuildResult {
45+
cached: number
46+
compiled: number
47+
discovered: number
48+
outputsWritten: number
49+
}
50+
```
51+
52+
- `discovered` 是所有集合中发现的 MDX 文件数。
53+
- `compiled` 是产生了新缓存记录的文件数。
54+
- `cached` 等于 `discovered - compiled`
55+
- `outputsWritten` 是实际写入的生成文件数;其余与现有产物字节一致,未触碰。
56+
57+
`build()` 涵盖了适配器在启动时做的工作的完整超集。在生成的模块被非 Vite / 非 Next 的
58+
代码消费时,显式调它很有用。
59+
60+
## transform
61+
62+
```ts
63+
const result = await compiler.transform('content/posts/hello.mdx')
64+
```
65+
66+
重新解析单个文件,校验其 frontmatter,并写入任何依赖它的生成产物。结果是刚编译的记录,以及
67+
一个反映底层批处理缓存行为的 `cached` 标记。
68+
69+
```ts
70+
interface ITransformResult {
71+
cached: boolean
72+
code: string
73+
map: null
74+
outputsWritten: number
75+
record: IDocumentRecord
76+
}
77+
```
78+
79+
`code` 是宿主应用应视为该文档模块体的 JavaScript 源码。`map` 当前总是 `null`,编译器为
80+
未来的 source map 保留了这个字段。`record` 暴露了解析后的 frontmatter、derived 字段、
81+
诊断信息、缓存键以及源字节的 SHA-256 哈希。
82+
83+
`transform` 在出错时以 `AmamoMdxError` 拒绝,`diagnostics` 字段列出编译器发现的所有问题。
84+
frontmatter schema 违规、媒体缺失、未知代码块语言都以诊断形式出现。
85+
86+
## remove
87+
88+
```ts
89+
const removed = await compiler.remove('content/posts/deleted.mdx')
90+
```
91+
92+
从缓存中移除一个文件,并重写任何引用过它的生成产物。返回实际被重写的生成文件数。对不属于
93+
任何集合的路径调用 `remove` 是 no-op,返回 `0`
94+
95+
`remove``transform` 在删除侧的对应操作。Vite 和 Next 适配器把它接到底层文件系统的
96+
watcher 上。
97+
98+
## dispose
99+
100+
```ts
101+
await compiler.dispose()
102+
```
103+
104+
释放高亮器以及任何打开的文件句柄。`dispose` 之后编译器不可再用;再调其它方法会被拒绝。
105+
从适配器的视角看,编译器是进程级单例,所以不要把一个已 dispose 的实例跨请求共享。
106+
107+
## 错误与诊断
108+
109+
大多数失败不以 JavaScript 异常抛出,而是作为 `IDiagnostic[]` 放在 `AmamoMdxError` 里返回:
110+
111+
```ts
112+
class AmamoMdxError extends Error {
113+
readonly diagnostics: IDiagnostic[]
114+
}
115+
116+
interface IDiagnostic {
117+
code: string
118+
file?: string
119+
hint?: string
120+
message: string
121+
range?: { start; end }
122+
severity: 'error' | 'warning'
123+
}
124+
```
125+
126+
编译器自身的内部管道会让外层 `Promise` 拒绝;具体怎么呈现给用户由调用方决定 —— 是单条
127+
消息,还是 `file:line` 形式的诊断列表,或直接让构建失败。
128+
129+
## 生成模块
130+
131+
`build()` 之后,生成模块位于 `<generatedDirectory>/collections.mjs`。它导出一棵具名
132+
记录的树,宿主应用可以直接 `import`。配套的 `collections.d.mts` 给 TypeScript 提供了同样
133+
的形状。
134+
135+
私有加载器索引位于 `<generatedDirectory>/index.json`,Vite 和 Next 适配器用它来按路径
136+
查找已编译的记录,而不必走公开模块 —— 它们需要的是 key 查找,不是整个 bundle。

0 commit comments

Comments
 (0)