@molecule/app-markdown-marked
Provider bond · markdown · App (browser) · v1.0.2 · Apache-2.0
Markdown rendering provider backed by marked: CommonMark + GFM to sanitized HTML with a table of contents, in Node or the browser (build-time static sites included)
npm install @molecule/app-markdown-markednpm · Source on GitHub · Implements @molecule/app-markdown
How it works
@molecule/app-markdown-marked is a provider bond on the app (browser) side: it implements the markdown core interface (@molecule/app-markdown) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { requireProvider, setProvider } from '@molecule/app-markdown'
import { provider } from '@molecule/app-markdown-marked'
setProvider(provider) // once, at startup (bonds.ts) — or in a build script
const { html, toc } = requireProvider().render('## Hello\n\n| a | b |\n| - | - |\n| 1 | 2 |')
// html === '<h2 id="hello">Hello</h2>\n<table>…</table>'
// toc === [{ id: 'hello', text: 'Hello', level: 2 }]Works with: @molecule/app-markdown
Live demos
Running apps built with Molecule that install this package — open the app or study the template.
Reference
Markdown provider for @molecule/app-markdown, backed by marked:
CommonMark + GitHub Flavored Markdown rendered to an HTML string,
synchronously, with no DOM and no UI framework. The same provider serves
a browser preview and a Node build step, so a static site generator can
render posts at build time with the bond the app already uses.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { requireProvider, setProvider } from '@molecule/app-markdown'
import { provider } from '@molecule/app-markdown-marked'
setProvider(provider) // once, at startup (bonds.ts) — or in a build script
const { html, toc } = requireProvider().render('## Hello\n\n| a | b |\n| - | - |\n| 1 | 2 |')
// html === '<h2 id="hello">Hello</h2>\n<table>…</table>'
// toc === [{ id: 'hello', text: 'Hello', level: 2 }]
Type
provider
Installation
npm install @molecule/app-markdown-marked @molecule/app-markdown marked
API
Interfaces
MarkedConfig
Provider-specific configuration for createProvider. These are the
defaults baked into a provider instance; per-call MarkdownOptions (from
@molecule/app-markdown) override the ones they share (sanitize, gfm,
breaks, linkTarget).
interface MarkedConfig {
/**
* Whether to sanitize by default (the XSS gate). When `true` (default) raw
* HTML in the source is escaped to inert text and dangerous link/image URL
* schemes (`javascript:`, `data:`, `vbscript:`, `file:`) are dropped. When
* `false`, raw HTML passes through untouched and every URL scheme is kept —
* only for fully trusted, app-authored content. Defaults to `true`.
*/
sanitize?: boolean
/**
* Whether to enable GitHub Flavored Markdown (tables, strikethrough, task
* lists, autolinks) by default. Defaults to `true`.
*/
gfm?: boolean
/**
* Whether a single newline inside a paragraph becomes a `<br>` by default.
* Defaults to `false`.
*/
breaks?: boolean
/**
* Default `target` for rendered links. `_blank` also adds
* `rel="noopener noreferrer"`. Defaults to no `target` attribute.
*/
linkTarget?: '_blank' | '_self'
/**
* Whether headings get a slug `id` (so `result.toc` anchors resolve).
* Defaults to `true`.
*/
headingIds?: boolean
/**
* Custom slug function for heading ids. Defaults to a lowercase, hyphenated
* slug of the heading's text; duplicate slugs in one document get a `-2`,
* `-3`, … suffix regardless of the function used.
*/
slugify?: (text: string) => string
}
Functions
escapeHtml(text)
Escapes text for safe inclusion in HTML.
function escapeHtml(text: string): string
text— Raw text.
Returns: The escaped text.
slugify(text)
Generates a URL-friendly slug from heading text: lowercase, letters, digits, hyphens; whitespace becomes a single hyphen.
function slugify(text: string): string
text— Heading text.
Returns: The slug (may be empty when the text has no word characters).
isSafeUrl(href)
Decides whether a link or image URL is safe to emit. Relative URLs,
fragments and the protocols in SAFE_PROTOCOLS pass; anything with
another scheme (javascript:, data:, vbscript:, file:, …) does not.
The scheme check strips control characters and whitespace first, so the
classic java\tscript: bypass does not work.
function isSafeUrl(href: string): boolean
href— The URL from the markdown source.
Returns: true when the URL may be emitted as-is.
createProvider(config)
Creates a markdown provider backed by marked.
function createProvider(config?: MarkedConfig): MarkdownProvider
config— Provider defaults; per-callMarkdownOptionsoverride the shared ones.
Returns: A MarkdownProvider for @molecule/app-markdown.
Constants
provider
The default provider: sanitize on, GFM on, no target, heading ids on.
const provider: MarkdownProvider
Core Interface
Implements @molecule/app-markdown interface.
Bond Wiring
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/app-markdown'
import { provider } from '@molecule/app-markdown-marked'
export function setupMarkdownMarked(): void {
setProvider(provider)
}
Injection Notes
Requirements
Peer dependencies:
@molecule/app-markdown^1.0.1
Runtime Dependencies
-
@molecule/app-markdown -
marked -
Works in Node. Nothing here touches
documentorwindow; render at build time (node scripts/build.mjs) exactly as in the browser. This is the provider to bond for prerendered and static sites. -
Sanitization is by construction, and on by default. With
sanitizeon, every raw HTML token in the source (<script>,<b>, anonerrorattribute) is escaped to inert text, and a link or image whose URL has a dangerous scheme (javascript:,data:,vbscript:,file:) loses its URL (the link text stays). Onlyhttp:,https:,mailto:,tel:, relative URLs and fragments are emitted.sanitize: falsepasses raw HTML and every scheme through: reserve it for fully trusted, app-authored content, never for user- or model-generated markdown. -
GFM is on by default (tables,
~~strikethrough~~,- [x]task lists, bare-URL autolinks).gfm: falseper call or in the config gives plain CommonMark. -
Honored options:
gfm,sanitize,breaks(a single newline in a paragraph becomes<br>),linkTarget(_blankalso addsrel="noopener noreferrer").componentsis a React-only concept and is ignored here. Headings get slugids (duplicates get-2,-3, …) andresult.toclists them, so anchors resolve;headingIds: falsein the config turns both off. Fenced code carries alanguage-*class, highlighter-ready; colorizing is the app's concern. -
Malformed markdown never throws. An unclosed
**, a broken[link](or a half-open code fence renders as readable text.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- Common markdown renders as real HTML wherever the app shows it (chat
reply, comment, note, README preview):
#..######become<h1>..<h6>,**bold**a<strong>,*italic*an<em>,-/1.lists become<ul>/<ol>of<li>,[text](url)an<a>anchor, and a fenced code block a<pre><code>— not the raw*/backticks shown as literal text. - GFM extras render when enabled (
gfm, default on): a pipe table| a | b |becomes a real<table>with header + rows, andan<img>. - The output tracks the source: editing the markdown updates the preview
live — change a heading's text and its
<h#>updates; add a list item and a new<li>appears. No stale or one-shot render. - SECURITY (XSS) — user- or model-supplied markdown is sanitized
(
sanitize, default on, the XSS gate): markdown containing a raw<script>alert(1)</script>, an<img src=x onerror=alert(1)>, or a[click](javascript:alert(1))link must NOT execute and must NOT produce active markup — the script is stripped, theonerroris gone, and the link'shrefis neutralized. Verify in the browser: no alert fires, and DevTools shows no<script>,onerror, orjavascript:in the rendered DOM. Never render untrusted content withsanitize: false. - Malformed markdown degrades gracefully — an unclosed
**, a broken[link](, or a half-open code fence renders as readable text and never throws, blanks the view, or breaks the surrounding page.