sveltedownA Svelte component for rendering markdown. Inspired by react-markdown.
Check out the demo.
remark and rehypeInstall the package:
pnpm i sveltedown # or npm, yarn
Import and use the component:
<script lang="ts">
import { Markdown } from 'sveltedown';
</script>
<Markdown content="# Hello, world!" />
This package focusses on making it easy for beginners to safely use markdown in
Svelte. It provides sane defaults and options for rendering markdown in a dynamic context.
If you reach the end of the customization you can achieve with this component and want to strike
out on your own, have no fear! The implementation is actually quite simple, and you can take advantage
of svehast to build
your own markdown processing pipeline.
This package exports two components and one function:
MarkdownMarkdownAsync (experimental, exported from sveltedown/experimental-async)defaultUrlTransformIt also exports the following additional TypeScript types:
OptionsURLTransformsvehastMarkdownThe core export of this package. You can use it like this:
<Markdown content="# Hello, world!" />
content can be any markdown-formatted string.
It also supports custom renderers. Normally, the easiest way to declare these is as snippets that are direct children of Markdown:
<Markdown content="[a link to this package](https://npmjs.com/package/sveltedown)">
{#snippet a({ tagName, props, children, node })}
<a {...props} href="/haha-all-links-are-now-the-same">
{@render children()}
</a>
{/snippet}
</Hast>
Remember to render the children!
You can also pass snippets as arguments to the Markdown component (see RendererArg below for argument details):
{#snippet a({ tagName, props, children, node })}
<a {...props} href="/haha-all-links-are-now-the-same">
{@render children()}
</a>
{/snippet}
<Hast node={/* Root */} {a}/>
You can also map nodes to other nodes. For example, if you wanted to only ever render down to a h3, you could map headings 4-6 back to h3:
<Hast node={/* Root */} h4="h3" h5="h3" h6="h3">
That's pretty much it!
MarkdownAsyncIf you have an asynchronous plugin in your pipeline, regular Markdown will fail. MarkdownAsync will run your pipeline asynchronously, and is compatible with Svelte's experimental.async compiler option.
The API is the same as Markdown, save that it will suspend while rendering your content. You'll want to use a svelte:boundary with pending content:
<svelte:boundary>
<Markdown content="# Neato burrito">
{#snippet pending()}
<Skeleton />
{/snippet}
<svelte:boundary>
Most of the time, you should avoid asynchronous plugins. Many of them actually have a way to hoist the asynchronous work out of your Markdown pipeline. For example, when using Shiki to highlight code, you can instantiate a global highlighter instance, then share that instance between all of your plugin invocations, which can run synchronously.
defaultUrlTransformBy default, Markdown does what GitHub does with links. It allows the protocols http, https, irc, ircs, mailto, and xmpp, and URLs relative to the current protocol (such as /something). This function is exported
so that if you implement your own URL transform logic, you can reapply the default if necessary.
This package exports a number of types.
OptionsThe options you can pass to Markdown and MarkdownAsync.
content: The markdown content. string | undefinedremarkPlugins: Remark plugins to run prior to transforming the mdast to hastrehypePlugins: Rehype plugins to run prior to rendering the hast to the DOMremarkParseOptions: Options to pass to remark-parse (the plugin that parses your content to mdast)remarkRehypeOptions: Options to pass to remark-rehype (the plugin that translates mdast to hast)skipHtml: Ignore HTML in markdown completely. Defaults to false. boolean | undefinedurlTransform: Transform URLs in HTML attributes (href, ping, src, etc.). Defaults to defaultUrlTransform.Notes:
remarkPlugins and rehypePlugins are of type PluggableList. This means there are a few ways you can register them:
[myOptionlessPlugin][[myPlugin, myPluginOptions]][myOptionlessPlugin, [myPlugin, myPluginOptions]]remark and rehype stuff is confusing, that's fine -- there's a section later explaining the markdown processing pipelineURLTransformIs called every time a HTML property containing a URL is found. Has the opportunity to transform the URL or remove it. Receives the URL, the property the URL came from (src, href, etc.), and the node it came from.
RendererThe type of a custom renderer. This is either a HTML/SVG tag name (for remapping) or a Snippet accepting a RenderArg as its only argument.
RendererArgThe argument a custom renderer accepts:
tagName is the HTML/SVG tag name to renderprops are the props. Typically you should spread these onto the element you're renderingchildren is the snippet you need to render as a child. It will be undefined for void elements like <img>.node is the original and unmodified hast nodeA note on tagName: This is the name associated with the resolved renderer, not the one we started with. So if we started with a hast element with a tagName of h6, but h6 had been mapped to h3, the tag name passed to your custom renderer would be h3. If you need the original tag name, you can find it on the node prop, as that remains unchanged.
RenderersA map of all HTML/SVG tag names that Svelte can render to their corresponding Renderer definition.
<script lang="ts">
import { Markdown } from 'sveltedown';
const markdown = '# Neato _burrito_';
</script>
<Markdown content={markdown}>
<!--
In Svelte, declaring a snippet as the child of a component passes that snippet
to the component as a prop! You don't have to do it this way; you can also use
snippets declared elsewhere by passing them as props to the `Markdown` component.
`em` captures all `em` elements that would be rendered. We can replace them with
`strong` elements if we want to.
Snippets also receive a `tagName` argument (`em` in this case) and a `node` argument,
which is the original `hast` node. This can be useful if you need to do really fancy
things with your custom renderers.
-->
{#snippet em({ props, children })}
<strong>{@render children()}</strong>
{/snippet}
</Markdown>
<details>
<summary>Show equivalent HTML</summary>
<h1>
Neato <strong>burrito</strong>!
</h1>
</details>
```
I use unified, specifically remark for markdown and rehype for HTML, which are tools to transform content with plugins. Here are three good ways to find plugins:
awesome-remark and
awesome-rehype
— selection of the most awesome projectsremark-plugin and
rehype-plugin topics
— any tagged repo on GitHubsveltedown follows CommonMark, which standardizes the differences between
markdown implementations, by default.
Some syntax extensions are supported through plugins.
remark uses micromark under the hood for its parsing.
See its documentation for more information on markdown, CommonMark, and
extensions.
When you pass a string to Markdown, it passes through a pipeline before it becomes the content you see on the screen:
remark-parse parses your markdown string into a mdast, the ast representation of markdownmdastremark-rehype converts your mdast to hast, a html astrehype plugins run on this hasthast is converted to Svelte, which renders it