Lit directive to render markdown. Similar to rehype-react or react-markdown.
- [x] safe by default (no dagenrous
unsafeHtml
vurnerable to XSS attacks) - [x] customizeable (pass your own redering functions instead of
<h2>
for## hi
) - [x] plugins
- [x] compliant (100% to CommonMark compliant)
$ npm i rehype-lit
import { renderMarkdown } from "rehype-lit";
class MyElement extends LitElement {
render() {
const markdown = `# Hello **World**`;
// …
return html`<div>${renderMarkdown(markdown)}</div>`;
// returns: <div><h1>Hello <strong>World</strong></div>
}
}
Here is an example that shows how to use a plugin (remark-gfm, which adds support for footnotes, strikethrough, tables, tasklists and URLs directly):
import { renderMarkdown } from "rehype-lit";
import remarkGfm from "remark-gfm";
const markdown = `
Todo:
- [ ] todo
- [x] done
A table:
| a | b |
| - | - |
`;
class MyElement extends LitElement {
render() {
// …
return html`<div>${renderMarkdown(markdown, [remarkGfm])}</div>`;
}
}
This example shows how to use a plugin and give it options. To do that, use an array with the plugin at the first place, and the options second. remark-gfm has an option to allow only double tildes for strikethrough:
import { renderMarkdown } from "rehype-lit";
import remarkGfm from "remark-gfm";
const markdown = "This ~is not~ strikethrough, but ~~this is~~!";
class MyElement extends LitElement {
render() {
// …
return html`<div>
${renderMarkdown(markdown, [[remarkGfm, { singleTilde: false }]])}
</div>`;
}
}
You can change how elements are rendered extending LitHastRenderer
.
The first generic argument of LitHastRenderer
denotes a type map of tagName
to expected hast
Node type, which is usually Element
:
import { renderMarkdown, LitHastRenderer } from "rehype-lit";
class MyRenderer extends LitHastRenderer<{ h2: Element }, {}> {
constructor() {
super(
{
h2: (node: Element) =>
html`<h2 style="color: red">${this.renderChildren(node)}</h2>`,
},
{}
);
}
}
const myRenderer = new MyRenderer();
class MyElement extends LitElement {
render() {
const markdown = `## Hello, I'm red`;
// …
return html`<div>${renderMarkdown(markdown, [], [], {}, myRenderer)}</div>`;
}
}
Sometimes you might prefer to to the markdown to TemplateResult
translation yourself. In that case, you can also instruct remarkRehype
to passThrough
some Markdown AST elements to your LitHastRenderer
. In that case you'll need to provide a Typemap from the node.type
to its Markdown AST Node Type. (E.g. from leafDirective
to LeafDirective
):
import { renderMarkdown, LitHastRenderer } from "rehype-lit";
class MyRenderer extends LitHastRenderer<{}, { leafDirective: LeafDirective }> {
constructor() {
super(
{},
{
leafDirective: (node) => {
if (node.name === "button") {
return html`<button>${this.renderChildren(node)}</button>`;
}
return html`<div>Unknown directive: ${node.name}</div>`;
},
}
);
}
}
const myRenderer = new MyRenderer();
class MyElement extends LitElement {
render() {
const markdown = `::button[i'm displayed as a button]`;
// …
return html`<div>${renderMarkdown(
markdown,
[remarkDirective],
[],
{ passThrough: ["leafDirective"] },
myRenderer
)}</div>`;
}
}
See example and try it out by using pnpm run dev
The following diagram show, where the arguments to renderMarkdown
are plugged into the unified
pipeline:
- The README and API is heavily inspired by
react-markdown
-
unified
remark
andrehype
infrastructure, which makes this easy.