@luckyfishes/markdown-core 0.1.0 → 0.2.1

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 CHANGED
@@ -1,21 +1,406 @@
1
1
  # `@luckyfishes/markdown-core`
2
2
 
3
- Reusable Markdown/MDX parsing and AST transformation utilities extracted from `noname_blog`.
3
+ 一个可复用的 Markdown/MDX 语法解析与 AST 转换核心库,抽离自 `noname_blog`。
4
4
 
5
- This package only handles syntax parsing and AST transforms. It does not ship React components, styles, or framework-specific rendering glue.
5
+ 这个包只负责:
6
6
 
7
- ## Included
7
+ - 解析自定义 Markdown 语法
8
+ - 在 remark / rehype 阶段转换 AST
9
+ - 生成 MDX 组件节点或标准 HTML 节点
10
+ - 提供目录提取等通用能力
8
11
 
9
- - Custom block syntax like `::tabs`, `::details`, and `::ComponentName`
10
- - Inline component syntax like `:badge[...]()` and `:tip[...]()`
11
- - Superscript and subscript transforms
12
- - Code fence to MDX component transforms
13
- - Footnotes heading normalization
14
- - Heading extraction helpers
12
+ 这个包**不负责**:
15
13
 
16
- ## Not Included
14
+ - React / Vue / Svelte 组件实现
15
+ - MDX 运行时渲染
16
+ - Next.js / React Markdown / Contentlayer 等框架集成
17
+ - 样式、交互和主题
18
+ - 业务组件本身的设计与行为
17
19
 
