mdxToJs(source, { optimizeStatic: { component: "div", prop: "dangerouslySetInnerHTML", wrapPropValue: true } });
```
--------------------------------
### Define and use a plugin with defineMdastPlugin
Source: https://satteri.bruits.org/docs/quick-start
Create a plugin object with a name and visitor functions. The visitor receives a read-only node view and a ctx object for mutations. This example strips inline code by replacing nodes.
```javascript
import { markdownToHtml, defineMdastPlugin } from "satteri";
const stripInlineCode = defineMdastPlugin({
name: "strip-inline-code",
inlineCode(node, ctx) {
ctx.replaceNode(node, { type: "text", value: node.value });
},
});
const { html } = markdownToHtml("Use `let` instead of `var`.", {
mdastPlugins: [stripInlineCode],
});
//
Use let instead of var.
```
--------------------------------
### Configure vite-plugin-satteri in vite.config.ts
Source: https://satteri.bruits.org/docs/vite
Add the plugin to `vite.config.ts` with the `features` option enabling GFM and frontmatter parsing. After this setup, any `.md` or `.mdx` file in the project becomes importable.
```typescript
import { defineConfig } from "vite";
import satteri from "vite-plugin-satteri";
export default defineConfig({
plugins: [
satteri({
features: {
gfm: true,
frontmatter: true,
},
}),
],
});
```
--------------------------------
### Heading with merged id and class attributes
Source: https://satteri.bruits.org/docs/features
Example showing how explicit id= and class= merge with # and . shorthands. The last id wins, classes accumulate.
```markdown
## Heading {.intro #x class=lead id=main}
```
--------------------------------
### Plugin entry: factory and conditional inclusion
Source: https://satteri.bruits.org/docs/plugins
Shows that plugin entries can be factories or false/null. The factory runs once per compile and receives context about the document. This example conditionally includes plugins based on isDev and sourceFormat.
```javascript
markdownToHtml(source, {
mdastPlugins: [
isDev && debugPlugin,
(ctx) => (ctx.sourceFormat === "mdx" ? mdxOnly : null),
],
});
```
--------------------------------
### mdxToJs usage example
Source: https://satteri.bruits.org/docs/entry-points
Imports mdxToJs from satteri and compiles an MDX string to JavaScript, extracting the compiled code from the result.
```typescript
import { mdxToJs } from "satteri";
const { code } = mdxToJs("# Hello\n\n
");
```
--------------------------------
### markdownToJs usage example
Source: https://satteri.bruits.org/docs/entry-points
Imports markdownToJs from satteri and compiles a Markdown string to JavaScript, extracting the compiled code from the result.
```typescript
import { markdownToJs } from "satteri";
const { code } = markdownToJs("# Hello\n\n{not an expression}");
```
--------------------------------
### Heading attributes basic usage and HTML output
Source: https://satteri.bruits.org/docs/features
Example of using heading attributes with `#` for id and `.` for class. The id and classes appear on the rendered heading.
```markdown
## My heading {#my-id .my-class}
```
```html
My heading
```
--------------------------------
### Import Markdown with frontmatter
Source: https://satteri.bruits.org/docs/vite
Import a Markdown file to get the rendered HTML as a string and parsed frontmatter. The default export and the named `html` export point to the same string, so either can be used.
```typescript
import postHtml, { frontmatter } from "./post.md";
document.getElementById("post").innerHTML = postHtml;
console.log(frontmatter.title);
```
--------------------------------
### Source positions (options: { position: true })
Source: https://satteri.bruits.org/docs/plugin-api
Visitors can read node.position (the { start, end } source range) only when the plugin opts in with options: { position: true }. Position tracking is off by default due to parsing cost.
```APIDOC
## Source positions
### Description
Visitors read `node.position` (the `{ start, end }` source range) only when the plugin opts in with `options: { position: true }`. Tracking positions adds a measurable parsing cost (~15% of parse), so it is off by default. `node.position` is `undefined` unless some plugin in the pipeline requests it.
### Usage
```js
const plugin = defineMdastPlugin({
name: "needs-source-range",
options: { position: true },
heading(node) {
console.log(node.position); // { start, end } instead of undefined
},
});
```
### Notes
A single opted-in plugin enables positions for the whole pipeline, so a later plugin sees them too.
```
--------------------------------
### Register multiple filtered visitors with defineHastPlugin
Source: https://satteri.bruits.org/docs/plugin-api
Shows how to register multiple filtered visitors for the same node type by passing an array. The example registers visitors for h1-h3 headings and for links.
```typescript
const plugin = defineHastPlugin({
name: "headings-and-links",
element: [
{
filter: ["h1", "h2", "h3"],
visit(node, ctx) {
/* headings */
},
},
{
filter: ["a"],
visit(node, ctx) {
/* links */
},
},
],
});
```
--------------------------------
### Code block data.lang: markdown example
Source: https://satteri.bruits.org/docs/divergences
Shows a fenced code block with a language and meta string. Sätteri keeps the language as data.lang on the HAST element, while remark-rehype drops it.
```markdown
```rust title=foo.rs
fn main() {}
```
```
--------------------------------
### Factory that skips based on file URL
Source: https://satteri.bruits.org/docs/plugin-api
A factory can return a skip value (null) to drop the plugin for certain documents. This example only rewrites versions for CHANGELOG.md files.
```typescript
const onlyChangelogs = (ctx) => (ctx.fileURL?.pathname.endsWith("/CHANGELOG.md") ? rewriteVersions : null);
markdownToHtml(source, { mdastPlugins: [onlyChangelogs, myPlugin] });
```
--------------------------------
### Using a skip value in a plugin list
Source: https://satteri.bruits.org/docs/plugin-api
A skip value (null, undefined, or false) leaves that entry out. This example conditionally includes a plugin based on a boolean.
```typescript
markdownToHtml(source, { mdastPlugins: [isDev && debugPlugin, myPlugin] });
```
--------------------------------
### htmlToHast and hastToHtml usage
Source: https://satteri.bruits.org/docs/entry-points
Shows converting an HTML string to a HAST tree with htmlToHast, then serializing a HAST element back to HTML with hastToHtml. The second example demonstrates round-tripping a full document.
```javascript
import { hastToHtml, htmlToHast } from "satteri";
hastToHtml({ type: "element", tagName: "p", properties: {}, children: [{ type: "text", value: "hi" }] });
// '
hi
'
hastToHtml(htmlToHast("
hi
"));
// '
hi
'
```
--------------------------------
### Heading attributes merging id and class
Source: https://satteri.bruits.org/docs/features
Example showing that explicit `id=` and `class=` merge with `#`/`.` shorthands. The last id wins, classes accumulate in source order.
```markdown
## Heading {.intro #x class=lead id=main}
```
```html
Heading
```
--------------------------------
### Missing positions on GFM autolinks: markdown example
Source: https://satteri.bruits.org/docs/divergences
Shows a GFM autolink preceded by an opening bracket. Sätteri reports a position for the autolink, while remark-gfm emits it with no position.
```markdown
[www.example.com
```
--------------------------------
### Get plain JS tree with markdownToMdast
Source: https://satteri.bruits.org/docs/plugin-api
Converts markdown source to a plain JavaScript MDAST tree that you own. Use markdownToMdast or markdownToHast from the entry points.
```js
import { markdownToMdast } from "satteri";
const tree = markdownToMdast(source); // plain objects, yours to keep
```
--------------------------------
### Convert markdown to a plain JavaScript tree with markdownToMdast
Source: https://satteri.bruits.org/docs/plugin-api
Use markdownToMdast to get a plain JavaScript object tree of the whole document. The returned tree is independent of Sätteri's internal Rust state and can be kept freely.
```javascript
import { markdownToMdast } from "satteri";
const tree = markdownToMdast(source); // plain objects, yours to keep
```
--------------------------------
### evaluate
Source: https://satteri.bruits.org/docs/entry-points
Compile and run MDX in one step, returning the module's exports including the default component.
```APIDOC
## evaluate
### Description
Compile and run MDX in one step, returning the module's exports (including `default`, the component). Requires a JSX runtime.
### Signature
`evaluate(source: string, options: { jsx: Function, jsxs: Function, Fragment: ComponentType }) => Promise<{ default: ComponentType, [key: string]: any }>`
### Parameters
- **source** (string) - Required - The MDX source string to compile and evaluate.
- **options** (object) - Required - Must include a JSX runtime (e.g., `react/jsx-runtime`).
### Example
```js
import { evaluate } from "satteri";
import * as runtime from "react/jsx-runtime";
const { default: Content } = evaluate("# Hello\n\n
", { ...runtime });
```
```
--------------------------------
### Plugin order: mdastPlugins and hastPlugins
Source: https://satteri.bruits.org/docs/plugins
Shows how to pass multiple plugins to markdownToHtml. Plugins run in array order, MDAST stage first, then HAST.
```javascript
markdownToHtml(source, {
mdastPlugins: [emojis, unwrapImages],
hastPlugins: [externalLinks, headingIds],
});
```
--------------------------------
### evaluate: Compile and run MDX
Source: https://satteri.bruits.org/docs/entry-points
Usage of the evaluate entry point. It compiles and runs MDX in one step, returning the module's exports (including `default`, the component). Pass a JSX runtime as the second argument.
```js
import { evaluate } from "satteri";
import * as runtime from "react/jsx-runtime";
const { default: Content } = evaluate("# Hello\n\n
", { ...runtime });
```
--------------------------------
### markdownToMdast usage example
Source: https://satteri.bruits.org/docs/entry-points
Parses a Markdown string into an Mdast tree. The example shows accessing the tree's children to inspect the heading type and depth.
```javascript
import { markdownToMdast } from "satteri";
const tree = markdownToMdast("# Hello");
tree.children[0].type; // "heading"
tree.children[0].depth; // 1
```
--------------------------------
### MDAST node example
Source: https://satteri.bruits.org/docs/asts
Example of an MDAST node representing a heading with depth and text children. Use this shape when operating on Markdown-level structures like headings, lists, and images.
```js
{
type: "heading",
depth: 2,
children: [{ type: "text", value: "Hello" }],
}
```
--------------------------------
### HAST node example
Source: https://satteri.bruits.org/docs/asts
Example of a HAST node representing an HTML element with tagName, properties, and children. Use this shape when operating on HTML-level structures like adding attributes or wrapping elements.
```js
{
type: "element",
tagName: "h2",
properties: { id: "hello" },
children: [{ type: "text", value: "Hello" }],
}
```
--------------------------------
### Heading with quoted attribute value
Source: https://satteri.bruits.org/docs/features
Example of quoting an attribute value that contains spaces.
```markdown
# Title {data-label="hello world"}
```
--------------------------------
### evaluate
Source: https://satteri.bruits.org/docs/entry-points
Compiles and runs MDX in one step, returning the module's exports (including default, the component). Requires a JSX runtime.
```APIDOC
## evaluate
### Description
Compile and run MDX in one step, returning the module's exports (including `default`, the component). Pass a JSX runtime.
### Method
Function
### Signature
```ts
evaluate(source: string, options?: EvaluateOptions): Promise
;
```
### Parameters
- **source** (string) - Required - The MDX source string.
- **options** (object) - Required - Options object, must include a JSX runtime (e.g., `...runtime` from `react/jsx-runtime`).
### Return Value
Returns a `Promise` resolving to an object containing the module's exports, including `default` (the component).
### Example
```js
import { evaluate } from "satteri";
import * as runtime from "react/jsx-runtime";
const { default: Content } = evaluate("# Hello\n\n", { ...runtime });
```
```
--------------------------------
### Passing a bundle of plugins
Source: https://satteri.bruits.org/docs/plugin-api
A bundle is an array of entries, nested as deeply as you like. The bundle's plugins run in their own order at the bundle's position.
```javascript
import { typography } from "some-package"; // an array of plugins
markdownToHtml(source, { mdastPlugins: [typography, myPlugin] });
```
--------------------------------
### Heading attributes with quoted values
Source: https://satteri.bruits.org/docs/features
Example of wrapping a value in `"` or `'` quotes when it needs to contain spaces.
```markdown
# Title {data-label="hello world"}
```
```html
Title
```
--------------------------------
### mdastPlugins / hastPlugins
Source: https://satteri.bruits.org/docs/plugin-api
Options that take a list of plugin entries. An entry can be a definition, a factory returning one, a bundle of entries, or a skip value. The shapes compose: a factory may return a bundle, a bundle may contain factories, and a skip value is accepted wherever an entry is.
```APIDOC
## mdastPlugins / hastPlugins
### Description
Options that accept a list of plugin entries. Each entry can be a plugin definition, a factory returning an entry, a bundle (array) of entries, or a skip value (`null`, `undefined`, `false`). The shapes compose recursively.
### Type Definitions
```ts
type MdastPluginEntry = MdastPluginDefinition | ((ctx: PluginFactoryContext) => MdastPluginEntry) | readonly MdastPluginEntry[] | null | undefined | false;
type MdastPluginList = readonly MdastPluginEntry[];
```
`HastPluginEntry` and `HastPluginList` are the HAST-side equivalents.
### Entry Types
- **Definition** - Used as-is for every document. Use when the plugin keeps no state between compiles.
- **Factory** - Called once per compile; anything it closes over resets for each document. May return a bundle or a single plugin.
- **Bundle** - An array of entries, nested as deeply as needed. Plugins in a bundle run in their own order at the bundle's position.
- **Skip value** (`null`, `undefined`, `false`) - Leaves that entry out. Any other invalid value is rejected with an error.
### Example
```js
const headingAnchors = () => {
const slugs = new Set();
return [collectSlugs(slugs), linkSlugs(slugs)];
};
markdownToHtml(source, { mdastPlugins: [headingAnchors] });
```
```
--------------------------------
### Plugin entry as nested array (bundle)
Source: https://satteri.bruits.org/docs/plugins
Shows a plugin entry that is an array of plugins, imported from a package. The bundle's plugins keep their own order and run at the bundle's position, equivalent to spreading the array.
```javascript
import { typography } from "some-package"; // an array of plugins
markdownToHtml(source, {
mdastPlugins: [typography, unwrapImages],
});
```
--------------------------------
### Configure and import MDX with Preact
Source: https://satteri.bruits.org/docs/vite
Configure the MDX JSX runtime to use Preact and then import an MDX file as a component. The plugin compiles MDX with `development: true` in `serve` and production runtime in `build` by default; override with `mdx: { development: false }`.
```typescript
import { defineConfig } from "vite";
import satteri from "vite-plugin-satteri";
export default defineConfig({
plugins: [
satteri({
mdx: {
jsxImportSource: "preact",
},
}),
],
});
```
```typescript
import { render } from "preact";
import Intro, { frontmatter } from "./intro.mdx";
render(, document.getElementById("root"));
```
--------------------------------
### Configure pnpm workspace for wasm32
Source: https://satteri.bruits.org/docs/installation
Add the wasm32 architecture to pnpm-workspace.yaml to opt in to the WASI build for browser usage.
```yaml
supportedArchitectures:
cpu:
- current
- wasm32
```
--------------------------------
### Tree functions
Source: https://satteri.bruits.org/docs/entry-points
Functions to get plain JavaScript ASTs without running plugins or rendering. Includes markdownToMdast, mdxToMdast, markdownToHast, mdxToHast, htmlToHast, and hastToHtml.
```APIDOC
## Tree functions
### Description
To get a plain JavaScript AST without running plugins or rendering, use the tree functions. Each parses the source and returns a materialized tree directly (not a result object), and accepts a `TreeOptions` object with a `features` and a `position` option.
### Functions
#### markdownToMdast
```ts
markdownToMdast(source: string, options?: TreeOptions): MdastNode;
```
Parses Markdown and returns an MDAST tree.
#### mdxToMdast
```ts
mdxToMdast(source: string, options?: TreeOptions): MdastNode;
```
Parses MDX and returns an MDAST tree.
#### markdownToHast
```ts
markdownToHast(source: string, options?: TreeOptions): HastNode;
```
Parses Markdown and returns a HAST tree.
#### mdxToHast
```ts
mdxToHast(source: string, options?: TreeOptions): HastNode;
```
Parses MDX and returns a HAST tree.
#### htmlToHast
```ts
htmlToHast(html: string): HastNode;
```
Parses HTML and returns a HAST tree.
#### hastToHtml
```ts
hastToHtml(tree: HastNode | HastNode[]): string;
```
Serializes a HAST tree to an HTML string.
### TreeOptions
```ts
interface TreeOptions {
features?: Features;
position?: boolean;
}
```
- **features** (Features) - Optional - Feature flags.
- **position** (boolean) - Optional - Whether to record node positions. Defaults to true. Pass `false` to skip recording `node.position` for better performance.
### Example
```js
import { markdownToMdast } from "satteri";
const tree = markdownToMdast("# Hello");
tree.children[0].type; // "heading"
tree.children[0].depth; // 1
```
### Notes
Pass `position: false` to skip recording `node.position`. Disabling positions can greatly increase performance and lower memory usage. The returned tree is plain objects, yours to keep.
```
--------------------------------
### Plugin entry as array bundle
Source: https://satteri.bruits.org/docs/plugins
Shows that a plugin list entry can be an array of plugins, allowing a package to export a bundle without spreading. The bundle's plugins keep their own order and run at the bundle's position.
```javascript
import { typography } from "some-package"; // an array of plugins
markdownToHtml(source, {
mdastPlugins: [typography, unwrapImages],
});
```