Ferromark / Node.js
Build a Node.js Content Pipeline
Choose the result your application needs: an HTML string, UTF-8 bytes, or HTML with document metadata. All operations are synchronous.
Render a file from bytes
import { toHtml } from "ferromark";
import { readFile } from "node:fs/promises";
const html = toHtml(await readFile("guide.md"));
console.log(html);
Every call that takes Markdown accepts markdown: string | Uint8Array, so the
Buffer that readFile(path) returns without an encoding goes in as it is.
Bytes render exactly like the string buffer.toString("utf8") returns, so a file
read as a Buffer produces the same HTML as the same file read with "utf8".
Get HTML, front matter, and headings
import { transform } from "ferromark";
const source = "---\ntitle: Installation\n---\n# Installation\n\nRun `npm install ferromark`.";
const page = transform(source, { frontMatter: true });
console.log(page.frontMatter); // 'title: Installation\n'
console.log(page.headings[0]);
// { level: 1, id: 'installation', text: 'Installation' }
console.log(page.html);
Front-matter extraction is opt-in, including for transform(). The result is
raw text between --- or +++ delimiters; parse it with the YAML or TOML library
your application uses. Headings arrive in document order for application-owned
navigation or outline processing.
See the shared pipeline concepts for output responsibilities.
Compose native transforms
Afterburner adds the publishing conveniences your content needs through an
ordered passes array. The engine parses once, runs the selected native
transforms, and renders the result. These passes are opt-in and run in array order.
import { toHtml } from "ferromark";
const html = toHtml('Ship "small improvements"... Fixes #42 :rocket:.', {
passes: [
{ kind: "typography", language: "en" },
{ kind: "githubReferences", repository: "sebastian-software/ferromark" },
{ kind: "emojiShortcodes" },
],
});
Typography gives the prose language-specific punctuation. GitHub references link supported issue, mention, and commit forms using the repository you supply; they never infer it or access the network. Known emoji shortcodes become Unicode emoji from the bundled dictionary. Unknown aliases remain unchanged. Code, math, existing links, and other protected syntax keep their content.
The same options work with transform(), byte output, reusable renderers, and
highlighter helpers. Do not combine passes with the separate typography
option; put typography in the array to choose its position. See
configuration and the
native transform reference
for supported forms and language rules.
The processing stages may feel familiar from Remark, but these are Ferromark APIs, not JavaScript Remark plugins. Arbitrary custom passes are available through the Rust transform API.
Reuse a prepared document for analysis
Use prepareDocument() or CodeCompiler.prepare() to parse once, then request
collectReferences() for link and image inventories, extractText() for plain
text and final heading sections, or projectMarkdown() for a static Markdown
export. These operations leave the handle available for JSX rendering and do
not rerun native passes or highlighting.
The document analysis guide includes examples,
release availability, UTF-8 source ranges, and the explicit trackReferences
setting required for reference collection after native passes.
Reuse a renderer for a collection
import { Renderer } from "ferromark";
const renderer = new Renderer({ headingIds: true, footnotes: true });
const pages = ["# First page", "# Next page"].map((source) => renderer.toHtml(source));
console.log(pages);
Options are fixed at construction. Keep one renderer per worker. It retains
parser scratch allocations while resetting headings, references, and footnotes
between documents. Each call returns its own HTML. Renderer provides
toHtml() and toHtmlBuffer(); use transform() when you need metadata.
Send UTF-8 HTML directly
import { toHtmlBuffer } from "ferromark";
import { createServer } from "node:http";
const server = createServer((_request, response) => {
response.setHeader("content-type", "text/html; charset=utf-8");
response.end(toHtmlBuffer("# Hello"));
});
server.listen(3000, "127.0.0.1");
toHtmlBuffer() returns a UTF-8 Node.js Buffer copied from the rendered
bytes. It avoids converting the HTML into a JavaScript string when the next
consumer wants bytes. Reusable renderers also expose renderer.toHtmlBuffer().
With Markdown read as bytes, toHtmlBuffer(await readFile(path)) takes bytes in
and returns bytes.
Deploy under a subpath
import { toHtml } from "ferromark";
const html = toHtml("[Guide](/guide/getting-started)", { linkBasePath: "/docs" });
console.log(html);
This enables v2 site routing: internal absolute links, image sources, and root-absolute raw HTML URLs use the base. Markdown links convert to index.html routes. Check representative pages when migrating from v1.
Next: syntax highlighting with Ferriki or deployment requirements.