18
- - React component implementations
19
- - MDX runtime rendering
20
- - Next.js integration
21
- - CSS styles
20
+ ## 安装
21
+
22
+ ```bash
23
+ npm install @luckyfishes/markdown-core
24
+ ```
25
+
26
+ ## 能力概览
27
+
28
+ - 自定义块语法:`::tabs`、`::details`、`::ComponentName`
29
+ - 内联组件语法:`:badge[...]()`、`:tip[...]()`
30
+ - 上标 / 下标转换:`sup`、`sub`
31
+ - 特定代码块到组件的转换:`chart`、`mermaid`、`music`
32
+ - 脚注标题修正
33
+ - Markdown 标题提取
34
+
35
+ ## 最常用的接入方式
36
+
37
+ ### 1. 使用预设插件
38
+
39
+ ```ts
40
+ import { createMarkdownPreset } from "@luckyfishes/markdown-core";
41
+
42
+ const markdownPreset = createMarkdownPreset();
43
+
44
+ const { remarkPlugins, rehypePlugins } = markdownPreset;
45
+ ```
46
+
47
+ `createMarkdownPreset()` 默认返回:
48
+
49
+ - `remarkPlugins`
50
+ - `createCustomSyntaxRemarkPlugin()`
51
+ - `remark-gfm`
52
+ - `remarkSuperSub`
53
+ - `rehypePlugins`
54
+ - `createCodeFenceComponentPlugin()`
55
+ - `rehypeFootnotesHeading()`
56
+
57
+ ### 2. 在 MDX 渲染链路中使用
58
+
59
+ 适合 `next-mdx-remote`、`@mdx-js/mdx` 一类真正支持 MDX 组件的方案。
60
+
61
+ ```ts
62
+ import {
63
+ createMarkdownPreset,
64
+ YAML_PROP_PREFIX,
65
+ } from "@luckyfishes/markdown-core";
66
+
67
+ const markdownPreset = createMarkdownPreset();
68
+
69
+ function decodeYamlPropValue(value: unknown): unknown {
70
+ if (typeof value !== "string" || !value.startsWith(YAML_PROP_PREFIX)) {
71
+ return value;
72
+ }
73
+
74
+ return JSON.parse(decodeURIComponent(value.slice(YAML_PROP_PREFIX.length)));
75
+ }
76
+
77
+ function decodeYamlProps<T extends Record<string, unknown>>(props: T): T {
78
+ return Object.fromEntries(
79
+ Object.entries(props).map(([key, value]) => [key, decodeYamlPropValue(value)]),
80
+ ) as T;
81
+ }
82
+ ```
83
+
84
+ 说明:
85
+
86
+ - 自定义块语法里的 YAML 属性,在 AST 中可能会被编码成字符串
87
+ - 运行时渲染组件前,建议使用 `YAML_PROP_PREFIX` 进行一次解码
88
+ - `noname_blog` 就是通过这种方式把数组、对象、布尔值还原回组件 props
89
+
90
+ ### 3. 在 `react-markdown` 中使用
91
+
92
+ 适合“不跑 MDX 编译,只想在 React Markdown 中消费这些语法”的场景。
93
+
94
+ 这时接入方通常需要额外做三件事:
95
+
96
+ - 在 `remarkRehypeOptions.passThrough` 中保留 `mdxJsxFlowElement` 和 `mdxJsxTextElement`
97
+ - 增加一个 rehype 插件,把 MDX JSX 节点转成普通 HAST 元素
98
+ - 在 `rehype-sanitize` 中放行这些自定义标签和属性
99
+
100
+ 参考思路可以看仓库外的接入项目 `luckywiki`:
101
+
102
+ - 把 `Badge` 转成 `mdx-badge`
103
+ - 把 `Tabs` 转成 `mdx-tabs`
104
+ - 未识别的块组件转成兜底标签,例如 `mdx-component-block`
105
+
106
+ ## 默认会生成哪些组件
107
+
108
+ 如果调用 `createMarkdownPreset()` 且不传自定义配置,引用本项目的项目至少需要准备这些组件或等价渲染器。
109
+
110
+ ### 自定义内联 / 块组件
111
+
112
+ 来自 `createCustomSyntaxRemarkPlugin()` 的默认命名:
113
+
114
+ - `Badge`
115
+ - `Tip`
116
+ - `Tabs`
117
+ - `TabsList`
118
+ - `TabsTrigger`
119
+ - `TabsContent`
120
+
121
+ ### 代码块组件
122
+
123
+ 来自 `createCodeFenceComponentPlugin()` 的默认映射:
124
+
125
+ - ```` ```chart ```` -> `ChartBlock`,属性名 `spec`
126
+ - ```` ```mermaid ```` -> `MermaidDiagram`,属性名 `chart`
127
+ - ```` ```music ```` -> `MusicScore`,属性名 `score`
128
+
129
+ ### 业务块组件
130
+
131
+ 凡是写成下面这种语法的内容:
132
+
133
+ ```md
134
+ ::Callout
135
+ title: Example
136
+ ---
137
+ Body
138
+ ::
139
+ ```
140
+
141
+ 都会被转换成同名组件:
142
+
143
+ - `::Callout` -> `Callout`
144
+ - `::Timeline` -> `Timeline`
145
+ - `::Chat` -> `Chat`
146
+
147
+ 也就是说,**接入方需要自行提供这些业务组件**。这个库不会内置任何实现。
148
+
149
+ ### 原生 HTML 元素
150
+
151
+ 以下内容不会要求你补 React 组件,但你的渲染链路需要能正确处理:
152
+
153
+ - `::details` -> `details` / `summary`
154
+ - 上标 / 下标 -> `sup` / `sub`
155
+ - 脚注标题 -> `h1` 或 `h2`
156
+
157
+ ## “引用本项目的项目”需要补充什么
158
+
159
+ 如果另一个项目要依赖这个库,通常至少要补齐下面几类内容。
160
+
161
+ ### 1. 组件实现
162
+
163
+ 最低建议清单:
164
+
165
+ - `Badge`
166
+ - `Tip`
167
+ - `Tabs`
168
+ - `TabsList`
169
+ - `TabsTrigger`
170
+ - `TabsContent`
171
+ - `ChartBlock`
172
+ - `MermaidDiagram`
173
+ - `MusicScore`
174
+
175
+ 如果文档内容里用了额外块组件,还要继续补:
176
+
177
+ - `Callout`
178
+ - `Timeline`
179
+ - `Chat`
180
+ - `LinkCard`
181
+ - `Icon`
182
+ - `Card`
183
+ - 其他 `::ComponentName` 对应的同名组件
184
+
185
+ ### 2. YAML props 解码
186
+
187
+ 当块组件 props 中包含:
188
+
189
+ - 布尔值
190
+ - 数组
191
+ - 对象
192
+ - 数字数组等复杂结构
193
+
194
+ 这些值会被编码后挂到 MDX 节点属性上。接入方渲染组件前,需要使用 `YAML_PROP_PREFIX` 做一次解码。
195
+
196
+ ### 3. 渲染框架集成
197
+
198
+ 接入方要自己决定如何把 AST 接到渲染层,例如:
199
+
200
+ - `next-mdx-remote`
201
+ - `@mdx-js/mdx`
202
+ - `react-markdown`
203
+ - 自己的 unified 管线
204
+
205
+ ### 4. 样式与交互
206
+
207
+ 这个库只产出语义结构,不提供:
208
+
209
+ - Tabs 的切换逻辑
210
+ - Tip 的复制交互
211
+ - Mermaid / Music 的实际渲染
212
+ - 图表绘制
213
+ - 任何 CSS 样式
214
+
215
+ 这些都需要引用方自己补。
216
+
217
+ ## 支持的语法
218
+
219
+ ### 1. `::tabs`
220
+
221
+ ```md
222
+ ::tabs
223
+ tabs:
224
+ - Overview
225
+ - Details
226
+ ---
227
+ 第一屏内容
228
+ ---
229
+ 第二屏内容
230
+ ::
231
+ ```
232
+
233
+ 会生成:
234
+
235
+ - `Tabs`
236
+ - `TabsList`
237
+ - `TabsTrigger`
238
+ - `TabsContent`
239
+
240
+ ### 2. `::details`
241
+
242
+ ```md
243
+ ::details [open] Deep Dive
244
+
245
+ 可折叠内容
246
+
247
+ ::
248
+ ```
249
+
250
+ 会生成:
251
+
252
+ - `details`
253
+ - `summary`
254
+
255
+ ### 3. 通用块组件 `::ComponentName`
256
+
257
+ ```md
258
+ ::Callout
259
+ title: Example
260
+ count:
261
+ - 1
262
+ - 2
263
+ ---
264
+ Paragraph content
265
+ ::
266
+ ```
267
+
268
+ 会生成 `Callout` 组件,并把 YAML 内容作为 props。
269
+
270
+ ### 4. 内联组件
271
+
272
+ ```md
273
+ Hello :badge[Stable](outline)
274
+ Nested :tip[Token](copy)
275
+ ```
276
+
277
+ 默认会生成:
278
+
279
+ - `Badge`
280
+ - `Tip`
281
+
282
+ 其中 `:tip[...]()` 支持的常见参数包括:
283
+
284
+ - `copy`
285
+ - `tip`
286
+ - `tooltip`
287
+ - `message`
288
+ - `value`
289
+
290
+ ### 5. 特定代码块
291
+
292
+ ````md
293
+ ```chart
294
+ { "type": "pie" }
295
+ ```
296
+ ````
297
+
298
+ ````md
299
+ ```mermaid
300
+ flowchart LR
301
+ A --> B
302
+ ```
303
+ ````
304
+
305
+ ````md
306
+ ```music
307
+ C D E F G
308
+ ```
309
+ ````
310
+
311
+ 默认分别转换为:
312
+
313
+ - `ChartBlock`
314
+ - `MermaidDiagram`
315
+ - `MusicScore`
316
+
317
+ ## 自定义配置
318
+
319
+ ### 覆盖自定义组件名
320
+
321
+ ```ts
322
+ import { createMarkdownPreset } from "@luckyfishes/markdown-core";
323
+
324
+ const preset = createMarkdownPreset({
325
+ customSyntax: {
326
+ componentNames: {
327
+ badge: "Pill",
328
+ },
329
+ },
330
+ });
331
+ ```
332
+
333
+ 这样 `:badge[New](soft)` 会生成 `Pill` 而不是 `Badge`。
334
+
335
+ ### 覆盖代码块映射
336
+
337
+ ```ts
338
+ import {
339
+ createMarkdownPreset,
340
+ DEFAULT_CODE_FENCE_COMPONENT_MAPPINGS,
341
+ } from "@luckyfishes/markdown-core";
342
+
343
+ const preset = createMarkdownPreset({
344
+ codeFenceMappings: {
345
+ ...DEFAULT_CODE_FENCE_COMPONENT_MAPPINGS,
346
+ mermaid: {
347
+ componentName: "Diagram",
348
+ propName: "value",
349
+ },
350
+ },
351
+ });
352
+ ```
353
+
354
+ ## 标题提取
355
+
356
+ ```ts
357
+ import { extractHeadings } from "@luckyfishes/markdown-core";
358
+
359
+ const headings = extractHeadings(markdown);
360
+ ```
361
+
362
+ 返回值示例:
363
+
364
+ ```ts
365
+ [
366
+ { depth: 1, text: "Title", id: "title" },
367
+ { depth: 2, text: "Section", id: "section" },
368
+ ]
369
+ ```
370
+
371
+ 如果文档里包含脚注,还会自动补一个脚注标题。
372
+
373
+ ## 适合谁用
374
+
375
+ 适合这些场景:
376
+
377
+ - 你已经有自己的 Markdown/MDX 渲染系统
378
+ - 你想复用一套自定义 Markdown 语法
379
+ - 你愿意自己实现渲染组件、样式和交互
380
+
381
+ 不适合这些场景:
382
+
383
+ - 你希望安装后开箱即用地渲染完整页面
384
+ - 你不想维护任何自定义组件
385
+ - 你需要一个带 UI 的 Markdown 渲染器
386
+
387
+ ## 导出 API
388
+
389
+ - `createMarkdownPreset`
390
+ - `createCustomSyntaxRemarkPlugin`
391
+ - `createCodeFenceComponentPlugin`
392
+ - `rehypeFootnotesHeading`
393
+ - `remarkSuperSub`
394
+ - `extractHeadings`
395
+ - `YAML_PROP_PREFIX`
396
+ - `DEFAULT_CUSTOM_SYNTAX_COMPONENT_NAMES`
397
+ - `DEFAULT_CODE_FENCE_COMPONENT_MAPPINGS`
398
+ - `FOOTNOTES_HEADING_ID`
399
+ - `FOOTNOTES_HEADING_TEXT`
400
+
401
+ ## 参考
402
+
403
+ 这个库当前主要在以下项目中被消费:
404
+
405
+ - `noname_blog`:MDX 编译链路接入,运行时解码 props 并挂载真实 React 组件
406
+ - `luckywiki`:`react-markdown` 链路接入,把 MDX JSX 节点转换成自定义标签后再渲染