satteri 0.1.1 → 0.2.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 +300 -4
- package/dist/binding.browser.d.ts +1 -0
- package/dist/binding.browser.js +2 -0
- package/dist/binding.d.ts +1 -0
- package/dist/binding.js +1 -0
- package/dist/command-buffer.js +0 -1
- package/dist/compile.d.ts +62 -9
- package/dist/compile.js +122 -38
- package/dist/hast/hast-materializer.js +2 -1
- package/dist/hast/hast-reader.d.ts +13 -0
- package/dist/hast/hast-reader.js +27 -0
- package/dist/hast/hast-visitor.d.ts +12 -12
- package/dist/hast/hast-visitor.js +3 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +10 -1
- package/dist/mdast/mdast-materializer.d.ts +3 -3
- package/dist/mdast/mdast-materializer.js +1 -1
- package/dist/mdast/mdast-reader.d.ts +1 -1
- package/dist/mdast/mdast-reader.js +1 -1
- package/dist/mdast/mdast-visitor.d.ts +11 -11
- package/dist/mdast/mdast-visitor.js +17 -34
- package/dist/plugin.d.ts +4 -6
- package/dist/plugin.js +0 -6
- package/dist/types.js +0 -4
- package/index.d.ts +55 -6
- package/index.js +52 -52
- package/package.json +30 -9
- package/wasi-worker-browser.mjs +36 -0
- package/dist/hast-types.d.ts +0 -3
- package/dist/hast-types.js +0 -1
- package/dist/mdast-types.d.ts +0 -3
- package/dist/mdast-types.js +0 -1
- package/satteri_napi.linux-x64-gnu.node +0 -0
package/README.md
CHANGED
|
@@ -1,7 +1,303 @@
|
|
|
1
1
|
# satteri
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Native-enhanced Markdown parsing and processing for JavaScript. Parse and compile in Rust, create flexible plugins in JavaScript.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install satteri
|
|
9
|
+
yarn add satteri
|
|
10
|
+
pnpm add satteri
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Markdown to HTML
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { markdownToHtml } from "satteri";
|
|
19
|
+
|
|
20
|
+
const html = markdownToHtml("# Hello\n\nWorld");
|
|
21
|
+
// <h1>Hello</h1>\n<p>World</p>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### MDX to JS
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { mdxToJs } from "satteri";
|
|
28
|
+
|
|
29
|
+
const js = mdxToJs("# Hello\n\n<MyComponent />");
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### With plugins
|
|
33
|
+
|
|
34
|
+
Both functions accept `mdastPlugins` (operate on the Markdown AST before conversion to HAST) and `hastPlugins` (operate on the HAST before output).
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { markdownToHtml } from "satteri";
|
|
38
|
+
import { removeHeadings } from "./my-mdast-plugins.js";
|
|
39
|
+
import { addLinkClasses } from "./my-hast-plugins.js";
|
|
40
|
+
|
|
41
|
+
const html = markdownToHtml("# Hello\n\n[link](https://example.com)", {
|
|
42
|
+
mdastPlugins: [removeHeadings],
|
|
43
|
+
hastPlugins: [addLinkClasses],
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If you're familiar with the unified ecosystem, mdast and hast plugins would be similar to remark and rehype plugins, respectively, and re-uses the same AST shape for both. This project does not currently have an equivalent of micromark or recma plugins.
|
|
48
|
+
|
|
49
|
+
## Plugins
|
|
50
|
+
|
|
51
|
+
### MDAST plugins
|
|
52
|
+
|
|
53
|
+
MDAST plugins run on the Markdown syntax tree, allowing you to do things like replace emoji shortcodes, unwrap images from paragraphs, or collect headings for a table of contents before Markdown is transformed to HTML / JS. Define visitor methods named after node types (`heading`, `code`, `link`, `image`, etc.). Each visitor receives the node and a context object for mutations.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const emojis = defineMdastPlugin({
|
|
57
|
+
name: "emojis",
|
|
58
|
+
text(node, ctx) {
|
|
59
|
+
if (node.value.includes(":wave:")) {
|
|
60
|
+
ctx.setProperty(node, "value", node.value.replaceAll(":wave:", "\u{1F44B}"));
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Visitors can alternatively return a replacement node, raw Markdown, or raw HTML. This is useful when the replacement can't be expressed as property changes on the original node:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const highlightCode = defineMdastPlugin({
|
|
70
|
+
name: "highlight-code",
|
|
71
|
+
code(node) {
|
|
72
|
+
return { rawHtml: `<pre class="highlighted">${escape(node.value)}</pre>` };
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
All standard mdast node types are supported, plus GFM extensions (`table`, `tableRow`, `tableCell`, `delete`, `footnoteDefinition`, `footnoteReference`) and MDX nodes (`mdxJsxFlowElement`, `mdxJsxTextElement`, `mdxFlowExpression`, `mdxTextExpression`, `mdxjsEsm`) if enabled. For more information on the AST shape and node types, see the [mdast spec](https://github.com/syntax-tree/mdast).
|
|
78
|
+
|
|
79
|
+
### HAST plugins
|
|
80
|
+
|
|
81
|
+
HAST plugins run on the HTML syntax tree after mdast-to-hast conversion, allowing you to do things like add classes to elements, set attributes on links, or wrap HTML elements with other elements, etc. Element visitors use a `filter` array to specify which tag names (or component names for MDX) to match.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const addLinkClasses = defineHastPlugin({
|
|
85
|
+
name: "add-link-classes",
|
|
86
|
+
element: {
|
|
87
|
+
filter: ["a"],
|
|
88
|
+
visit(node, ctx) {
|
|
89
|
+
ctx.setProperty(node, "class", "link");
|
|
90
|
+
ctx.setProperty(node, "target", "_blank");
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Multiple filter groups on the same node type:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const multiFilter = defineHastPlugin({
|
|
100
|
+
name: "multi-filter",
|
|
101
|
+
element: [
|
|
102
|
+
{
|
|
103
|
+
filter: ["h1", "h2", "h3"],
|
|
104
|
+
visit(node, ctx) {
|
|
105
|
+
ctx.setProperty(node, "class", "heading");
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
filter: ["a"],
|
|
110
|
+
visit(node, ctx) {
|
|
111
|
+
ctx.setProperty(node, "target", "_blank");
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
An empty filter matches all elements, but can quickly become expensive when used on large documents, so use with caution:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const allElements = defineHastPlugin({
|
|
122
|
+
name: "all-elements",
|
|
123
|
+
element: {
|
|
124
|
+
filter: [],
|
|
125
|
+
visit(node, ctx) {
|
|
126
|
+
ctx.setProperty(node, "data-visited", "true");
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Non-element visitors (`text`, `comment`, `raw`, `doctype`, MDX expression types) use bare functions instead of filter objects:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const uppercaseText = defineHastPlugin({
|
|
136
|
+
name: "uppercase-text",
|
|
137
|
+
text(node, ctx) {
|
|
138
|
+
ctx.setProperty(node, "value", node.value.toUpperCase());
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
For more information on the AST shape and node types, see the [hast spec](https://github.com/syntax-tree/hast).
|
|
144
|
+
|
|
145
|
+
### Mutating nodes
|
|
146
|
+
|
|
147
|
+
Unlike remark and rehype plugins, nodes in Sätteri inside plugins are read-only. The AST lives in Rust memory and JavaScript only has a "view" over the different nodes, so direct mutations like `node.value = "new text"` have no effect. Use the context methods (`ctx.setProperty`, `ctx.removeNode`, `ctx.replaceNode`, etc.) instead, which send changes back to Rust in an efficient way.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
// Won't work
|
|
151
|
+
heading(node, ctx) {
|
|
152
|
+
node.depth = 2; // no effect, TypeScript will also complain that the node is readonly
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Do this instead
|
|
156
|
+
heading(node, ctx) {
|
|
157
|
+
ctx.setProperty(node, "depth", 2);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Or return a new node to replace it entirely, but this is less efficient and generally not recommended
|
|
161
|
+
heading(node) {
|
|
162
|
+
return { ..node, depth: 2 };
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Async plugins
|
|
167
|
+
|
|
168
|
+
Visitors can optionally be async. When any visitor is async, `markdownToHtml` and `mdxToJs` return a `Promise<string>` instead of `string`. For performance reasons, it is typically best to avoid async visitors, especially if your visitor matches a large number of nodes.
|
|
169
|
+
|
|
170
|
+
````ts
|
|
171
|
+
const highlighter = await createHighlighter({ themes: ["github-dark"], langs: ["js", "ts"] });
|
|
172
|
+
|
|
173
|
+
const asyncHighlight = defineMdastPlugin({
|
|
174
|
+
name: "async-highlight",
|
|
175
|
+
async code(node) {
|
|
176
|
+
const html = await highlighter.codeToHtml(node.value, {
|
|
177
|
+
lang: node.lang,
|
|
178
|
+
theme: "github-dark",
|
|
179
|
+
});
|
|
180
|
+
return { rawHtml: html };
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Returns Promise<string> when async plugins are used
|
|
185
|
+
const html = await markdownToHtml("```js\ncode\n```", {
|
|
186
|
+
mdastPlugins: [asyncHighlight],
|
|
187
|
+
});
|
|
188
|
+
````
|
|
189
|
+
|
|
190
|
+
## API
|
|
191
|
+
|
|
192
|
+
### `markdownToHtml(source: string, options?: CompileOptions)`
|
|
193
|
+
|
|
194
|
+
Parse Markdown and compile to HTML. Returns `string` if all plugins are sync, `Promise<string>` if any are async.
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
const html = markdownToHtml("# Hello\n\nWorld");
|
|
198
|
+
// <h1>Hello</h1>\n<p>World</p>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### `mdxToJs(source: string, options?: MdxCompileOptions)`
|
|
202
|
+
|
|
203
|
+
Parse MDX and compile to JavaScript module code. Same sync/async return behavior.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const js = mdxToJs("# Hello\n\n<MyComponent />");
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Static optimization
|
|
210
|
+
|
|
211
|
+
The `optimizeStatic` option for MDX collapses static subtrees into pre-rendered HTML strings, reducing the number of JSX element calls in the output and increasing rendering performance. Dynamic content (JSX components, expressions) is preserved as normal JSX calls.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
// Astro-style: wraps static HTML in <Fragment set:html="...">
|
|
215
|
+
const js = mdxToJs("# Hello\n\nWorld", {
|
|
216
|
+
optimizeStatic: {
|
|
217
|
+
component: "Fragment",
|
|
218
|
+
prop: "set:html",
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// React-style: wraps in <div dangerouslySetInnerHTML={{ __html: "..." }}>
|
|
223
|
+
const js = mdxToJs("# Hello\n\nWorld", {
|
|
224
|
+
optimizeStatic: {
|
|
225
|
+
component: "div",
|
|
226
|
+
prop: "dangerouslySetInnerHTML",
|
|
227
|
+
wrapPropValue: true,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The `ignoreElements` option can be used to exclude specific elements from collapsing.
|
|
233
|
+
|
|
234
|
+
Shoutout to [Bjorn Lu](https://bjornlu.com) for originally developing this optimization for [Astro](https://astro.build/).
|
|
235
|
+
|
|
236
|
+
### `markdownToMdast(source: string)`
|
|
237
|
+
|
|
238
|
+
Parse Markdown and return a complete mdast tree. This can be useful if you wanted to benefit from the fast native parsing of Sätteri, but ultimately wanted another pipeline to handle transformations and compilation, e.g. using remark plugins and `remark-stringify` to convert back to Markdown after processing.
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
import { markdownToMdast } from "satteri";
|
|
242
|
+
|
|
243
|
+
const tree = markdownToMdast("# Hello\n\nWorld");
|
|
244
|
+
// tree.children[0].type === "heading"
|
|
245
|
+
// tree.children[0].depth === 1
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### `mdxToMdast(source: string)`
|
|
249
|
+
|
|
250
|
+
Parse MDX and return a complete mdast tree.
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
const tree = mdxToMdast('<Component foo="bar" />');
|
|
254
|
+
// tree.children[0].type === "mdxJsxFlowElement"
|
|
255
|
+
// tree.children[0].name === "Component"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### `markdownToHast(source: string)`
|
|
259
|
+
|
|
260
|
+
Parse Markdown, convert to hast, and return a complete hast tree.
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
const tree = markdownToHast("# Hello\n\nWorld");
|
|
264
|
+
// tree.children[0].type === "element"
|
|
265
|
+
// tree.children[0].tagName === "h1"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### `mdxToHast(source: string)`
|
|
269
|
+
|
|
270
|
+
Parse MDX, convert to hast, and return a complete hast tree.
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
const tree = mdxToHast("<MyComponent />");
|
|
274
|
+
// tree.children[0].type === "mdxJsxFlowElement"
|
|
275
|
+
// tree.children[0].name === "MyComponent"
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### `defineMdastPlugin(definition: MdastPluginDefinition)`
|
|
279
|
+
|
|
280
|
+
Type-safe wrapper for MDAST plugin definitions.
|
|
281
|
+
|
|
282
|
+
### `defineHastPlugin(definition: HastPluginDefinition)`
|
|
283
|
+
|
|
284
|
+
Type-safe wrapper for HAST plugin definitions.
|
|
285
|
+
|
|
286
|
+
### `CompileOptions`
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
interface CompileOptions {
|
|
290
|
+
mdastPlugins?: MdastPluginDefinition[];
|
|
291
|
+
hastPlugins?: HastPluginDefinition[];
|
|
292
|
+
filename?: string;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// mdxToJs accepts MdxCompileOptions, which extends CompileOptions
|
|
296
|
+
interface MdxCompileOptions extends CompileOptions {
|
|
297
|
+
optimizeStatic?: OptimizeStaticConfig;
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { applyCommandsAndConvertToHastHandle, applyCommandsToHandle, applyCommandsToMdastHandle, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createMdastHandle, createMdxHastHandle, createMdxMdastHandle, dropHandle, getHandleSource, getNodeData, mdastTextContentHandle, parseExpression, parseToHtml, renderHandle, serializeHandle, serializeMdastHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../satteri_napi.wasi-browser.js";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @ts-nocheck — WASM browser binding has no type declarations
|
|
2
|
+
export { applyCommandsAndConvertToHastHandle, applyCommandsToHandle, applyCommandsToMdastHandle, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createMdastHandle, createMdxHastHandle, createMdxMdastHandle, dropHandle, getHandleSource, getNodeData, mdastTextContentHandle, parseExpression, parseToHtml, renderHandle, serializeHandle, serializeMdastHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../satteri_napi.wasi-browser.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { applyCommandsAndConvertToHastHandle, applyCommandsToHandle, applyCommandsToMdastHandle, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createMdastHandle, createMdxHastHandle, createMdxMdastHandle, dropHandle, getHandleSource, getNodeData, mdastTextContentHandle, parseExpression, parseToHtml, renderHandle, serializeHandle, serializeMdastHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../index.js";
|
package/dist/binding.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { applyCommandsAndConvertToHastHandle, applyCommandsToHandle, applyCommandsToMdastHandle, compileHandle, compileMdx, convertMdastToHastHandle, createHastHandle, createMdastHandle, createMdxHastHandle, createMdxMdastHandle, dropHandle, getHandleSource, getNodeData, mdastTextContentHandle, parseExpression, parseToHtml, renderHandle, serializeHandle, serializeMdastHandle, setNodeData, textContentHandle, walkHandle, walkMdastHandle, } from "../index.js";
|
package/dist/command-buffer.js
CHANGED
|
@@ -40,7 +40,6 @@ export function classifyReturn(value) {
|
|
|
40
40
|
return "structured_node";
|
|
41
41
|
throw new Error("Invalid return value from visitor: must have raw, rawHtml, or type");
|
|
42
42
|
}
|
|
43
|
-
// CommandBuffer
|
|
44
43
|
const INITIAL_SIZE = 4096;
|
|
45
44
|
const encoder = new TextEncoder();
|
|
46
45
|
const EMPTY_U8 = new Uint8Array(0);
|
package/dist/compile.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Top-level compile functions, the primary public API.
|
|
3
|
-
*
|
|
4
|
-
* Both MDAST and HAST arenas stay in Rust memory via opaque handles.
|
|
5
|
-
* Only matched nodes and mutation commands cross the NAPI boundary.
|
|
6
|
-
*/
|
|
7
1
|
import type { MdastPluginDefinition, HastPluginDefinition } from "./plugin.js";
|
|
2
|
+
import type { MdastNode, HastNode } from "./types.js";
|
|
8
3
|
/** Configuration for static subtree collapsing during MDX compilation. */
|
|
9
4
|
export interface OptimizeStaticConfig {
|
|
10
5
|
component: string;
|
|
@@ -12,11 +7,69 @@ export interface OptimizeStaticConfig {
|
|
|
12
7
|
wrapPropValue?: boolean;
|
|
13
8
|
ignoreElements?: string[];
|
|
14
9
|
}
|
|
10
|
+
/** Parser feature toggles. All default to their documented value when omitted. */
|
|
11
|
+
export interface Features {
|
|
12
|
+
/** GFM: tables, footnotes, strikethrough, task lists, blockquote tags. Default: true. */
|
|
13
|
+
gfm?: boolean;
|
|
14
|
+
/** Frontmatter: YAML (`--- ... ---`) and TOML (`+++ ... +++`). Default: true. */
|
|
15
|
+
frontmatter?: boolean;
|
|
16
|
+
/** Math blocks and inline math. Default: true. */
|
|
17
|
+
math?: boolean;
|
|
18
|
+
/** Heading attributes (`# text { #id .class }`). Default: true. */
|
|
19
|
+
headingAttributes?: boolean;
|
|
20
|
+
/** Colon-delimited container directive blocks (`:::`). Default: false. */
|
|
21
|
+
directive?: boolean;
|
|
22
|
+
/** Superscript (`^super^`). Default: false. */
|
|
23
|
+
superscript?: boolean;
|
|
24
|
+
/** Subscript (`~sub~`). Default: false. */
|
|
25
|
+
subscript?: boolean;
|
|
26
|
+
/** Obsidian-style wikilinks (`[[link]]`). Default: false. */
|
|
27
|
+
wikilinks?: boolean;
|
|
28
|
+
/** Smart punctuation à la SmartyPants. Default: false. */
|
|
29
|
+
smartPunctuation?: boolean;
|
|
30
|
+
/** Definition lists. Default: false. */
|
|
31
|
+
definitionList?: boolean;
|
|
32
|
+
}
|
|
15
33
|
export interface CompileOptions {
|
|
16
34
|
mdastPlugins?: MdastPluginDefinition[];
|
|
17
35
|
hastPlugins?: HastPluginDefinition[];
|
|
18
|
-
|
|
36
|
+
features?: Features;
|
|
19
37
|
filename?: string;
|
|
20
38
|
}
|
|
21
|
-
export
|
|
22
|
-
|
|
39
|
+
export interface MdxCompileOptions extends CompileOptions {
|
|
40
|
+
optimizeStatic?: OptimizeStaticConfig;
|
|
41
|
+
/** Place to import automatic JSX runtimes from (e.g. "react", "preact"). Default: "react". */
|
|
42
|
+
jsxImportSource?: string;
|
|
43
|
+
/** Whether to keep JSX instead of compiling it to functions. Default: false. */
|
|
44
|
+
jsx?: boolean;
|
|
45
|
+
/** JSX runtime: "automatic" (default) or "classic". */
|
|
46
|
+
jsxRuntime?: "automatic" | "classic";
|
|
47
|
+
/** Enable development mode. Default: false. */
|
|
48
|
+
development?: boolean;
|
|
49
|
+
/** Place to import the component provider from. */
|
|
50
|
+
providerImportSource?: string;
|
|
51
|
+
/** Pragma for JSX in classic runtime (default: "React.createElement"). */
|
|
52
|
+
pragma?: string;
|
|
53
|
+
/** Pragma for JSX fragments in classic runtime (default: "React.Fragment"). */
|
|
54
|
+
pragmaFrag?: string;
|
|
55
|
+
/** Where to import the pragma from in classic runtime (default: "react"). */
|
|
56
|
+
pragmaImportSource?: string;
|
|
57
|
+
}
|
|
58
|
+
export declare function markdownToHtml(source: string, options?: CompileOptions): string | Promise<string>;
|
|
59
|
+
export declare function mdxToJs(source: string, options?: MdxCompileOptions): string | Promise<string>;
|
|
60
|
+
/** Parse Markdown source into a materialized mdast tree. */
|
|
61
|
+
export declare function markdownToMdast(source: string, options?: {
|
|
62
|
+
features?: Features;
|
|
63
|
+
}): MdastNode;
|
|
64
|
+
/** Parse MDX source into a materialized mdast tree. */
|
|
65
|
+
export declare function mdxToMdast(source: string, options?: {
|
|
66
|
+
features?: Features;
|
|
67
|
+
}): MdastNode;
|
|
68
|
+
/** Convert Markdown source to a materialized hast tree. */
|
|
69
|
+
export declare function markdownToHast(source: string, options?: {
|
|
70
|
+
features?: Features;
|
|
71
|
+
}): HastNode;
|
|
72
|
+
/** Convert MDX source to a materialized hast tree. */
|
|
73
|
+
export declare function mdxToHast(source: string, options?: {
|
|
74
|
+
features?: Features;
|
|
75
|
+
}): HastNode;
|
package/dist/compile.js
CHANGED
|
@@ -1,37 +1,54 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Top-level compile functions, the primary public API.
|
|
3
|
-
*
|
|
4
|
-
* Both MDAST and HAST arenas stay in Rust memory via opaque handles.
|
|
5
|
-
* Only matched nodes and mutation commands cross the NAPI boundary.
|
|
6
|
-
*/
|
|
7
1
|
import { visitHastHandle, resolveSubscriptions } from "./hast/hast-visitor.js";
|
|
8
2
|
import { visitMdastHandle, resolveMdastSubscriptions, } from "./mdast/mdast-visitor.js";
|
|
9
|
-
import { parseToHtml, compileMdx, createHastHandle, createMdxHastHandle, renderHandle, compileHandle, applyCommandsToHandle, dropHandle, createMdastHandle, createMdxMdastHandle, applyCommandsToMdastHandle, convertMdastToHastHandle, applyCommandsAndConvertToHastHandle, getHandleSource, } from "
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
import { parseToHtml, compileMdx, createHastHandle, createMdxHastHandle, renderHandle, compileHandle, applyCommandsToHandle, dropHandle, createMdastHandle, createMdxMdastHandle, applyCommandsToMdastHandle, convertMdastToHastHandle, applyCommandsAndConvertToHastHandle, getHandleSource, serializeHandle, serializeMdastHandle, } from "#binding";
|
|
4
|
+
import { MdastReader } from "./mdast/mdast-reader.js";
|
|
5
|
+
import { materializeMdastTree } from "./mdast/mdast-materializer.js";
|
|
6
|
+
import { HastReader } from "./hast/hast-reader.js";
|
|
7
|
+
import { materializeHastTree } from "./hast/hast-materializer.js";
|
|
8
|
+
function featuresToNative(features) {
|
|
9
|
+
if (!features)
|
|
10
|
+
return undefined;
|
|
11
|
+
// Build object with only defined keys to satisfy exactOptionalPropertyTypes
|
|
12
|
+
const result = {};
|
|
13
|
+
if (features.gfm !== undefined)
|
|
14
|
+
result.gfm = features.gfm;
|
|
15
|
+
if (features.frontmatter !== undefined)
|
|
16
|
+
result.frontmatter = features.frontmatter;
|
|
17
|
+
if (features.math !== undefined)
|
|
18
|
+
result.math = features.math;
|
|
19
|
+
if (features.headingAttributes !== undefined)
|
|
20
|
+
result.headingAttributes = features.headingAttributes;
|
|
21
|
+
if (features.directive !== undefined)
|
|
22
|
+
result.directive = features.directive;
|
|
23
|
+
if (features.superscript !== undefined)
|
|
24
|
+
result.superscript = features.superscript;
|
|
25
|
+
if (features.subscript !== undefined)
|
|
26
|
+
result.subscript = features.subscript;
|
|
27
|
+
if (features.wikilinks !== undefined)
|
|
28
|
+
result.wikilinks = features.wikilinks;
|
|
29
|
+
if (features.smartPunctuation !== undefined)
|
|
30
|
+
result.smartPunctuation = features.smartPunctuation;
|
|
31
|
+
if (features.definitionList !== undefined)
|
|
32
|
+
result.definitionList = features.definitionList;
|
|
33
|
+
return result;
|
|
16
34
|
}
|
|
17
35
|
function runMdastPluginsOnHandle(handle, plugins, filename) {
|
|
18
|
-
const instances = initPlugins(plugins);
|
|
19
36
|
let pendingCommands = null;
|
|
20
37
|
const source = getHandleSource(handle);
|
|
21
38
|
let i = 0;
|
|
22
39
|
const runNext = () => {
|
|
23
|
-
while (i <
|
|
40
|
+
while (i < plugins.length) {
|
|
24
41
|
const idx = i++;
|
|
25
|
-
const
|
|
26
|
-
const subs = resolveMdastSubscriptions(
|
|
27
|
-
const result = visitMdastHandle(handle,
|
|
42
|
+
const plugin = plugins[idx];
|
|
43
|
+
const subs = resolveMdastSubscriptions(plugin);
|
|
44
|
+
const result = visitMdastHandle(handle, plugin, subs, source, filename);
|
|
28
45
|
if (result instanceof Promise) {
|
|
29
46
|
return result.then((r) => {
|
|
30
|
-
applyMdastResult(r, idx,
|
|
47
|
+
applyMdastResult(r, idx, plugins.length, handle);
|
|
31
48
|
return runNext();
|
|
32
49
|
});
|
|
33
50
|
}
|
|
34
|
-
applyMdastResult(result, idx,
|
|
51
|
+
applyMdastResult(result, idx, plugins.length, handle);
|
|
35
52
|
}
|
|
36
53
|
return { handle, pendingCommands };
|
|
37
54
|
};
|
|
@@ -47,18 +64,16 @@ function runMdastPluginsOnHandle(handle, plugins, filename) {
|
|
|
47
64
|
}
|
|
48
65
|
return runNext();
|
|
49
66
|
}
|
|
50
|
-
// HAST plugin runner (handle-based)
|
|
51
67
|
function runHastPluginsOnHandle(handle, plugins, source, filename) {
|
|
52
68
|
if (plugins.length === 0)
|
|
53
69
|
return;
|
|
54
|
-
const instances = initPlugins(plugins);
|
|
55
70
|
let i = 0;
|
|
56
71
|
const runNext = () => {
|
|
57
|
-
while (i <
|
|
58
|
-
const
|
|
72
|
+
while (i < plugins.length) {
|
|
73
|
+
const plugin = plugins[i];
|
|
59
74
|
i++;
|
|
60
|
-
const subs = resolveSubscriptions(
|
|
61
|
-
const result = visitHastHandle(handle,
|
|
75
|
+
const subs = resolveSubscriptions(plugin);
|
|
76
|
+
const result = visitHastHandle(handle, plugin, subs, source, filename);
|
|
62
77
|
if (result instanceof Promise) {
|
|
63
78
|
return result.then(runNext);
|
|
64
79
|
}
|
|
@@ -66,12 +81,47 @@ function runHastPluginsOnHandle(handle, plugins, source, filename) {
|
|
|
66
81
|
};
|
|
67
82
|
return runNext();
|
|
68
83
|
}
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
// Public API
|
|
85
|
+
function mdxOptionsToNative(opts) {
|
|
86
|
+
const hasAny = opts.optimizeStatic ||
|
|
87
|
+
opts.jsxImportSource !== undefined ||
|
|
88
|
+
opts.jsx !== undefined ||
|
|
89
|
+
opts.jsxRuntime !== undefined ||
|
|
90
|
+
opts.development !== undefined ||
|
|
91
|
+
opts.providerImportSource !== undefined ||
|
|
92
|
+
opts.pragma !== undefined ||
|
|
93
|
+
opts.pragmaFrag !== undefined ||
|
|
94
|
+
opts.pragmaImportSource !== undefined;
|
|
95
|
+
if (!hasAny)
|
|
96
|
+
return undefined;
|
|
97
|
+
const result = {};
|
|
98
|
+
if (opts.optimizeStatic)
|
|
99
|
+
result.optimizeStatic = opts.optimizeStatic;
|
|
100
|
+
if (opts.jsxImportSource !== undefined)
|
|
101
|
+
result.jsxImportSource = opts.jsxImportSource;
|
|
102
|
+
if (opts.jsx !== undefined)
|
|
103
|
+
result.jsx = opts.jsx;
|
|
104
|
+
if (opts.jsxRuntime !== undefined)
|
|
105
|
+
result.jsxRuntime = opts.jsxRuntime;
|
|
106
|
+
if (opts.development !== undefined)
|
|
107
|
+
result.development = opts.development;
|
|
108
|
+
if (opts.providerImportSource !== undefined)
|
|
109
|
+
result.providerImportSource = opts.providerImportSource;
|
|
110
|
+
if (opts.pragma !== undefined)
|
|
111
|
+
result.pragma = opts.pragma;
|
|
112
|
+
if (opts.pragmaFrag !== undefined)
|
|
113
|
+
result.pragmaFrag = opts.pragmaFrag;
|
|
114
|
+
if (opts.pragmaImportSource !== undefined)
|
|
115
|
+
result.pragmaImportSource = opts.pragmaImportSource;
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
export function markdownToHtml(source, options = {}) {
|
|
119
|
+
const { mdastPlugins = [], hastPlugins = [], features, filename = "<unknown>" } = options;
|
|
120
|
+
const nativeFeatures = featuresToNative(features);
|
|
71
121
|
if (mdastPlugins.length === 0 && hastPlugins.length === 0) {
|
|
72
|
-
return parseToHtml(source);
|
|
122
|
+
return parseToHtml(source, nativeFeatures);
|
|
73
123
|
}
|
|
74
|
-
const handleResult = createHastHandleFromMdast(source, mdastPlugins, false, filename);
|
|
124
|
+
const handleResult = createHastHandleFromMdast(source, mdastPlugins, false, filename, nativeFeatures);
|
|
75
125
|
const finish = (hastHandle) => {
|
|
76
126
|
const asyncResult = runHastPluginsOnHandle(hastHandle, hastPlugins, source, filename);
|
|
77
127
|
if (asyncResult instanceof Promise) {
|
|
@@ -90,13 +140,14 @@ export function compileMarkdownToHtml(source, options = {}) {
|
|
|
90
140
|
}
|
|
91
141
|
return finish(handleResult);
|
|
92
142
|
}
|
|
93
|
-
export function
|
|
94
|
-
const { mdastPlugins = [], hastPlugins = [],
|
|
95
|
-
const mdxOptions =
|
|
143
|
+
export function mdxToJs(source, options = {}) {
|
|
144
|
+
const { mdastPlugins = [], hastPlugins = [], features, filename = "<unknown>", ...mdxFields } = options;
|
|
145
|
+
const mdxOptions = mdxOptionsToNative(mdxFields);
|
|
146
|
+
const nativeFeatures = featuresToNative(features);
|
|
96
147
|
if (mdastPlugins.length === 0 && hastPlugins.length === 0) {
|
|
97
|
-
return compileMdx(source, mdxOptions);
|
|
148
|
+
return compileMdx(source, mdxOptions, nativeFeatures);
|
|
98
149
|
}
|
|
99
|
-
const handleResult = createHastHandleFromMdast(source, mdastPlugins, true, filename);
|
|
150
|
+
const handleResult = createHastHandleFromMdast(source, mdastPlugins, true, filename, nativeFeatures);
|
|
100
151
|
const finish = (hastHandle) => {
|
|
101
152
|
const asyncResult = runHastPluginsOnHandle(hastHandle, hastPlugins, source, filename);
|
|
102
153
|
if (asyncResult instanceof Promise) {
|
|
@@ -118,11 +169,17 @@ export function compileMdxToJs(source, options = {}) {
|
|
|
118
169
|
// Pipeline: parse → mdast plugins → hast conversion → hast plugins
|
|
119
170
|
// All arenas stay in Rust. No intermediate buffer copies to JS.
|
|
120
171
|
/** Parse + mdast plugins + convert to HAST handle. */
|
|
121
|
-
function createHastHandleFromMdast(source, mdastPlugins, mdx, filename
|
|
172
|
+
function createHastHandleFromMdast(source, mdastPlugins, mdx, filename,
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
174
|
+
nativeFeatures) {
|
|
122
175
|
if (mdastPlugins.length === 0) {
|
|
123
|
-
return mdx
|
|
176
|
+
return mdx
|
|
177
|
+
? createMdxHastHandle(source, nativeFeatures)
|
|
178
|
+
: createHastHandle(source, nativeFeatures);
|
|
124
179
|
}
|
|
125
|
-
const mdastHandle = mdx
|
|
180
|
+
const mdastHandle = mdx
|
|
181
|
+
? createMdxMdastHandle(source, nativeFeatures)
|
|
182
|
+
: createMdastHandle(source, nativeFeatures);
|
|
126
183
|
const mdastResult = runMdastPluginsOnHandle(mdastHandle, mdastPlugins, filename);
|
|
127
184
|
const convert = (r) => {
|
|
128
185
|
if (r.pendingCommands) {
|
|
@@ -135,3 +192,30 @@ function createHastHandleFromMdast(source, mdastPlugins, mdx, filename) {
|
|
|
135
192
|
}
|
|
136
193
|
return convert(mdastResult);
|
|
137
194
|
}
|
|
195
|
+
// Step-by-step API: individual pipeline stages with materialized trees
|
|
196
|
+
/** Parse Markdown source into a materialized mdast tree. */
|
|
197
|
+
export function markdownToMdast(source, options = {}) {
|
|
198
|
+
const handle = createMdastHandle(source, featuresToNative(options.features));
|
|
199
|
+
const buf = serializeMdastHandle(handle);
|
|
200
|
+
return materializeMdastTree(new MdastReader(buf));
|
|
201
|
+
}
|
|
202
|
+
/** Parse MDX source into a materialized mdast tree. */
|
|
203
|
+
export function mdxToMdast(source, options = {}) {
|
|
204
|
+
const handle = createMdxMdastHandle(source, featuresToNative(options.features));
|
|
205
|
+
const buf = serializeMdastHandle(handle);
|
|
206
|
+
return materializeMdastTree(new MdastReader(buf));
|
|
207
|
+
}
|
|
208
|
+
/** Convert Markdown source to a materialized hast tree. */
|
|
209
|
+
export function markdownToHast(source, options = {}) {
|
|
210
|
+
const handle = createHastHandle(source, featuresToNative(options.features));
|
|
211
|
+
const buf = serializeHandle(handle);
|
|
212
|
+
dropHandle(handle);
|
|
213
|
+
return materializeHastTree(new HastReader(buf));
|
|
214
|
+
}
|
|
215
|
+
/** Convert MDX source to a materialized hast tree. */
|
|
216
|
+
export function mdxToHast(source, options = {}) {
|
|
217
|
+
const handle = createMdxHastHandle(source, featuresToNative(options.features));
|
|
218
|
+
const buf = serializeHandle(handle);
|
|
219
|
+
dropHandle(handle);
|
|
220
|
+
return materializeHastTree(new HastReader(buf));
|
|
221
|
+
}
|
|
@@ -51,7 +51,8 @@ export function materializeHastNode(reader, nodeId) {
|
|
|
51
51
|
typeName = `unknown(${nodeType})`;
|
|
52
52
|
break;
|
|
53
53
|
}
|
|
54
|
-
const
|
|
54
|
+
const position = reader.getPosition(nodeId);
|
|
55
|
+
const node = (position ? { type: typeName, position } : { type: typeName });
|
|
55
56
|
// _nodeId: non-enumerable internal reference
|
|
56
57
|
Object.defineProperty(node, "_nodeId", {
|
|
57
58
|
value: nodeId,
|
|
@@ -25,6 +25,19 @@ export declare class HastReader {
|
|
|
25
25
|
getSource(): string;
|
|
26
26
|
/** Read a substring from the string pool by byte offset and length. */
|
|
27
27
|
getString(offset: number, len: number): string;
|
|
28
|
+
/** Get position data for a node. */
|
|
29
|
+
getPosition(nodeId: number): {
|
|
30
|
+
start: {
|
|
31
|
+
offset: number;
|
|
32
|
+
line: number;
|
|
33
|
+
column: number;
|
|
34
|
+
};
|
|
35
|
+
end: {
|
|
36
|
+
offset: number;
|
|
37
|
+
line: number;
|
|
38
|
+
column: number;
|
|
39
|
+
};
|
|
40
|
+
} | undefined;
|
|
28
41
|
/** Get the node_type byte for a given node ID. */
|
|
29
42
|
getNodeType(nodeId: number): number;
|
|
30
43
|
/** Get child node IDs for a given node. */
|