### Install satteri with bun Source: https://satteri.bruits.org/docs/quick-start Install the satteri package using bun. ```bash bun add satteri ``` -------------------------------- ### Install satteri with npm Source: https://satteri.bruits.org/docs/quick-start Install the satteri package using npm. ```bash npm install satteri ``` -------------------------------- ### Install satteri-expressive-code and satteri Source: https://satteri.bruits.org/docs/expressive-code Install the plugin and its peer dependency satteri together. ```bash npm install satteri-expressive-code satteri ``` -------------------------------- ### Install satteri with yarn Source: https://satteri.bruits.org/docs/quick-start Install the satteri package using yarn. ```bash yarn add satteri ``` -------------------------------- ### Install satteri with pnpm Source: https://satteri.bruits.org/docs/quick-start Install the satteri package using pnpm. ```bash pnpm add satteri ``` -------------------------------- ### Install vite-plugin-satteri with pnpm, npm, yarn, or bun Source: https://satteri.bruits.org/docs/vite Install the plugin and its peer dependency `satteri` using your preferred package manager. The `satteri` package is a peer dependency, so it must be installed alongside the plugin. ```bash pnpm add -D vite-plugin-satteri satteri ``` ```bash npm install --save-dev vite-plugin-satteri satteri ``` ```bash yarn add -D vite-plugin-satteri satteri ``` ```bash bun add --dev vite-plugin-satteri satteri ``` -------------------------------- ### Install satteri-expressive-code with pnpm, npm, yarn, or bun Source: https://satteri.bruits.org/docs/expressive-code Install the plugin and its peer dependency `satteri` using your preferred package manager. The `satteri` package is a peer dependency, so it must be installed alongside the plugin. ```bash pnpm add satteri-expressive-code satteri ``` ```bash npm install satteri-expressive-code satteri ``` ```bash yarn add satteri-expressive-code satteri ``` ```bash bun add satteri-expressive-code satteri ``` -------------------------------- ### Paragraph start in a task list item: markdown example Source: https://satteri.bruits.org/docs/divergences Shows a task list item with an emphasized first child. Sätteri always starts the paragraph after the checkbox, while remark-gfm pulls the start back over the checkbox only when the first child is text. ```markdown - [ ] _e_ ``` -------------------------------- ### Definition list syntax example Source: https://satteri.bruits.org/docs/features Example of the definition list syntax: a term line followed by colon-marked definitions. ```markdown Apple : Pomaceous fruit. : A tech company. ``` -------------------------------- ### markdownToHtml usage example Source: https://satteri.bruits.org/docs/entry-points Imports markdownToHtml from satteri and parses a Markdown string, destructuring the result to get html, frontmatter, and data. ```typescript import { markdownToHtml } from "satteri"; const { html, frontmatter, data } = markdownToHtml("# Hello, *world*"); // html === "

Hello, world

" ``` -------------------------------- ### Smart punctuation configuration example Source: https://satteri.bruits.org/docs/features Example of enabling only curly quotes while leaving dashes and ellipses alone. Omitted keys default to true. ```js // Curly quotes only; leave -- and ... alone. markdownToHtml(source, { features: { smartPunctuation: { quotes: true, dashes: false, ellipses: false } }, }); ``` -------------------------------- ### Full features configuration example Source: https://satteri.bruits.org/docs/features Shows the complete features object with all options, as used with markdownToHtml. By default, gfm and frontmatter are enabled. ```js import { markdownToHtml } from "satteri"; markdownToHtml(source, { features: { gfm: true, frontmatter: true, math: false, headingAttributes: false, directive: false, superscript: false, subscript: false, wikilinks: false, definitionList: false, smartPunctuation: false, rawHtml: false, }, }); ``` -------------------------------- ### Unknown directive example (tip) Source: https://satteri.bruits.org/docs/divergences Displays a generic directive with a title and content. Sätteri drops such nodes unless a plugin sets data.hName, while remark-directive wraps them in a div. This example is used to show the difference in handling unknown directives. ```text :::tip[Title] content ::: ``` -------------------------------- ### Raw HTML reparse example Source: https://satteri.bruits.org/docs/features Shows how raw HTML is reparsed into real HAST elements when rawHtml is enabled. The example converts a div with a paragraph inside. ```js import { markdownToHast } from "satteri"; const tree = markdownToHast(`
\n\n**hi**\n\n
`, { features: { rawHtml: true, }, }); //
is now a real element wrapping the parsed

hi

``` -------------------------------- ### Heading attributes with arbitrary key=value and bare key Source: https://satteri.bruits.org/docs/features Example of writing arbitrary attributes as `key=value` or bare `key` (rendered as `key=""`). ```markdown ### Note {#intro data-level=2 hidden} ``` ```html ``` -------------------------------- ### Smart punctuation example Source: https://satteri.bruits.org/docs/divergences A phrase with quoted text. Sätteri converts straight quotes to curly quotes across inline nodes, while remark-smartypants leaves them straight. This example is used to show the difference in smart punctuation handling. ```text a "_quoted_" word ``` -------------------------------- ### Definition list syntax example Source: https://satteri.bruits.org/docs/features Shows the pandoc / PHP Markdown Extra definition list syntax: a term line followed by colon-marked definitions. ```text Apple : Pomaceous fruit. : A tech company. ``` -------------------------------- ### Smart punctuation usage example Source: https://satteri.bruits.org/docs/features Example enabling only curly quotes, leaving dashes and ellipses alone. Omitted keys default to `true`. ```javascript // Curly quotes only; leave -- and ... alone. markdownToHtml(source, { features: { smartPunctuation: { quotes: true, dashes: false, ellipses: false } }, }); ``` -------------------------------- ### Heading with arbitrary attributes Source: https://satteri.bruits.org/docs/features Example of heading attributes with arbitrary key=value and bare key attributes. ```markdown ### Note {#intro data-level=2 hidden} ``` -------------------------------- ### Heading attributes example Source: https://satteri.bruits.org/docs/features Shows how trailing {...} is treated as attributes when not a valid expression, and as an expression when valid. The example includes a heading with an id and a heading with an expression. ```mdx # My heading {#my-id} # Welcome {name} ``` -------------------------------- ### Heading with id and class attributes Source: https://satteri.bruits.org/docs/features Example of heading attribute syntax using # for id and . for class. ```markdown ## My heading {#my-id .my-class} ``` -------------------------------- ### Configure optimizeStatic for mdxToJs (Astro and React styles) Source: https://satteri.bruits.org/docs/options Use these examples to configure the `optimizeStatic` option for `mdxToJs`. The first example uses Astro-style wrapping with a `Fragment` component and `set:html` prop. The second uses React-style wrapping with a `div` and `dangerouslySetInnerHTML`, requiring `wrapPropValue: true` to wrap the value as `{{ __html: "…" }}`. ```javascript // Astro-style: wraps static HTML in mdxToJs(source, { optimizeStatic: { component: "Fragment", prop: "set:html" } }); // React-style: wraps in
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], }); ```