rehype-mdx-toc 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 boning-w
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # rehype-mdx-toc
2
+
3
+ [![github actions](https://github.com/boning-w/rehype-mdx-toc/actions/workflows/ci.yml/badge.svg)](https://github.com/boning-w/rehype-mdx-toc/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/github/boning-w/rehype-mdx-toc/graph/badge.svg?token=5GBJECAL34)](https://codecov.io/github/boning-w/rehype-mdx-toc)
5
+ [![license](https://img.shields.io/github/license/boning-w/rehype-mdx-toc)](https://github.com/boning-w/rehype-mdx-toc/blob/main/LICENSE)
6
+
7
+ A rehype plugin to create the table of contents and convert it into MDX exports.
8
+
9
+ ## Table of Contents
10
+
11
+ - [What is this?](#what-is-this)
12
+ - [When should I use this?](#when-should-i-use-this)
13
+ - [Install](#install)
14
+ - [Usage](#usage)
15
+ - [✅ For `@mdx-js/mdx`](#-for-mdx-jsmdx)
16
+ - [✅ For `mdx-bundler`](#-for-mdx-bundler)
17
+ - [🚫 For `next-mdx-remote`](#-for-next-mdx-remote)
18
+ - [API](#api)
19
+ - [Options](#options)
20
+ - [Types](#types)
21
+ - [License](#license)
22
+
23
+ ## What is this?
24
+
25
+ This package is a [unified](https://unifiedjs.com/) ([rehype](https://rehypejs.github.io/rehype/)) plugin that generates a table of contents (TOC) from headings in your MDX files and converts it into MDX exports.
26
+
27
+ ## When should I use this?
28
+
29
+ This plugin is useful when you want a table of contents data that is exported from MDX modules, which can then be used everwhere in your application.
30
+
31
+ ## Install
32
+
33
+ You can install this package using npm:
34
+
35
+ ```shell
36
+ npm install rehype-mdx-toc
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ Say we have mdx contents like this:
42
+
43
+ ```mdx
44
+ # Heading 1
45
+
46
+ ## Heading 2
47
+
48
+ ### Heading 3
49
+ ```
50
+
51
+ ...and our module `example.js` contains:
52
+
53
+ ```js
54
+ import { compile, run } from "@mdx-js/mdx";
55
+ import rehypeMdxToc from "rehype-mdx-toc";
56
+ import * as runtime from "react/jsx-runtime";
57
+
58
+ const mdxContent = `
59
+ # Heading 1
60
+ ## Heading 2
61
+ ### Heading 3
62
+ `;
63
+
64
+ const compiled = await compile(mdxContent, {
65
+ outputFormat: "function-body",
66
+ rehypePlugins: [rehypeMdxToc],
67
+ });
68
+ const result = await run(compiled, { ...runtime });
69
+ const toc = result.toc;
70
+ console.log(toc);
71
+ ```
72
+
73
+ ...then running `node example.js` will output:
74
+
75
+ ```json
76
+ [
77
+ {
78
+ "depth": 1,
79
+ "value": "Heading 1",
80
+ "numbering": [1, 0, 0, 0, 0, 0],
81
+ "id": undefined,
82
+ "href": undefined
83
+ },
84
+ {
85
+ "depth": 2,
86
+ "value": "Heading 2",
87
+ "numbering": [1, 1, 0, 0, 0, 0],
88
+ "id": undefined,
89
+ "href": undefined
90
+ },
91
+ {
92
+ "depth": 3,
93
+ "value": "Heading 3",
94
+ "numbering": [1, 1, 1, 0, 0, 0],
95
+ "id": undefined,
96
+ "href": undefined
97
+ }
98
+ ]
99
+ ```
100
+
101
+ ### Use with `rehype-slug`
102
+
103
+ To generate `id` and `href` properties for each TOC item, you can use the `rehype-slug` plugin before `rehype-mdx-toc`. This will automatically add `id` attributes to headings based on their text content.
104
+
105
+ ```js
106
+ import { compile, run } from "@mdx-js/mdx";
107
+ import rehypeSlug from "rehype-slug";
108
+ import rehypeMdxToc from "rehype-mdx-toc";
109
+ import * as runtime from "react/jsx-runtime";
110
+
111
+ const mdxContent = `
112
+ # Heading 1
113
+ ## Heading 2
114
+ ### Heading 3
115
+ `;
116
+
117
+ const compiled = await compile(mdxContent, {
118
+ outputFormat: "function-body",
119
+ rehypePlugins: [rehypeSlug, rehypeMdxToc],
120
+ });
121
+ const result = await run(compiled, { ...runtime });
122
+ const toc = result.toc;
123
+ console.log(toc);
124
+ ```
125
+
126
+ This will output:
127
+
128
+ ```json
129
+ [
130
+ {
131
+ "depth": 1,
132
+ "value": "Heading 1",
133
+ "numbering": [1, 0, 0, 0, 0, 0],
134
+ "id": "heading-1",
135
+ "href": "#heading-1"
136
+ },
137
+ {
138
+ "depth": 2,
139
+ "value": "Heading 2",
140
+ "numbering": [1, 1, 0, 0, 0, 0],
141
+ "id": "heading-2",
142
+ "href": "#heading-2"
143
+ },
144
+ {
145
+ "depth": 3,
146
+ "value": "Heading 3",
147
+ "numbering": [1, 1, 1, 0, 0, 0],
148
+ "id": "heading-3",
149
+ "href": "#heading-3"
150
+ }
151
+ ]
152
+ ```
153
+
154
+ ### ✅ For `@mdx-js/mdx`
155
+
156
+ See [Usage](#usage) and [Use with rehype-slug](#use-with-rehype-slug) sections above.
157
+
158
+ ### ✅ For `mdx-bundler`
159
+
160
+ For `mdx-bundler`, you can add this plugin to `bundleMDX()`'s mdxOptions:
161
+
162
+ ```js
163
+ bundleMDX({
164
+ source: mdxSource,
165
+ mdxOptions(options, frontmatter) {
166
+ options.rehypePlugins = [...(options.rehypePlugins ?? []), rehypeMdxToc];
167
+ return options;
168
+ },
169
+ });
170
+ ```
171
+
172
+ > See [mdx-bundler#Accessing named exports](https://github.com/kentcdodds/mdx-bundler?tab=readme-ov-file#accessing-named-exports)
173
+ >
174
+ > To access the `toc` export, You can use `getMDXExport` instead of `getMDXComponent` to treat the mdx file as a module instead of just a component.
175
+
176
+ ```js
177
+ import { getMDXExport } from "mdx-bundler/client";
178
+
179
+ function MDXPage({ code }: { code: string }) {
180
+ const mdxExport = getMDXExport(code);
181
+ console.log(mdxExport.toc); // 👈 get toc export here
182
+
183
+ const Component = React.useMemo(() => mdxExport.default, [code]);
184
+
185
+ return <Component />;
186
+ }
187
+ ```
188
+
189
+ ### 🚫 For `next-mdx-remote`
190
+
191
+ This plugin is not compatible with `next-mdx-remote` because `next-mdx-remote` does not support `import` and `export` statements in MDX files.
192
+
193
+ See [import/export caveats of next-mdx-remote](https://github.com/hashicorp/next-mdx-remote?tab=readme-ov-file#import--export)
194
+
195
+ > `import` and `export` statements cannot be used inside an MDX file.
196
+ >
197
+ > ...As for exports, the MDX content is treated as data, not a module, so there is no way for us to access any value which may be exported from the MDX passed to `next-mdx-remote`.
198
+
199
+ ## API
200
+
201
+ The default export is the rehype plugin function.
202
+
203
+ ### Options
204
+
205
+ - `default`: The default value to export if no headings. (Default: undefined).
206
+ - `name`: The name of the export. (Default: "toc").
207
+ - `skipDepth`: An array of heading depths to skip. If specified, headings of these depths will not be included in the TOC. (Default: `[]`).
208
+
209
+ ## Types
210
+
211
+ This package is fully typed with [TypeScript](https://www.typescriptlang.org/). The plugin exports the types `HeadingDepth`, `RehypeMdxTocOptions`, `TocItem`.
212
+
213
+ Mostly, you only need the `TocItem` type in your project:
214
+
215
+ ```ts
216
+ export type TocItem = {
217
+ depth: HeadingDepth; // The level of the heading (1 for `<h1>`, 2 for `<h2>`, etc.).
218
+ value: string; // The text content of the heading.
219
+ numbering: number[]; // An array representing the hierarchical numbering of the heading.
220
+ id?: string; // The ID of the heading, if available.
221
+ href?: string; // The link to the heading.
222
+ data?: Record<string, unknown>;
223
+ };
224
+ ```
225
+
226
+ ## License
227
+
228
+ [MIT](https://github.com/boning-w/rehype-mdx-toc/blob/main/LICENSE)
@@ -0,0 +1,76 @@
1
+ import type { Root } from "hast";
2
+ import type { VFile } from "vfile";
3
+ /**
4
+ * Type representing the depth of headings in a table of contents.
5
+ * This is used to specify which heading levels (h1, h2, etc.) should be included
6
+ * in the table of contents.
7
+ *
8
+ * The values correspond to HTML heading elements:
9
+ * - 1 for `<h1>`
10
+ * - 2 for `<h2>`
11
+ * - 3 for `<h3>`
12
+ * - 4 for `<h4>`
13
+ * - 5 for `<h5>`
14
+ * - 6 for `<h6>`
15
+ */
16
+ export type HeadingDepth = 1 | 2 | 3 | 4 | 5 | 6;
17
+ /**
18
+ * Options for the rehype-mdx-toc plugin.
19
+ *
20
+ * This type defines the configuration options that can be passed to the plugin.
21
+ * - `default`: Optional default value for the table of contents when no headings are found.
22
+ * - `name`: The name of the export variable for the table of contents.
23
+ */
24
+ export type RehypeMdxTocOptions = {
25
+ /**
26
+ * Optional default value for the table of contents.
27
+ * If provided, this will be used when no headings are found.
28
+ *
29
+ * @default undefined
30
+ */
31
+ default?: unknown;
32
+ /**
33
+ * The name of the export variable for the table of contents.
34
+ *
35
+ * @default "toc"
36
+ */
37
+ name?: string;
38
+ /**
39
+ * An array of heading depths to skip.
40
+ * If specified, headings of these depths will not be included in the TOC.
41
+ *
42
+ * @default []
43
+ */
44
+ skipDepth?: HeadingDepth[];
45
+ };
46
+ /**
47
+ * Represents a single item in the table of contents (TOC).
48
+ *
49
+ * Each item corresponds to a heading in the MDX file and includes:
50
+ * - `depth`: The level of the heading (1 for `<h1>`, 2 for `<h2>`, etc.).
51
+ * - `value`: The text content of the heading.
52
+ * - `numbering`: An array representing the hierarchical numbering of the heading.
53
+ * - `id`: The ID of the heading, if available.
54
+ * - `href`: The link to the heading, typically a fragment identifier.
55
+ * - `data`: Additional data associated with the heading.
56
+ */
57
+ export type TocItem = {
58
+ depth: HeadingDepth;
59
+ value: string;
60
+ numbering: number[];
61
+ id?: string;
62
+ href?: string;
63
+ data?: Record<string, unknown>;
64
+ };
65
+ /**
66
+ * Rehype plugin to generate a table of contents (TOC) for MDX files.
67
+ *
68
+ * This plugin processes the HAST tree to find all headings and constructs a TOC
69
+ * with hierarchical numbering. The TOC is then exported as a variable that can
70
+ * be used in the MDX module.
71
+ *
72
+ * @param options - Optional configuration for the plugin.
73
+ * @returns A function that transforms the HAST tree.
74
+ */
75
+ export default function rehypeMdxToc({ name, ...options }?: RehypeMdxTocOptions): (tree: Root, file: VFile) => void;
76
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAOnC;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;CAC5B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,IAAY,EACZ,GAAG,OAAO,EACX,GAAE,mBAAwB,IAEjB,MAAM,IAAI,EAAE,MAAM,KAAK,UA8ChC"}
@@ -0,0 +1,58 @@
1
+ import { define } from "unist-util-mdx-define";
2
+ import { headingRank } from "hast-util-heading-rank";
3
+ import { toString } from "hast-util-to-string";
4
+ import { valueToEstree } from "estree-util-value-to-estree";
5
+ import { CONTINUE, visit } from "unist-util-visit";
6
+ /**
7
+ * Rehype plugin to generate a table of contents (TOC) for MDX files.
8
+ *
9
+ * This plugin processes the HAST tree to find all headings and constructs a TOC
10
+ * with hierarchical numbering. The TOC is then exported as a variable that can
11
+ * be used in the MDX module.
12
+ *
13
+ * @param options - Optional configuration for the plugin.
14
+ * @returns A function that transforms the HAST tree.
15
+ */
16
+ export default function rehypeMdxToc({ name = "toc", ...options } = {}) {
17
+ // Return the actual transformer function for rehype.
18
+ return (tree, file) => {
19
+ // This will hold the final table of contents data.
20
+ const toc = [];
21
+ // Tracks the current numbering for headings.
22
+ // Each index corresponds to a heading level: [h1, h2, h3, h4, h5, h6].
23
+ const currentNumbering = [0, 0, 0, 0, 0, 0];
24
+ // Visit every HTML element in the tree.
25
+ visit(tree, "element", function (node) {
26
+ // Check if this element is a heading (<h1>–<h6>).
27
+ const rank = headingRank(node);
28
+ // If the rank is undefined, skip this node.
29
+ if (rank === undefined)
30
+ return CONTINUE;
31
+ // If the depth is in the skipDepth array, skip this node.
32
+ const depth = rank;
33
+ if (options.skipDepth?.includes(depth))
34
+ return CONTINUE;
35
+ // Increment the counter for this heading level.
36
+ currentNumbering[depth - 1] += 1;
37
+ // Reset all deeper levels to zero.
38
+ // This ensures the numbering doesn't accidentally carry over.
39
+ for (let i = depth; i < currentNumbering.length; i++) {
40
+ currentNumbering[i] = 0;
41
+ }
42
+ // Add this heading to the TOC array.
43
+ toc.push({
44
+ depth,
45
+ value: toString(node),
46
+ numbering: [...currentNumbering],
47
+ id: node.properties?.id,
48
+ href: node.properties?.id ? `#${node.properties.id}` : undefined,
49
+ });
50
+ });
51
+ // Attach the TOC as an exported variable in the MDX file.
52
+ const data = toc.length ? toc : options.default;
53
+ define(tree, file, {
54
+ [name]: valueToEstree(data),
55
+ });
56
+ };
57
+ }
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAqEnD;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,IAAI,GAAG,KAAK,EACZ,GAAG,OAAO,KACa,EAAE;IACzB,qDAAqD;IACrD,OAAO,CAAC,IAAU,EAAE,IAAW,EAAE,EAAE;QACjC,mDAAmD;QACnD,MAAM,GAAG,GAAc,EAAE,CAAC;QAE1B,6CAA6C;QAC7C,uEAAuE;QACvE,MAAM,gBAAgB,GAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAEtD,wCAAwC;QACxC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,IAAI;YACnC,kDAAkD;YAClD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;YAE/B,4CAA4C;YAC5C,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,QAAQ,CAAC;YAExC,0DAA0D;YAC1D,MAAM,KAAK,GAAG,IAAoB,CAAC;YACnC,IAAI,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;gBAAE,OAAO,QAAQ,CAAC;YAExD,gDAAgD;YAChD,gBAAgB,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;YAEjC,mCAAmC;YACnC,8DAA8D;YAC9D,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,gBAAgB,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACrD,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;YAED,qCAAqC;YACrC,GAAG,CAAC,IAAI,CAAC;gBACP,KAAK;gBACL,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC;gBACrB,SAAS,EAAE,CAAC,GAAG,gBAAgB,CAAC;gBAChC,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,EAAwB;gBAC7C,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS;aACjE,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,0DAA0D;QAC1D,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;QAEhD,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE;YACjB,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC;SAC5B,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../test/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,250 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { compile, run } from "@mdx-js/mdx";
4
+ import rehypeMdxToc, {} from "rehype-mdx-toc";
5
+ import rehypeSlug from "rehype-slug";
6
+ import * as runtime from "react/jsx-runtime";
7
+ const source = `
8
+ # The Main Heading
9
+
10
+ ## Section _1_
11
+
12
+ ### Subheading
13
+
14
+ ### Mubheading
15
+
16
+ ## Section _2_
17
+
18
+ ### \`Subheading\`
19
+
20
+ ### [Mubheading](#)
21
+
22
+ > #### QuoteHeading
23
+ > Some Content
24
+ > #### **QuoteHeading**
25
+ > Some Content
26
+
27
+ + ##### **ListItem** _Heading_
28
+ + ##### _ListItem_ **Heading**
29
+ `;
30
+ async function getTocFromMdxModule(mdxContent) {
31
+ const compiled = await compile(mdxContent, {
32
+ outputFormat: "function-body",
33
+ rehypePlugins: [rehypeMdxToc],
34
+ });
35
+ const result = await run(compiled, { ...runtime });
36
+ const toc = result.toc;
37
+ return toc;
38
+ }
39
+ async function getTocFromMdxModuleWithRehypeSlug(mdxContent) {
40
+ const compiled = await compile(mdxContent, {
41
+ outputFormat: "function-body",
42
+ rehypePlugins: [rehypeSlug, rehypeMdxToc],
43
+ });
44
+ const result = await run(compiled, { ...runtime });
45
+ const toc = result.toc;
46
+ return toc;
47
+ }
48
+ describe("rehype-mdx-toc", () => {
49
+ it("should export toc to MDX module", async () => {
50
+ const mdx = "# Heading 1";
51
+ const toc = await getTocFromMdxModule(mdx);
52
+ assert.ok(Array.isArray(toc), "Should export toc");
53
+ });
54
+ it("should generate undefined TOC for no headings", async () => {
55
+ const mdx = "This is a test without headings.";
56
+ const toc = await getTocFromMdxModule(mdx);
57
+ assert.ok(toc === undefined, "Should generate undefined TOC for no headings");
58
+ });
59
+ it("should generate correct TOC structure without heading IDs", async () => {
60
+ const toc = await getTocFromMdxModule("# Heading 1\n\n## Heading 2\n\n### Heading 3");
61
+ assert.deepEqual(toc, [
62
+ {
63
+ depth: 1,
64
+ value: "Heading 1",
65
+ numbering: [1, 0, 0, 0, 0, 0],
66
+ id: undefined,
67
+ href: undefined,
68
+ },
69
+ {
70
+ depth: 2,
71
+ value: "Heading 2",
72
+ numbering: [1, 1, 0, 0, 0, 0],
73
+ id: undefined,
74
+ href: undefined,
75
+ },
76
+ {
77
+ depth: 3,
78
+ value: "Heading 3",
79
+ numbering: [1, 1, 1, 0, 0, 0],
80
+ id: undefined,
81
+ href: undefined,
82
+ },
83
+ ]);
84
+ });
85
+ it("should generate correct TOC structure with heading IDs", async () => {
86
+ const toc = await getTocFromMdxModuleWithRehypeSlug(source);
87
+ assert.deepEqual(toc, [
88
+ {
89
+ depth: 1,
90
+ value: "The Main Heading",
91
+ numbering: [1, 0, 0, 0, 0, 0],
92
+ id: "the-main-heading",
93
+ href: "#the-main-heading",
94
+ },
95
+ {
96
+ depth: 2,
97
+ value: "Section 1",
98
+ numbering: [1, 1, 0, 0, 0, 0],
99
+ id: "section-1",
100
+ href: "#section-1",
101
+ },
102
+ {
103
+ depth: 3,
104
+ value: "Subheading",
105
+ numbering: [1, 1, 1, 0, 0, 0],
106
+ id: "subheading",
107
+ href: "#subheading",
108
+ },
109
+ {
110
+ depth: 3,
111
+ value: "Mubheading",
112
+ numbering: [1, 1, 2, 0, 0, 0],
113
+ id: "mubheading",
114
+ href: "#mubheading",
115
+ },
116
+ {
117
+ depth: 2,
118
+ value: "Section 2",
119
+ numbering: [1, 2, 0, 0, 0, 0],
120
+ id: "section-2",
121
+ href: "#section-2",
122
+ },
123
+ {
124
+ depth: 3,
125
+ value: "Subheading",
126
+ numbering: [1, 2, 1, 0, 0, 0],
127
+ id: "subheading-1",
128
+ href: "#subheading-1",
129
+ },
130
+ {
131
+ depth: 3,
132
+ value: "Mubheading",
133
+ numbering: [1, 2, 2, 0, 0, 0],
134
+ id: "mubheading-1",
135
+ href: "#mubheading-1",
136
+ },
137
+ {
138
+ depth: 4,
139
+ value: "QuoteHeading",
140
+ numbering: [1, 2, 2, 1, 0, 0],
141
+ id: "quoteheading",
142
+ href: "#quoteheading",
143
+ },
144
+ {
145
+ depth: 4,
146
+ value: "QuoteHeading",
147
+ numbering: [1, 2, 2, 2, 0, 0],
148
+ id: "quoteheading-1",
149
+ href: "#quoteheading-1",
150
+ },
151
+ {
152
+ depth: 5,
153
+ value: "ListItem Heading",
154
+ numbering: [1, 2, 2, 2, 1, 0],
155
+ id: "listitem-heading",
156
+ href: "#listitem-heading",
157
+ },
158
+ {
159
+ depth: 5,
160
+ value: "ListItem Heading",
161
+ numbering: [1, 2, 2, 2, 2, 0],
162
+ id: "listitem-heading-1",
163
+ href: "#listitem-heading-1",
164
+ },
165
+ ]);
166
+ });
167
+ it("should export toc with default export name", async () => {
168
+ const toc = await getTocFromMdxModule(source);
169
+ assert.ok(Array.isArray(toc), "should export toc with default export name");
170
+ });
171
+ it("should export toc with custom export name", async () => {
172
+ const compiled = await compile(source, {
173
+ outputFormat: "function-body",
174
+ rehypePlugins: [[rehypeMdxToc, { name: "customToc" }]],
175
+ });
176
+ const result = await run(compiled, { ...runtime });
177
+ assert.ok(Array.isArray(result.customToc), "Should export toc with custom name");
178
+ });
179
+ it("should export toc with default value (which is undefined) without headings", async () => {
180
+ const mdx = "This is a test without headings.";
181
+ const toc = await getTocFromMdxModule(mdx);
182
+ assert.ok(toc === undefined, "should export toc with default value (which is undefined) without headings");
183
+ });
184
+ it("should export toc with custom default value without headings", async () => {
185
+ const mdx = "This is a test without headings.";
186
+ const compiled = await compile(mdx, {
187
+ outputFormat: "function-body",
188
+ rehypePlugins: [[rehypeMdxToc, { default: "No headings in this MDX" }]],
189
+ });
190
+ const result = await run(compiled, { ...runtime });
191
+ assert.ok((result.toc = "No headings in this MDX"), "should export toc with custom default value without headings");
192
+ });
193
+ it("should skip headings based on skipDepth option", async () => {
194
+ const mdx = `
195
+ # Heading 1
196
+ ## Heading 2
197
+ ### Heading 3
198
+ #### Heading 4
199
+ #### Heading 4.1
200
+ ##### Heading 5
201
+ ###### Heading 6
202
+ ### Heading 3.1
203
+ #### Heading 4.2
204
+ `;
205
+ const compiled = await compile(mdx, {
206
+ outputFormat: "function-body",
207
+ rehypePlugins: [[rehypeMdxToc, { skipDepth: [1, 2, 5, 6] }]],
208
+ });
209
+ const result = await run(compiled, { ...runtime });
210
+ const toc = result.toc;
211
+ assert.deepEqual(toc, [
212
+ {
213
+ depth: 3,
214
+ value: "Heading 3",
215
+ numbering: [0, 0, 1, 0, 0, 0],
216
+ id: undefined,
217
+ href: undefined,
218
+ },
219
+ {
220
+ depth: 4,
221
+ value: "Heading 4",
222
+ numbering: [0, 0, 1, 1, 0, 0],
223
+ id: undefined,
224
+ href: undefined,
225
+ },
226
+ {
227
+ depth: 4,
228
+ value: "Heading 4.1",
229
+ numbering: [0, 0, 1, 2, 0, 0],
230
+ id: undefined,
231
+ href: undefined,
232
+ },
233
+ {
234
+ depth: 3,
235
+ value: "Heading 3.1",
236
+ numbering: [0, 0, 2, 0, 0, 0],
237
+ id: undefined,
238
+ href: undefined,
239
+ },
240
+ {
241
+ depth: 4,
242
+ value: "Heading 4.2",
243
+ numbering: [0, 0, 2, 1, 0, 0],
244
+ id: undefined,
245
+ href: undefined,
246
+ },
247
+ ]);
248
+ });
249
+ });
250
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../test/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"root":["../src/index.ts","../test/index.ts"],"version":"5.8.3"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "rehype-mdx-toc",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "description": "A rehype plugin to create the table of contents and convert it into MDX exports",
6
+ "keywords": [
7
+ "toc",
8
+ "table of contents",
9
+ "markdown",
10
+ "markdown-toc",
11
+ "mdx",
12
+ "rehype",
13
+ "rehype-plugin",
14
+ "hast",
15
+ "unified"
16
+ ],
17
+ "homepage": "https://github.com/boning-w/rehype-mdx-toc#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/boning-w/rehype-mdx-toc/issues"
20
+ },
21
+ "license": "MIT",
22
+ "author": "boning <wang.boning@outlook.com>",
23
+ "files": [
24
+ "dist",
25
+ "src"
26
+ ],
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/src/index.d.ts",
30
+ "import": "./dist/src/index.js",
31
+ "default": "./dist/src/index.js"
32
+ }
33
+ },
34
+ "types": "./dist/src/index.d.ts",
35
+ "main": "./dist/src/index.js",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/boning-w/rehype-mdx-toc.git"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc --build",
42
+ "pretest": "npm run build",
43
+ "test": "node --test",
44
+ "test-coverage": "c8 --100 node --test && c8 report --reporter=html --reporter=lcov"
45
+ },
46
+ "dependencies": {
47
+ "estree-util-value-to-estree": "^3.4.0",
48
+ "hast-util-heading-rank": "^3.0.0",
49
+ "hast-util-to-string": "^3.0.1",
50
+ "unist-util-mdx-define": "^1.1.2",
51
+ "unist-util-visit": "^5.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "@mdx-js/mdx": "^3.1.0",
55
+ "@types/hast": "^3.0.4",
56
+ "@types/node": "^22.16.0",
57
+ "@types/react": "^19.1.8",
58
+ "@types/vfile": "^3.0.2",
59
+ "c8": "^10.1.3",
60
+ "react": "^19.1.0",
61
+ "rehype-slug": "^6.0.0",
62
+ "typescript": "^5.8.3"
63
+ },
64
+ "sideEffects": false
65
+ }
package/src/index.ts ADDED
@@ -0,0 +1,137 @@
1
+ import type { Root } from "hast";
2
+ import type { VFile } from "vfile";
3
+ import { define } from "unist-util-mdx-define";
4
+ import { headingRank } from "hast-util-heading-rank";
5
+ import { toString } from "hast-util-to-string";
6
+ import { valueToEstree } from "estree-util-value-to-estree";
7
+ import { CONTINUE, visit } from "unist-util-visit";
8
+
9
+ /**
10
+ * Type representing the depth of headings in a table of contents.
11
+ * This is used to specify which heading levels (h1, h2, etc.) should be included
12
+ * in the table of contents.
13
+ *
14
+ * The values correspond to HTML heading elements:
15
+ * - 1 for `<h1>`
16
+ * - 2 for `<h2>`
17
+ * - 3 for `<h3>`
18
+ * - 4 for `<h4>`
19
+ * - 5 for `<h5>`
20
+ * - 6 for `<h6>`
21
+ */
22
+ export type HeadingDepth = 1 | 2 | 3 | 4 | 5 | 6;
23
+
24
+ /**
25
+ * Options for the rehype-mdx-toc plugin.
26
+ *
27
+ * This type defines the configuration options that can be passed to the plugin.
28
+ * - `default`: Optional default value for the table of contents when no headings are found.
29
+ * - `name`: The name of the export variable for the table of contents.
30
+ */
31
+ export type RehypeMdxTocOptions = {
32
+ /**
33
+ * Optional default value for the table of contents.
34
+ * If provided, this will be used when no headings are found.
35
+ *
36
+ * @default undefined
37
+ */
38
+ default?: unknown;
39
+
40
+ /**
41
+ * The name of the export variable for the table of contents.
42
+ *
43
+ * @default "toc"
44
+ */
45
+ name?: string;
46
+
47
+ /**
48
+ * An array of heading depths to skip.
49
+ * If specified, headings of these depths will not be included in the TOC.
50
+ *
51
+ * @default []
52
+ */
53
+ skipDepth?: HeadingDepth[];
54
+ };
55
+
56
+ /**
57
+ * Represents a single item in the table of contents (TOC).
58
+ *
59
+ * Each item corresponds to a heading in the MDX file and includes:
60
+ * - `depth`: The level of the heading (1 for `<h1>`, 2 for `<h2>`, etc.).
61
+ * - `value`: The text content of the heading.
62
+ * - `numbering`: An array representing the hierarchical numbering of the heading.
63
+ * - `id`: The ID of the heading, if available.
64
+ * - `href`: The link to the heading, typically a fragment identifier.
65
+ * - `data`: Additional data associated with the heading.
66
+ */
67
+ export type TocItem = {
68
+ depth: HeadingDepth;
69
+ value: string;
70
+ numbering: number[];
71
+ id?: string;
72
+ href?: string;
73
+ data?: Record<string, unknown>;
74
+ };
75
+
76
+ /**
77
+ * Rehype plugin to generate a table of contents (TOC) for MDX files.
78
+ *
79
+ * This plugin processes the HAST tree to find all headings and constructs a TOC
80
+ * with hierarchical numbering. The TOC is then exported as a variable that can
81
+ * be used in the MDX module.
82
+ *
83
+ * @param options - Optional configuration for the plugin.
84
+ * @returns A function that transforms the HAST tree.
85
+ */
86
+ export default function rehypeMdxToc({
87
+ name = "toc",
88
+ ...options
89
+ }: RehypeMdxTocOptions = {}) {
90
+ // Return the actual transformer function for rehype.
91
+ return (tree: Root, file: VFile) => {
92
+ // This will hold the final table of contents data.
93
+ const toc: TocItem[] = [];
94
+
95
+ // Tracks the current numbering for headings.
96
+ // Each index corresponds to a heading level: [h1, h2, h3, h4, h5, h6].
97
+ const currentNumbering: number[] = [0, 0, 0, 0, 0, 0];
98
+
99
+ // Visit every HTML element in the tree.
100
+ visit(tree, "element", function (node) {
101
+ // Check if this element is a heading (<h1>–<h6>).
102
+ const rank = headingRank(node);
103
+
104
+ // If the rank is undefined, skip this node.
105
+ if (rank === undefined) return CONTINUE;
106
+
107
+ // If the depth is in the skipDepth array, skip this node.
108
+ const depth = rank as HeadingDepth;
109
+ if (options.skipDepth?.includes(depth)) return CONTINUE;
110
+
111
+ // Increment the counter for this heading level.
112
+ currentNumbering[depth - 1] += 1;
113
+
114
+ // Reset all deeper levels to zero.
115
+ // This ensures the numbering doesn't accidentally carry over.
116
+ for (let i = depth; i < currentNumbering.length; i++) {
117
+ currentNumbering[i] = 0;
118
+ }
119
+
120
+ // Add this heading to the TOC array.
121
+ toc.push({
122
+ depth,
123
+ value: toString(node),
124
+ numbering: [...currentNumbering],
125
+ id: node.properties?.id as string | undefined,
126
+ href: node.properties?.id ? `#${node.properties.id}` : undefined,
127
+ });
128
+ });
129
+
130
+ // Attach the TOC as an exported variable in the MDX file.
131
+ const data = toc.length ? toc : options.default;
132
+
133
+ define(tree, file, {
134
+ [name]: valueToEstree(data),
135
+ });
136
+ };
137
+ }