@docubook/core 1.0.0
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 +131 -0
- package/package.json +44 -0
- package/src/compile.ts +131 -0
- package/src/content.ts +129 -0
- package/src/extract.ts +44 -0
- package/src/index.ts +23 -0
- package/src/types.ts +13 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @docubook/core
|
|
2
|
+
|
|
3
|
+
Shared MDX compile pipeline and markdown utilities for DocuBook.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Centralized MDX Pipeline** - One shared compile flow for all DocuBook projects
|
|
8
|
+
- **Managed Remark/Rehype Plugins** - Core markdown plugins are maintained by DocuBook author
|
|
9
|
+
- **Content-First Workflow** - Users can focus on docs content instead of plugin maintenance
|
|
10
|
+
- **Utility Helpers** - Frontmatter extraction, TOC extraction, and slug helpers included
|
|
11
|
+
- **Consistent Behavior** - Same markdown rendering behavior across apps and templates
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Node.js ecosystem:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @docubook/core
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @docubook/core
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
yarn add @docubook/core
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Bun (independent runtime/package manager):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bun add @docubook/core
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Dependency Management Policy
|
|
36
|
+
|
|
37
|
+
Dependencies required for markdown processing are managed in this package and updated by the DocuBook author.
|
|
38
|
+
|
|
39
|
+
This means app-level users should focus on content and integration. Plugin upgrades, compatibility checks, and pipeline maintenance are handled centrally by DocuBook.
|
|
40
|
+
|
|
41
|
+
### Managed Markdown Dependencies
|
|
42
|
+
|
|
43
|
+
- gray-matter
|
|
44
|
+
- rehype-autolink-headings
|
|
45
|
+
- rehype-code-titles
|
|
46
|
+
- rehype-prism-plus
|
|
47
|
+
- rehype-slug
|
|
48
|
+
- remark-gfm
|
|
49
|
+
- unist-util-visit
|
|
50
|
+
|
|
51
|
+
The most important part is the `remark` and `rehype` plugin stack, which is intentionally owned by this package to avoid dependency drift across apps.
|
|
52
|
+
|
|
53
|
+
## Why This Matters
|
|
54
|
+
|
|
55
|
+
- Consistent behavior across all DocuBook-based projects
|
|
56
|
+
- Easier maintenance and safer upgrades
|
|
57
|
+
- Less dependency duplication in app-level package.json files
|
|
58
|
+
- Faster onboarding for users who only need to write docs
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import {
|
|
64
|
+
parseMdx,
|
|
65
|
+
extractFrontmatter,
|
|
66
|
+
extractTocsFromRawMdx,
|
|
67
|
+
} from "@docubook/core";
|
|
68
|
+
|
|
69
|
+
const raw = `---\ntitle: Intro\n---\n\n## Hello`;
|
|
70
|
+
|
|
71
|
+
const frontmatter = extractFrontmatter<{ title: string }>(raw);
|
|
72
|
+
const toc = extractTocsFromRawMdx(raw);
|
|
73
|
+
const compiled = await parseMdx<{ title: string }>(raw);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### File-Based Pipeline (Recommended)
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import {
|
|
80
|
+
createMdxContentService,
|
|
81
|
+
readMdxFileBySlug,
|
|
82
|
+
parseMdxFile,
|
|
83
|
+
compileParsedMdxFile,
|
|
84
|
+
} from "@docubook/core";
|
|
85
|
+
|
|
86
|
+
type Frontmatter = {
|
|
87
|
+
title: string;
|
|
88
|
+
description: string;
|
|
89
|
+
image: string;
|
|
90
|
+
date: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const raw = await readMdxFileBySlug("getting-started/introduction");
|
|
94
|
+
const parsed = parseMdxFile<Frontmatter>(raw);
|
|
95
|
+
const compiled = await compileParsedMdxFile(parsed, {
|
|
96
|
+
components: {
|
|
97
|
+
// your mdx components
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const docsService = createMdxContentService<Frontmatter>({
|
|
102
|
+
parseOptions: {
|
|
103
|
+
components: {
|
|
104
|
+
// your mdx components
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const doc = await docsService.getCompiledForSlug("getting-started/introduction");
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## API Overview
|
|
113
|
+
|
|
114
|
+
- `parseMdx` - Compiles raw MDX with default or custom plugin options
|
|
115
|
+
- `createMdxContentService` - Unified high-level API for read/parse/compile/getters
|
|
116
|
+
- `readMdxFileBySlug` - Reads `slug.mdx` or `slug/index.mdx` from docs directory
|
|
117
|
+
- `parseMdxFile` - Converts raw file result into `frontmatter` + `tocs` + `content`
|
|
118
|
+
- `compileParsedMdxFile` - Compiles a parsed document while preserving metadata
|
|
119
|
+
- `extractFrontmatter` - Parses frontmatter from raw markdown/MDX
|
|
120
|
+
- `extractTocsFromRawMdx` - Extracts headings for TOC generation
|
|
121
|
+
- `sluggify` - Converts heading text into URL-friendly slugs
|
|
122
|
+
|
|
123
|
+
## Notes
|
|
124
|
+
|
|
125
|
+
If your app uses `next-mdx-remote` directly for rendering in custom components, keep that direct dependency in the app.
|
|
126
|
+
|
|
127
|
+
For compile pipeline plugins (especially `remark` and `rehype` plugins), rely on this package and avoid re-declaring them at app level unless you have a specific override requirement.
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@docubook/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared MDX compile pipeline and markdown utilities for DocuBook",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "echo 'core uses source exports'"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"docubook",
|
|
15
|
+
"documentation",
|
|
16
|
+
"docs framework",
|
|
17
|
+
"mdx compile",
|
|
18
|
+
"markdown utilities"
|
|
19
|
+
],
|
|
20
|
+
"homepage": "https://docubook.pro/",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/DocuBook/docubook"
|
|
24
|
+
},
|
|
25
|
+
"author": "wildan.nrs",
|
|
26
|
+
"author-url": "https://wildan.dev",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"gray-matter": "^4.0.3",
|
|
30
|
+
"next-mdx-remote": "^6.0.0",
|
|
31
|
+
"rehype-autolink-headings": "^7.1.0",
|
|
32
|
+
"rehype-code-titles": "^1.2.1",
|
|
33
|
+
"rehype-prism-plus": "^2.0.2",
|
|
34
|
+
"rehype-slug": "^6.0.0",
|
|
35
|
+
"remark-gfm": "^4.0.1",
|
|
36
|
+
"unist-util-visit": "^5.1.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.19.37",
|
|
40
|
+
"@types/react": "19.2.8",
|
|
41
|
+
"@types/unist": "^3.0.3",
|
|
42
|
+
"typescript": "^5.9.3"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/compile.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { compileMDX } from "next-mdx-remote/rsc";
|
|
2
|
+
import type { Node, Parent } from "unist";
|
|
3
|
+
import { visit } from "unist-util-visit";
|
|
4
|
+
import remarkGfm from "remark-gfm";
|
|
5
|
+
import rehypePrism from "rehype-prism-plus";
|
|
6
|
+
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
|
7
|
+
import rehypeSlug from "rehype-slug";
|
|
8
|
+
import rehypeCodeTitles from "rehype-code-titles";
|
|
9
|
+
import type { MdxCompileResult } from "./types";
|
|
10
|
+
|
|
11
|
+
interface Element extends Node {
|
|
12
|
+
type: string;
|
|
13
|
+
tagName?: string;
|
|
14
|
+
properties?: Record<string, unknown> & {
|
|
15
|
+
className?: string[];
|
|
16
|
+
raw?: string;
|
|
17
|
+
};
|
|
18
|
+
children?: Node[];
|
|
19
|
+
raw?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface TextNode extends Node {
|
|
23
|
+
type: "text";
|
|
24
|
+
value: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type CompileMdxInput = Parameters<typeof compileMDX<Record<string, unknown>>>[0];
|
|
28
|
+
type CompileMdxOptions = NonNullable<CompileMdxInput["options"]>;
|
|
29
|
+
type CompilerMdxOptions = NonNullable<CompileMdxOptions["mdxOptions"]>;
|
|
30
|
+
|
|
31
|
+
export type ParseMdxOptions = {
|
|
32
|
+
components?: CompileMdxInput["components"];
|
|
33
|
+
rehypePlugins?: CompilerMdxOptions["rehypePlugins"];
|
|
34
|
+
remarkPlugins?: CompilerMdxOptions["remarkPlugins"];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const handleCodeTitles = () => (tree: Node) => {
|
|
38
|
+
visit(tree, "element", (node: Element, index: number | null, parent: Parent | null) => {
|
|
39
|
+
if (!parent || index === null || node.tagName !== "div") {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const isTitleDiv = node.properties?.className?.includes("rehype-code-title");
|
|
44
|
+
if (!isTitleDiv) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let nextElement: Element | null = null;
|
|
49
|
+
for (let i = index + 1; i < parent.children.length; i++) {
|
|
50
|
+
const sibling = parent.children[i];
|
|
51
|
+
if (sibling.type === "element") {
|
|
52
|
+
nextElement = sibling as Element;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (nextElement?.tagName === "pre") {
|
|
58
|
+
const titleNode = node.children?.[0] as TextNode;
|
|
59
|
+
if (titleNode?.type === "text") {
|
|
60
|
+
if (!nextElement.properties) {
|
|
61
|
+
nextElement.properties = {};
|
|
62
|
+
}
|
|
63
|
+
nextElement.properties["data-title"] = titleNode.value;
|
|
64
|
+
parent.children.splice(index, 1);
|
|
65
|
+
return index;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const preProcess = () => (tree: Node) => {
|
|
72
|
+
visit(tree, (node: Node) => {
|
|
73
|
+
const element = node as Element;
|
|
74
|
+
if (element?.type === "element" && element?.tagName === "pre" && element.children) {
|
|
75
|
+
const [codeEl] = element.children as Element[];
|
|
76
|
+
if (codeEl.tagName !== "code" || !codeEl.children?.[0]) return;
|
|
77
|
+
|
|
78
|
+
const textNode = codeEl.children[0] as TextNode;
|
|
79
|
+
if (textNode.type === "text" && textNode.value) {
|
|
80
|
+
element.raw = textNode.value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const postProcess = () => (tree: Node) => {
|
|
87
|
+
visit(tree, "element", (node: Node) => {
|
|
88
|
+
const element = node as Element;
|
|
89
|
+
if (element?.type === "element" && element?.tagName === "pre") {
|
|
90
|
+
if (element.properties && element.raw) {
|
|
91
|
+
element.properties.raw = element.raw;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export function createDefaultRehypePlugins(): unknown[] {
|
|
98
|
+
return [
|
|
99
|
+
preProcess,
|
|
100
|
+
rehypeCodeTitles,
|
|
101
|
+
handleCodeTitles,
|
|
102
|
+
rehypePrism,
|
|
103
|
+
rehypeSlug,
|
|
104
|
+
rehypeAutolinkHeadings,
|
|
105
|
+
postProcess,
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function createDefaultRemarkPlugins(): unknown[] {
|
|
110
|
+
return [remarkGfm];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function parseMdx<Frontmatter>(
|
|
114
|
+
rawMdx: string,
|
|
115
|
+
options: ParseMdxOptions = {}
|
|
116
|
+
): Promise<MdxCompileResult<Frontmatter>> {
|
|
117
|
+
const rehypePlugins = options.rehypePlugins ?? (createDefaultRehypePlugins() as CompilerMdxOptions["rehypePlugins"]);
|
|
118
|
+
const remarkPlugins = options.remarkPlugins ?? (createDefaultRemarkPlugins() as CompilerMdxOptions["remarkPlugins"]);
|
|
119
|
+
|
|
120
|
+
return await compileMDX<Frontmatter>({
|
|
121
|
+
source: rawMdx,
|
|
122
|
+
options: {
|
|
123
|
+
parseFrontmatter: true,
|
|
124
|
+
mdxOptions: {
|
|
125
|
+
rehypePlugins,
|
|
126
|
+
remarkPlugins,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
components: options.components,
|
|
130
|
+
});
|
|
131
|
+
}
|
package/src/content.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import { extractFrontmatter, extractTocsFromRawMdx } from "./extract";
|
|
4
|
+
import { parseMdx, type ParseMdxOptions } from "./compile";
|
|
5
|
+
import type { MdxCompileResult, TocItem } from "./types";
|
|
6
|
+
|
|
7
|
+
type CacheFn = <T extends (...args: any[]) => any>(fn: T) => T;
|
|
8
|
+
|
|
9
|
+
export type ReadMdxFileResult = {
|
|
10
|
+
content: string;
|
|
11
|
+
filePath: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ParsedMdxFile<Frontmatter, T extends TocItem = TocItem> = {
|
|
15
|
+
content: string;
|
|
16
|
+
filePath: string;
|
|
17
|
+
frontmatter: Frontmatter;
|
|
18
|
+
tocs: T[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type CompiledMdxFile<Frontmatter, T extends TocItem = TocItem> = MdxCompileResult<Frontmatter> & {
|
|
22
|
+
filePath: string;
|
|
23
|
+
tocs: T[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ReadMdxBySlugOptions = {
|
|
27
|
+
rootDir?: string;
|
|
28
|
+
docsDir?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export async function readMdxFileBySlug(slug: string, options: ReadMdxBySlugOptions = {}): Promise<ReadMdxFileResult> {
|
|
32
|
+
const docsDir = options.docsDir ?? "docs";
|
|
33
|
+
// Keep file-system path operations ignored by Turbopack tracing.
|
|
34
|
+
// The runtime path is constrained to docsDir and slug candidates below.
|
|
35
|
+
const docsRoot = options.rootDir
|
|
36
|
+
? path.join(/*turbopackIgnore: true*/ options.rootDir, docsDir)
|
|
37
|
+
: path.join(/*turbopackIgnore: true*/ process.cwd(), docsDir);
|
|
38
|
+
const paths = [
|
|
39
|
+
path.join(/*turbopackIgnore: true*/ docsRoot, `${slug}.mdx`),
|
|
40
|
+
path.join(/*turbopackIgnore: true*/ docsRoot, slug, "index.mdx"),
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const p of paths) {
|
|
44
|
+
try {
|
|
45
|
+
const content = await fs.readFile(/*turbopackIgnore: true*/ p, "utf-8");
|
|
46
|
+
return {
|
|
47
|
+
content,
|
|
48
|
+
filePath: `${docsDir}/${path.relative(/*turbopackIgnore: true*/ docsRoot, p)}`,
|
|
49
|
+
};
|
|
50
|
+
} catch {
|
|
51
|
+
// ignore and try next candidate
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
throw new Error(`Could not find mdx file for slug: ${slug}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type ParseMdxFileOptions<T extends TocItem> = {
|
|
59
|
+
tocsExtractor?: (rawMdx: string) => T[];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export function parseMdxFile<Frontmatter, T extends TocItem = TocItem>(
|
|
63
|
+
raw: ReadMdxFileResult,
|
|
64
|
+
options: ParseMdxFileOptions<T> = {}
|
|
65
|
+
): ParsedMdxFile<Frontmatter, T> {
|
|
66
|
+
const tocsExtractor = options.tocsExtractor ?? ((mdx) => extractTocsFromRawMdx(mdx) as T[]);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
content: raw.content,
|
|
70
|
+
filePath: raw.filePath,
|
|
71
|
+
frontmatter: extractFrontmatter<Frontmatter>(raw.content),
|
|
72
|
+
tocs: tocsExtractor(raw.content),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function compileParsedMdxFile<Frontmatter, T extends TocItem = TocItem>(
|
|
77
|
+
parsed: ParsedMdxFile<Frontmatter, T>,
|
|
78
|
+
options: ParseMdxOptions = {}
|
|
79
|
+
): Promise<CompiledMdxFile<Frontmatter, T>> {
|
|
80
|
+
const compiled = await parseMdx<Frontmatter>(parsed.content, options);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
...compiled,
|
|
84
|
+
frontmatter: parsed.frontmatter,
|
|
85
|
+
filePath: parsed.filePath,
|
|
86
|
+
tocs: parsed.tocs,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type CreateMdxContentServiceOptions<Frontmatter, T extends TocItem = TocItem> = {
|
|
91
|
+
parseOptions?: ParseMdxOptions;
|
|
92
|
+
readOptions?: ReadMdxBySlugOptions;
|
|
93
|
+
tocsExtractor?: (rawMdx: string) => T[];
|
|
94
|
+
cacheFn?: CacheFn;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export function createMdxContentService<Frontmatter, T extends TocItem = TocItem>(
|
|
98
|
+
options: CreateMdxContentServiceOptions<Frontmatter, T> = {}
|
|
99
|
+
) {
|
|
100
|
+
const identityCache: CacheFn = (fn) => fn;
|
|
101
|
+
const cacheFn = options.cacheFn ?? identityCache;
|
|
102
|
+
|
|
103
|
+
const getParsedForSlug = cacheFn(async (slug: string): Promise<ParsedMdxFile<Frontmatter, T>> => {
|
|
104
|
+
const raw = await readMdxFileBySlug(slug, options.readOptions);
|
|
105
|
+
return parseMdxFile<Frontmatter, T>(raw, { tocsExtractor: options.tocsExtractor });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const getCompiledForSlug = cacheFn(async (slug: string): Promise<CompiledMdxFile<Frontmatter, T>> => {
|
|
109
|
+
const parsed = await getParsedForSlug(slug);
|
|
110
|
+
return compileParsedMdxFile<Frontmatter, T>(parsed, options.parseOptions);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
async function getFrontmatterForSlug(slug: string): Promise<Frontmatter> {
|
|
114
|
+
const parsed = await getParsedForSlug(slug);
|
|
115
|
+
return parsed.frontmatter;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function getTocsForSlug(slug: string): Promise<T[]> {
|
|
119
|
+
const parsed = await getParsedForSlug(slug);
|
|
120
|
+
return parsed.tocs;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
getParsedForSlug,
|
|
125
|
+
getCompiledForSlug,
|
|
126
|
+
getFrontmatterForSlug,
|
|
127
|
+
getTocsForSlug,
|
|
128
|
+
};
|
|
129
|
+
}
|
package/src/extract.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import matter from "gray-matter";
|
|
2
|
+
import type { TocItem } from "./types";
|
|
3
|
+
|
|
4
|
+
export function sluggify(text: string): string {
|
|
5
|
+
const slug = text.toLowerCase().replace(/\s+/g, "-");
|
|
6
|
+
return slug.replace(/[^a-z0-9-]/g, "");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function extractTocsFromRawMdx(rawMdx: string): TocItem[] {
|
|
10
|
+
// Match code blocks, markdown headings, and <Release version="x.y.z" />.
|
|
11
|
+
const combinedRegex = /(```[\s\S]*?```)|^(#{2,4})\s(.+)$|<Release[^>]*version="([^"]+)"/gm;
|
|
12
|
+
const extractedHeadings: TocItem[] = [];
|
|
13
|
+
|
|
14
|
+
let match: RegExpExecArray | null;
|
|
15
|
+
while ((match = combinedRegex.exec(rawMdx)) !== null) {
|
|
16
|
+
if (match[1]) continue;
|
|
17
|
+
|
|
18
|
+
if (match[2]) {
|
|
19
|
+
const headingLevel = match[2].length;
|
|
20
|
+
const headingText = match[3].trim();
|
|
21
|
+
extractedHeadings.push({
|
|
22
|
+
level: headingLevel,
|
|
23
|
+
text: headingText,
|
|
24
|
+
href: `#${sluggify(headingText)}`,
|
|
25
|
+
});
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (match[4]) {
|
|
30
|
+
const version = match[4];
|
|
31
|
+
extractedHeadings.push({
|
|
32
|
+
level: 2,
|
|
33
|
+
text: `v${version}`,
|
|
34
|
+
href: `#${version}`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return extractedHeadings;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function extractFrontmatter<Frontmatter>(content: string): Frontmatter {
|
|
43
|
+
return matter(content).data as Frontmatter;
|
|
44
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type { TocItem, MdxCompileResult } from "./types";
|
|
2
|
+
export {
|
|
3
|
+
parseMdx,
|
|
4
|
+
preProcess,
|
|
5
|
+
postProcess,
|
|
6
|
+
handleCodeTitles,
|
|
7
|
+
createDefaultRehypePlugins,
|
|
8
|
+
createDefaultRemarkPlugins,
|
|
9
|
+
} from "./compile";
|
|
10
|
+
export { extractFrontmatter, extractTocsFromRawMdx, sluggify } from "./extract";
|
|
11
|
+
export type { ParseMdxOptions } from "./compile";
|
|
12
|
+
export {
|
|
13
|
+
readMdxFileBySlug,
|
|
14
|
+
parseMdxFile,
|
|
15
|
+
compileParsedMdxFile,
|
|
16
|
+
createMdxContentService,
|
|
17
|
+
} from "./content";
|
|
18
|
+
export type {
|
|
19
|
+
ReadMdxFileResult,
|
|
20
|
+
ParsedMdxFile,
|
|
21
|
+
CompiledMdxFile,
|
|
22
|
+
CreateMdxContentServiceOptions,
|
|
23
|
+
} from "./content";
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type TocItem = {
|
|
4
|
+
level: number;
|
|
5
|
+
text: string;
|
|
6
|
+
href: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type MdxCompileResult<Frontmatter> = {
|
|
10
|
+
content: ReactNode;
|
|
11
|
+
frontmatter: Frontmatter;
|
|
12
|
+
scope?: Record<string, unknown>;
|
|
13
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"jsx": "preserve"
|
|
11
|
+
},
|
|
12
|
+
"include": [
|
|
13
|
+
"src/**/*"
|
|
14
|
+
]
|
|
15
|
+
}
|