Native Shell
Native Shell은 모든 플랫폼의 앱 안에서 만들어지는 AI UI입니다
브라우저용 Markdown 렌더러: CommonMark 652/652, 플러그인 GFM 지원, 의존성 없이 11.04KB gzip, 기본 안전
Markdown renderer for the browser: CommonMark 652/652, GFM as a plugin, zero dependencies, 11.04 KB gzip, safe by default.
개발 도구TypeScript · JavaScriptGitHub @MichelePolo온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Lightweight, zero-dependency, CommonMark-compliant Markdown renderer for the browser. Extensible, Tailwind-friendly, semantic tokens. MIT.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Lightweight Markdown renderer for the browser.
Website, docs and playground: https://michelepolo.github.io/mdlib-pure/ · for AI agents: llms.txt
javascript: URLs blocked, DOM output without HTML parsingNot on the npm registry yet: npm install mdlib-pure does not work. Until
the first release there, install the tarball attached to the
GitHub release:
npm install https://github.com/MichelePolo/mdlib-pure/releases/download/mdlib-pure-v0.1.1/mdlib-pure-0.1.1.tgz
npm records the URL in your package.json and the tarball's checksum in
package-lock.json, so npm ci works on CI and for your colleagues. Imports
are the same as they will be from the registry (from 'mdlib-pure',
from 'mdlib-pure/gfm'…).
To build the tarball yourself instead (Node 22.18 or later):
git clone https://github.com/MichelePolo/mdlib-pure.git
cd mdlib-pure
npm ci # dev tools only: the package itself has no dependencies
npm pack # builds dist/, writes mdlib-pure-0.1.1.tgz
cd ../my-app
npm install ../mdlib-pure/mdlib-pure-0.1.1.tgz
The framework wrappers are not on a registry either, and have GitHub
releases too: Blazor (the .nupkg, not on
nuget.org) and Angular (a tarball, not on npm).
See Framework wrappers.
import { createMarkdown } from 'mdlib-pure'
const md = createMarkdown()
md.render('# Hello *world*') // '<h1>Hello <em>world</em></h1>\n'
md.renderTo(element, '# Hello') // builds DOM nodes directly
md.parse('# Hello') // mdast-style AST
createMarkdown({
plugins: [], // see "Plugins"
html: false, // recognize raw HTML (trusted input only, or with sanitize)
sanitize: undefined, // (html) => html, e.g. DOMPurify.sanitize
allowUrl: isSafeUrl, // (url, 'link' | 'image') => boolean
decodeEntity: undefined,// named-entity decoder (default: the browser's DOM)
classes: {}, // CSS classes per node type
attributes: undefined, // (node) => extra attributes, e.g. data-sourcepos
highlight: undefined, // (code, lang, meta) => html | VNodes | null
positions: false, // line/column/offset on block nodes
})
Full reference: docs/api.md.
import { createMarkdown } from 'mdlib-pure'
import { gfm } from 'mdlib-pure/gfm'
const md = createMarkdown({ plugins: gfm() })
// turn parts off: gfm({ footnotes: false, alerts: false })
Also exported one by one: table, strikethrough, autolink, tasklist,
footnotes, tagfilter, alerts.
Bring your own highlighter through highlight(code, lang, meta):
import hljs from 'highlight.js/lib/core'
createMarkdown({
// a string is trusted HTML for inside <code>
highlight: (code, lang) => (lang && hljs.getLanguage(lang) ? hljs.highlight(code, { language: lang }).value : null),
})
// shiki returns a whole <pre>: it replaces the block
createMarkdown({ highlight: (code, lang) => shiki.codeToHtml(code, { lang: lang ?? 'text', theme: 'github-dark' }) })
Return null for the default rendering, or VNodes (h('span', { class: 'kw' }, 'let'))
to stay on the DOM-safe path of renderTo(). Strings are treated as trusted
HTML: with untrusted input, use a highlighter that escapes code (all of the
above do) or set sanitize.
import { createElement } from 'react'
import { toElements } from 'mdlib-pure/adapter'
<article className="md">{toElements(md.toVNodes(src), createElement, 'react')}</article>
// Vue: toElements(vnodes, h, 'vue') · Preact: toElements(vnodes, h, 'preact')
The adapter applies each framework's conventions (className, uncontrolled
checkboxes, Vue's array children, dangerouslySetInnerHTML/innerHTML for
trusted raw HTML from highlight). Tested against the real React 19 on all
CommonMark and GFM examples. With html: true, inline HTML arrives in
fragments: use md.render() + sanitize on a container instead.
Named entities (©, –…) are decoded through the DOM to keep the
core small. Outside the browser, import the full table:
import { decodeEntity } from 'mdlib-pure/entities'
const md = createMarkdown({ decodeEntity })
Theme with semantic tokens. Import mdlib-pure/theme.css and put
class="md" on the container. Everything is customizable through --md-*
tokens; neutrals follow your text color and dark mode follows color-scheme.
See docs/tokens.md.
:root { --md-color-link: oklch(55% 0.2 260); --md-font-heading: "Inter", sans-serif; }
Tailwind CSS v4. The integration file loads the theme under your
utilities and exposes the tokens as theme values (text-md-link,
bg-md-surface…). Tailwind's preflight changes nothing inside .md: list
markers, link underlines and inline images stay as in the plain theme.
@import "tailwindcss";
@import "mdlib-pure/tailwind.css";
Or skip the theme: default output is plain semantic HTML, so @tailwindcss/typography works as is:
<article class="prose dark:prose-invert">…</article>
Or add utility classes per node type:
createMarkdown({
classes: {
heading: 'font-semibold tracking-tight',
heading1: 'text-3xl',
paragraph: 'my-4 leading-7',
link: 'text-primary underline-offset-4 hover:underline',
inlineCode: 'rounded bg-muted px-1 font-mono text-sm',
code: 'rounded-lg bg-muted p-4 overflow-x-auto',
},
})
Tailwind scans your sources for class names, so define the map in your own code (as above), not in a file under
node_modules.
const mark = {
name: 'mark',
delimiters: [{ char: '=', type: 'mark', sizes: [2] }],
render: { mark: (node, r) => r.h('mark', null, r.children(node)) },
}
createMarkdown({ plugins: [mark] }).render('==highlighted==')
// '<p><mark>highlighted</mark></p>\n'
A plugin can provide:
| Field | For |
|---|---|
block | new block syntax (math fences, containers, tables) |
inline | syntax starting at a trigger character (@mention, :emoji:) |
delimiters | symmetric runs handled like emphasis (~~, ==) |
transform | changes to the AST after parsing (heading ids, TOC) |
render | HTML output per node type, including core nodes |
For a complete application example, see the podcast plugin guide and copyable source: a split editor with participant completion, planned section durations, interactive tasks and linked supporting documents, using fictional people and content. Try the interactive sample.
First-party plugins:
mdlib-pure/headings: GitHub-style heading ids, optional # permalinks,
and toc(root) for a table of contents. For untrusted input, use
headings({ prefix: 'user-content-' }) (see docs/security.md).mdlib-pure/container: fenced containers (::: warning Title … :::,
nestable with longer fences).mdlib-pure/math: $inline$, $$display$$ and $$ blocks. The output
works as-is with KaTeX auto-render or MathJax, or pass
math({ render: (tex, display) => katex.renderToString(tex, { displayMode: display }) }).mdlib-pure/mark: ==highlight== → <mark>.mdlib-pure/mermaid: ```mermaid diagrams, with no dependency on
Mermaid: renderMermaid(el, { load: () => import('mermaid') }) loads it
only when a page has diagrams, themed from the tokens (light and dark).mdlib-pure/element: <mdlib-markdown> and <mdlib-editor> Web
Components (an editor with live preview, styled by mdlib-pure/editor.css),
every plugin included: docs/elements.md.mdlib-pure/deflist: definition lists (Term / : Definition, Pandoc and
PHP Markdown Extra style) → <dl>.mdlib-pure/frontmatter: YAML (---) / TOML (+++) front matter, kept
out of the output; getFrontmatter(root) returns the raw text.The full guide, written for both humans and AI agents, is
AGENTS.md. templates/plugin is a
complete, tested plugin to copy.
Both wrap the Web Components of mdlib-pure/element: the Blazor package
bundles them, the Angular package depends on mdlib-pure. Neither is on a
public registry yet. They offer the same features with each framework's idioms: a
new plugin or element reaches both (the agents guide, rule 11).
MdlibPure.Blazor wraps the Web Components for
every render mode: <MarkdownEditor @bind-Value>, <Markdown> and an AST
service. Not on nuget.org: download the .nupkg from the
GitHub release
and add it from a local package feed. Version 0.1.1 adds
PodcastEditor, with typed episode
binding, configurable participants, supporting documents and planned duration.
ngx-mdlib-pure wraps the Web Components as
standalone components: <mdlib-editor [(value)]> (also a form control for
ngModel and reactive forms), <mdlib-markdown [source]>,
provideMdlibPure() and a MarkdownService for the AST, the table of
contents and HTML; ngx-mdlib-pure/podcast adds
PodcastEditor
(since 0.2.0). Server-side rendering and hydration work. Not on npm,
like mdlib-pure itself: install both tarballs from the GitHub releases
(angular-v0.2.0,
mdlib-pure-v0.1.1).
Node 22, render to HTML string, median of 15 runs (full method and results; differences under ~10% are noise):
| CommonMark examples (exact) | spec.txt MB/s | README MB/s | min + gzip | |
|---|---|---|---|---|
| mdlib-pure | 652/652 | 43.5 | 36.4 | 10.9 KB |
| marked | 503/652 | 44.6 | 32.7 | 14.0 KB |
| markdown-it | 649/652 | 35.6 | 22.0 | 40.6 KB |
| commonmark.js | 652/652 | 47.4 | 48.2 | 46.8 KB |
With GFM: mdlib-pure + gfm() 39.6 / 25.1 MB/s, marked 38.8 / 27.2,
markdown-it 28.7 / 17.4 (mdlib-pure also does footnotes, task lists and
alerts).
Reproduce: npm run build && npm install --prefix bench && node bench/run.js.
html: false (default): HTML in the source is not recognized and renders as text.href/src passes through allowUrl. The default is an allowlist:
relative URLs, http:, https:, mailto:, tel:, sms:, plus
data:image/{png,gif,jpeg,webp} for images only.renderTo() builds DOM with createElement/createTextNode. Only when raw
HTML is present (you enabled html, or a plugin emits raw HTML) does it
assign innerHTML, after sanitize.Details and threat model: docs/security.md.
npm test # 1000+ tests, TypeScript runs natively in Node ≥ 22.18
npm run test:browser # DOM path, theme and Tailwind build in Chromium
npm run spec # CommonMark conformance report
npm run verify # typecheck + tests + invariants + size budgets
npm run build # dist/
Live demo: npm run build, serve the repository root with any static
server, open /demo/.
MIT © Michele Polo. Spec fixtures in test/fixtures keep their own
licenses (test data only, not published): see
test/fixtures/README.md.