satteri 0.1.0 → 0.1.3
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 +298 -4
- package/dist/command-buffer.js +1 -14
- package/dist/compile.d.ts +14 -9
- package/dist/compile.js +44 -36
- package/dist/hast/hast-materializer.js +0 -1
- package/dist/hast/hast-reader.d.ts +1 -1
- package/dist/hast/hast-reader.js +1 -2
- package/dist/hast/hast-visitor.d.ts +15 -15
- package/dist/hast/hast-visitor.js +7 -12
- package/dist/hast-types.d.ts +1 -3
- package/dist/hast-types.js +0 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -3
- package/dist/lazy-props.js +0 -1
- package/dist/mdast/mdast-materializer.js +0 -1
- package/dist/mdast/mdast-reader.js +0 -4
- package/dist/mdast/mdast-visitor.d.ts +19 -14
- package/dist/mdast/mdast-visitor.js +24 -36
- package/dist/mdast-types.d.ts +1 -3
- package/dist/mdast-types.js +0 -1
- package/dist/mdx-types.d.ts +140 -0
- package/dist/mdx-types.js +7 -0
- package/dist/plugin.d.ts +4 -6
- package/dist/plugin.js +0 -7
- package/dist/types.d.ts +4 -6
- package/dist/types.js +0 -5
- package/index.d.ts +21 -4
- package/index.js +54 -53
- package/package.json +16 -14
- package/satteri_napi.wasi-browser.js +2 -0
- package/satteri_napi.wasi.cjs +1 -0
- package/dist/command-buffer.js.map +0 -1
- package/dist/compile.js.map +0 -1
- package/dist/data-map.d.ts +0 -10
- package/dist/data-map.js +0 -26
- package/dist/data-map.js.map +0 -1
- package/dist/hast/hast-materializer.js.map +0 -1
- package/dist/hast/hast-reader.js.map +0 -1
- package/dist/hast/hast-visitor.js.map +0 -1
- package/dist/hast-types.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lazy-props.js.map +0 -1
- package/dist/mdast/mdast-materializer.js.map +0 -1
- package/dist/mdast/mdast-reader.js.map +0 -1
- package/dist/mdast/mdast-visitor.js.map +0 -1
- package/dist/mdast-types.js.map +0 -1
- package/dist/pipeline.d.ts +0 -29
- package/dist/pipeline.js +0 -87
- package/dist/pipeline.js.map +0 -1
- package/dist/plugin.js.map +0 -1
- package/dist/processor.d.ts +0 -33
- package/dist/processor.js +0 -49
- package/dist/processor.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/satteri_napi.linux-x64-gnu.node +0 -0
package/README.md
CHANGED
|
@@ -1,7 +1,301 @@
|
|
|
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
|
+
### `markdownToMdast(source: string)`
|
|
235
|
+
|
|
236
|
+
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.
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
import { markdownToMdast } from "satteri";
|
|
240
|
+
|
|
241
|
+
const tree = markdownToMdast("# Hello\n\nWorld");
|
|
242
|
+
// tree.children[0].type === "heading"
|
|
243
|
+
// tree.children[0].depth === 1
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### `mdxToMdast(source: string)`
|
|
247
|
+
|
|
248
|
+
Parse MDX and return a complete mdast tree.
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
const tree = mdxToMdast('<Component foo="bar" />');
|
|
252
|
+
// tree.children[0].type === "mdxJsxFlowElement"
|
|
253
|
+
// tree.children[0].name === "Component"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### `markdownToHast(source: string)`
|
|
257
|
+
|
|
258
|
+
Parse Markdown, convert to hast, and return a complete hast tree.
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
const tree = markdownToHast("# Hello\n\nWorld");
|
|
262
|
+
// tree.children[0].type === "element"
|
|
263
|
+
// tree.children[0].tagName === "h1"
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `mdxToHast(source: string)`
|
|
267
|
+
|
|
268
|
+
Parse MDX, convert to hast, and return a complete hast tree.
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
const tree = mdxToHast("<MyComponent />");
|
|
272
|
+
// tree.children[0].type === "mdxJsxFlowElement"
|
|
273
|
+
// tree.children[0].name === "MyComponent"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `defineMdastPlugin(definition: MdastPluginDefinition)`
|
|
277
|
+
|
|
278
|
+
Type-safe wrapper for MDAST plugin definitions.
|
|
279
|
+
|
|
280
|
+
### `defineHastPlugin(definition: HastPluginDefinition)`
|
|
281
|
+
|
|
282
|
+
Type-safe wrapper for HAST plugin definitions.
|
|
283
|
+
|
|
284
|
+
### `CompileOptions`
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
interface CompileOptions {
|
|
288
|
+
mdastPlugins?: MdastPluginDefinition[];
|
|
289
|
+
hastPlugins?: HastPluginDefinition[];
|
|
290
|
+
filename?: string;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// mdxToJs accepts MdxCompileOptions, which extends CompileOptions
|
|
294
|
+
interface MdxCompileOptions extends CompileOptions {
|
|
295
|
+
optimizeStatic?: OptimizeStaticConfig;
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## License
|
|
300
|
+
|
|
301
|
+
MIT
|
package/dist/command-buffer.js
CHANGED
|
@@ -8,9 +8,7 @@
|
|
|
8
8
|
* All multi-byte integers are little-endian to match native x86/ARM layout and
|
|
9
9
|
* avoid byte-swapping on the Rust side.
|
|
10
10
|
*/
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
11
|
// Command bytes (0x01–0x0F)
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
12
|
const CMD_REMOVE = 0x01;
|
|
15
13
|
const CMD_INSERT_BEFORE = 0x05;
|
|
16
14
|
const CMD_INSERT_AFTER = 0x06;
|
|
@@ -19,15 +17,11 @@ const CMD_APPEND_CHILD = 0x08;
|
|
|
19
17
|
const CMD_WRAP = 0x09;
|
|
20
18
|
const CMD_REPLACE = 0x0b;
|
|
21
19
|
const CMD_SET_PROPERTY = 0x0c;
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
20
|
// Payload types (0x10+, distinct range from commands)
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
21
|
const PAYLOAD_RAW_MARKDOWN = 0x10;
|
|
26
22
|
const PAYLOAD_RAW_HTML = 0x11;
|
|
27
23
|
const PAYLOAD_SERDE_JSON = 0x12;
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
24
|
// Value types for CMD_SET_PROPERTY (must match commands.rs PROP_* constants)
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
25
|
const PROP_STRING = 0;
|
|
32
26
|
const PROP_BOOL_TRUE = 1;
|
|
33
27
|
const PROP_BOOL_FALSE = 2;
|
|
@@ -46,9 +40,6 @@ export function classifyReturn(value) {
|
|
|
46
40
|
return "structured_node";
|
|
47
41
|
throw new Error("Invalid return value from visitor: must have raw, rawHtml, or type");
|
|
48
42
|
}
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
// CommandBuffer
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
43
|
const INITIAL_SIZE = 4096;
|
|
53
44
|
const encoder = new TextEncoder();
|
|
54
45
|
const EMPTY_U8 = new Uint8Array(0);
|
|
@@ -62,7 +53,6 @@ export class CommandBuffer {
|
|
|
62
53
|
this.view = new DataView(this.buffer);
|
|
63
54
|
this.bytes = new Uint8Array(this.buffer);
|
|
64
55
|
}
|
|
65
|
-
// -- Public command methods -----------------------------------------------
|
|
66
56
|
removeNode(nodeId) {
|
|
67
57
|
this.ensureCapacity(5);
|
|
68
58
|
this.writeU8(CMD_REMOVE);
|
|
@@ -154,7 +144,6 @@ export class CommandBuffer {
|
|
|
154
144
|
this.writeU32(encoded.length);
|
|
155
145
|
this.writeBytes(encoded);
|
|
156
146
|
}
|
|
157
|
-
// -- Result accessors -----------------------------------------------------
|
|
158
147
|
/** Return a Uint8Array view of the written bytes (no copy). */
|
|
159
148
|
getBuffer() {
|
|
160
149
|
return new Uint8Array(this.buffer, 0, this.offset);
|
|
@@ -170,7 +159,6 @@ export class CommandBuffer {
|
|
|
170
159
|
this.bytes = new Uint8Array(this.buffer);
|
|
171
160
|
this.offset = 0;
|
|
172
161
|
}
|
|
173
|
-
// -- Private helpers ------------------------------------------------------
|
|
174
162
|
writeStructuralCommand(cmd, nodeId, node) {
|
|
175
163
|
const v = node;
|
|
176
164
|
if (typeof v.raw === "string") {
|
|
@@ -192,7 +180,7 @@ export class CommandBuffer {
|
|
|
192
180
|
this.writeBytes(encoded);
|
|
193
181
|
}
|
|
194
182
|
else {
|
|
195
|
-
// Structured node
|
|
183
|
+
// Structured node, serialize as JSON
|
|
196
184
|
const json = JSON.stringify(node);
|
|
197
185
|
const encoded = encoder.encode(json);
|
|
198
186
|
this.ensureCapacity(10 + encoded.length);
|
|
@@ -228,4 +216,3 @@ export class CommandBuffer {
|
|
|
228
216
|
this.offset += data.length;
|
|
229
217
|
}
|
|
230
218
|
}
|
|
231
|
-
//# sourceMappingURL=command-buffer.js.map
|
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;
|
|
@@ -15,8 +10,18 @@ export interface OptimizeStaticConfig {
|
|
|
15
10
|
export interface CompileOptions {
|
|
16
11
|
mdastPlugins?: MdastPluginDefinition[];
|
|
17
12
|
hastPlugins?: HastPluginDefinition[];
|
|
18
|
-
optimizeStatic?: OptimizeStaticConfig;
|
|
19
13
|
filename?: string;
|
|
20
14
|
}
|
|
21
|
-
export
|
|
22
|
-
|
|
15
|
+
export interface MdxCompileOptions extends CompileOptions {
|
|
16
|
+
optimizeStatic?: OptimizeStaticConfig;
|
|
17
|
+
}
|
|
18
|
+
export declare function markdownToHtml(source: string, options?: CompileOptions): string | Promise<string>;
|
|
19
|
+
export declare function mdxToJs(source: string, options?: MdxCompileOptions): string | Promise<string>;
|
|
20
|
+
/** Parse Markdown source into a materialized mdast tree. */
|
|
21
|
+
export declare function markdownToMdast(source: string): MdastNode;
|
|
22
|
+
/** Parse MDX source into a materialized mdast tree. */
|
|
23
|
+
export declare function mdxToMdast(source: string): MdastNode;
|
|
24
|
+
/** Convert Markdown source to a materialized hast tree. */
|
|
25
|
+
export declare function markdownToHast(source: string): HastNode;
|
|
26
|
+
/** Convert MDX source to a materialized hast tree. */
|
|
27
|
+
export declare function mdxToHast(source: string): HastNode;
|
package/dist/compile.js
CHANGED
|
@@ -1,39 +1,27 @@
|
|
|
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 "../index.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return plugins.map((def) => ({
|
|
15
|
-
instance: def.createOnce(),
|
|
16
|
-
name: def.name,
|
|
17
|
-
}));
|
|
18
|
-
}
|
|
3
|
+
import { parseToHtml, compileMdx, createHastHandle, createMdxHastHandle, renderHandle, compileHandle, applyCommandsToHandle, dropHandle, createMdastHandle, createMdxMdastHandle, applyCommandsToMdastHandle, convertMdastToHastHandle, applyCommandsAndConvertToHastHandle, getHandleSource, serializeHandle, serializeMdastHandle, } from "../index.js";
|
|
4
|
+
import { ArenaReader } from "./mdast/mdast-reader.js";
|
|
5
|
+
import { materializeTree } from "./mdast/mdast-materializer.js";
|
|
6
|
+
import { HastReader } from "./hast/hast-reader.js";
|
|
7
|
+
import { materializeHastTree } from "./hast/hast-materializer.js";
|
|
19
8
|
function runMdastPluginsOnHandle(handle, plugins, filename) {
|
|
20
|
-
const instances = initPlugins(plugins);
|
|
21
9
|
let pendingCommands = null;
|
|
22
10
|
const source = getHandleSource(handle);
|
|
23
11
|
let i = 0;
|
|
24
12
|
const runNext = () => {
|
|
25
|
-
while (i <
|
|
13
|
+
while (i < plugins.length) {
|
|
26
14
|
const idx = i++;
|
|
27
|
-
const
|
|
28
|
-
const subs = resolveMdastSubscriptions(
|
|
29
|
-
const result = visitMdastHandle(handle,
|
|
15
|
+
const plugin = plugins[idx];
|
|
16
|
+
const subs = resolveMdastSubscriptions(plugin);
|
|
17
|
+
const result = visitMdastHandle(handle, plugin, subs, source, filename);
|
|
30
18
|
if (result instanceof Promise) {
|
|
31
19
|
return result.then((r) => {
|
|
32
|
-
applyMdastResult(r, idx,
|
|
20
|
+
applyMdastResult(r, idx, plugins.length, handle);
|
|
33
21
|
return runNext();
|
|
34
22
|
});
|
|
35
23
|
}
|
|
36
|
-
applyMdastResult(result, idx,
|
|
24
|
+
applyMdastResult(result, idx, plugins.length, handle);
|
|
37
25
|
}
|
|
38
26
|
return { handle, pendingCommands };
|
|
39
27
|
};
|
|
@@ -49,20 +37,16 @@ function runMdastPluginsOnHandle(handle, plugins, filename) {
|
|
|
49
37
|
}
|
|
50
38
|
return runNext();
|
|
51
39
|
}
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
// HAST plugin runner (handle-based)
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
40
|
function runHastPluginsOnHandle(handle, plugins, source, filename) {
|
|
56
41
|
if (plugins.length === 0)
|
|
57
42
|
return;
|
|
58
|
-
const instances = initPlugins(plugins);
|
|
59
43
|
let i = 0;
|
|
60
44
|
const runNext = () => {
|
|
61
|
-
while (i <
|
|
62
|
-
const
|
|
45
|
+
while (i < plugins.length) {
|
|
46
|
+
const plugin = plugins[i];
|
|
63
47
|
i++;
|
|
64
|
-
const subs = resolveSubscriptions(
|
|
65
|
-
const result = visitHastHandle(handle,
|
|
48
|
+
const subs = resolveSubscriptions(plugin);
|
|
49
|
+
const result = visitHastHandle(handle, plugin, subs, source, filename);
|
|
66
50
|
if (result instanceof Promise) {
|
|
67
51
|
return result.then(runNext);
|
|
68
52
|
}
|
|
@@ -70,7 +54,7 @@ function runHastPluginsOnHandle(handle, plugins, source, filename) {
|
|
|
70
54
|
};
|
|
71
55
|
return runNext();
|
|
72
56
|
}
|
|
73
|
-
export function
|
|
57
|
+
export function markdownToHtml(source, options = {}) {
|
|
74
58
|
const { mdastPlugins = [], hastPlugins = [], filename = "<unknown>" } = options;
|
|
75
59
|
if (mdastPlugins.length === 0 && hastPlugins.length === 0) {
|
|
76
60
|
return parseToHtml(source);
|
|
@@ -94,7 +78,7 @@ export function compileMarkdownToHtml(source, options = {}) {
|
|
|
94
78
|
}
|
|
95
79
|
return finish(handleResult);
|
|
96
80
|
}
|
|
97
|
-
export function
|
|
81
|
+
export function mdxToJs(source, options = {}) {
|
|
98
82
|
const { mdastPlugins = [], hastPlugins = [], optimizeStatic, filename = "<unknown>" } = options;
|
|
99
83
|
const mdxOptions = optimizeStatic ? { optimizeStatic } : undefined;
|
|
100
84
|
if (mdastPlugins.length === 0 && hastPlugins.length === 0) {
|
|
@@ -119,10 +103,8 @@ export function compileMdxToJs(source, options = {}) {
|
|
|
119
103
|
}
|
|
120
104
|
return finish(handleResult);
|
|
121
105
|
}
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
106
|
// Pipeline: parse → mdast plugins → hast conversion → hast plugins
|
|
124
107
|
// All arenas stay in Rust. No intermediate buffer copies to JS.
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
108
|
/** Parse + mdast plugins + convert to HAST handle. */
|
|
127
109
|
function createHastHandleFromMdast(source, mdastPlugins, mdx, filename) {
|
|
128
110
|
if (mdastPlugins.length === 0) {
|
|
@@ -141,4 +123,30 @@ function createHastHandleFromMdast(source, mdastPlugins, mdx, filename) {
|
|
|
141
123
|
}
|
|
142
124
|
return convert(mdastResult);
|
|
143
125
|
}
|
|
144
|
-
|
|
126
|
+
// Step-by-step API: individual pipeline stages with materialized trees
|
|
127
|
+
/** Parse Markdown source into a materialized mdast tree. */
|
|
128
|
+
export function markdownToMdast(source) {
|
|
129
|
+
const handle = createMdastHandle(source);
|
|
130
|
+
const buf = serializeMdastHandle(handle);
|
|
131
|
+
return materializeTree(new ArenaReader(buf));
|
|
132
|
+
}
|
|
133
|
+
/** Parse MDX source into a materialized mdast tree. */
|
|
134
|
+
export function mdxToMdast(source) {
|
|
135
|
+
const handle = createMdxMdastHandle(source);
|
|
136
|
+
const buf = serializeMdastHandle(handle);
|
|
137
|
+
return materializeTree(new ArenaReader(buf));
|
|
138
|
+
}
|
|
139
|
+
/** Convert Markdown source to a materialized hast tree. */
|
|
140
|
+
export function markdownToHast(source) {
|
|
141
|
+
const handle = createHastHandle(source);
|
|
142
|
+
const buf = serializeHandle(handle);
|
|
143
|
+
dropHandle(handle);
|
|
144
|
+
return materializeHastTree(new HastReader(buf));
|
|
145
|
+
}
|
|
146
|
+
/** Convert MDX source to a materialized hast tree. */
|
|
147
|
+
export function mdxToHast(source) {
|
|
148
|
+
const handle = createMdxHastHandle(source);
|
|
149
|
+
const buf = serializeHandle(handle);
|
|
150
|
+
dropHandle(handle);
|
|
151
|
+
return materializeHastTree(new HastReader(buf));
|
|
152
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BufferHeader } from "../types.js";
|
|
2
|
-
import type { MdxJsxAttribute, MdxJsxExpressionAttribute } from "
|
|
2
|
+
import type { MdxJsxAttribute, MdxJsxExpressionAttribute } from "../mdx-types.js";
|
|
3
3
|
export type { MdxJsxAttribute, MdxJsxExpressionAttribute };
|
|
4
4
|
export declare const HAST_ROOT = 0;
|
|
5
5
|
export declare const HAST_ELEMENT = 1;
|
package/dist/hast/hast-reader.js
CHANGED
|
@@ -21,7 +21,7 @@ const MDX_ATTR_BOOLEAN_PROP = 0;
|
|
|
21
21
|
const MDX_ATTR_LITERAL_PROP = 1;
|
|
22
22
|
const MDX_ATTR_EXPRESSION_PROP = 2;
|
|
23
23
|
const MDX_ATTR_SPREAD = 3;
|
|
24
|
-
// HastNode field offsets (same layout as MDAST
|
|
24
|
+
// HastNode field offsets (same layout as MDAST, shared binary format)
|
|
25
25
|
// id: u32 @ 0
|
|
26
26
|
// node_type: u8 @ 4
|
|
27
27
|
// _pad: [u8; 3] @ 5
|
|
@@ -277,4 +277,3 @@ export class HastReader {
|
|
|
277
277
|
return this.getString(ref.offset, ref.len);
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
|
-
//# sourceMappingURL=hast-reader.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type HastNode } from "./hast-materializer.js";
|
|
2
2
|
import type { HastRaw } from "../types.js";
|
|
3
3
|
import type { Element, Text, Comment, Doctype } from "hast";
|
|
4
|
-
import type { MdxJsxFlowElementHast, MdxJsxTextElementHast } from "
|
|
5
|
-
import type { MdxFlowExpressionHast, MdxTextExpressionHast } from "
|
|
6
|
-
import type { MdxjsEsmHast } from "
|
|
4
|
+
import type { MdxJsxFlowElementHast, MdxJsxTextElementHast } from "../mdx-types.js";
|
|
5
|
+
import type { MdxFlowExpressionHast, MdxTextExpressionHast } from "../mdx-types.js";
|
|
6
|
+
import type { MdxjsEsmHast } from "../mdx-types.js";
|
|
7
7
|
export type HastHandle = any;
|
|
8
8
|
/** ESTree-compatible Program node returned by `parseExpression()`. */
|
|
9
9
|
export type EstreeProgram = Record<string, any>;
|
|
@@ -15,19 +15,19 @@ export interface HastDiagnostic {
|
|
|
15
15
|
export interface HastVisitorContext {
|
|
16
16
|
readonly source: string;
|
|
17
17
|
readonly filename: string;
|
|
18
|
-
removeNode(node: HastNode): void;
|
|
19
|
-
replaceNode(node: HastNode
|
|
20
|
-
insertBefore(node: HastNode
|
|
21
|
-
insertAfter(node: HastNode
|
|
22
|
-
wrapNode(node: HastNode
|
|
23
|
-
prependChild(node: HastNode
|
|
24
|
-
appendChild(node: HastNode
|
|
25
|
-
setProperty(node: HastNode
|
|
18
|
+
removeNode(node: Readonly<HastNode>): void;
|
|
19
|
+
replaceNode(node: Readonly<HastNode>, newNode: HastNode): void;
|
|
20
|
+
insertBefore(node: Readonly<HastNode>, newNode: HastNode): void;
|
|
21
|
+
insertAfter(node: Readonly<HastNode>, newNode: HastNode): void;
|
|
22
|
+
wrapNode(node: Readonly<HastNode>, parentNode: HastNode): void;
|
|
23
|
+
prependChild(node: Readonly<HastNode>, childNode: HastNode): void;
|
|
24
|
+
appendChild(node: Readonly<HastNode>, childNode: HastNode): void;
|
|
25
|
+
setProperty(node: Readonly<HastNode>, key: string, value: unknown): void;
|
|
26
26
|
/** Collect the concatenated text of all descendant text nodes (like DOM textContent). */
|
|
27
|
-
textContent(node: HastNode): string;
|
|
27
|
+
textContent(node: Readonly<HastNode>): string;
|
|
28
28
|
report(opts: {
|
|
29
29
|
message: string;
|
|
30
|
-
node?: HastNode
|
|
30
|
+
node?: Readonly<HastNode>;
|
|
31
31
|
severity?: "error" | "warning" | "info";
|
|
32
32
|
}): void;
|
|
33
33
|
getDiagnostics(): HastDiagnostic[];
|
|
@@ -35,9 +35,9 @@ export interface HastVisitorContext {
|
|
|
35
35
|
/** A filtered visitor: Rust filters by tag/component name, only matched nodes cross the boundary. */
|
|
36
36
|
export interface HastFilteredVisitor<N extends HastNode = HastNode> {
|
|
37
37
|
filter: string[];
|
|
38
|
-
visit(node: N
|
|
38
|
+
visit(node: Readonly<N>, ctx: HastVisitorContext): HastNode | void | Promise<HastNode | void>;
|
|
39
39
|
}
|
|
40
|
-
type HastVisitorFn<N extends HastNode = HastNode> = (node: N
|
|
40
|
+
type HastVisitorFn<N extends HastNode = HastNode> = (node: Readonly<N>, ctx: HastVisitorContext) => HastNode | void | Promise<HastNode | void>;
|
|
41
41
|
export interface HastVisitorInstance {
|
|
42
42
|
element?: HastFilteredVisitor<Element> | HastFilteredVisitor<Element>[];
|
|
43
43
|
mdxJsxFlowElement?: HastFilteredVisitor<MdxJsxFlowElementHast> | HastFilteredVisitor<MdxJsxFlowElementHast>[];
|