@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-marked

npm · 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.ts JSDoc, 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-call MarkdownOptions override 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 document or window; 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 sanitize on, every raw HTML token in the source (<script>, <b>, an onerror attribute) 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). Only http:, https:, mailto:, tel:, relative URLs and fragments are emitted. sanitize: false passes 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: false per call or in the config gives plain CommonMark.

  • Honored options: gfm, sanitize, breaks (a single newline in a paragraph becomes <br>), linkTarget (_blank also adds rel="noopener noreferrer"). components is a React-only concept and is ignored here. Headings get slug ids (duplicates get -2, -3, …) and result.toc lists them, so anchors resolve; headingIds: false in the config turns both off. Fenced code carries a language-* 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, and ![alt](src) an <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, the onerror is gone, and the link's href is neutralized. Verify in the browser: no alert fires, and DevTools shows no <script>, onerror, or javascript: in the rendered DOM. Never render untrusted content with sanitize: 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.