sveltedown
v1.0.0
Published
Render dynamic markdown with Svelte
Maintainers
Readme
sveltedown
A Svelte component for rendering markdown. Inspired by react-markdown.
Check out the demo.
Feature highlights
- Super easy: You can render markdown in your application in seconds!
- Customizable: Pass your own snippets in to control what's rendered
- Huge plugin ecosystem: Built on
remarkandrehype - Compliant: 100% to CommonMark, 100% to GFM with a plugin
Contents
Getting started
Install the package:
pnpm i sveltedown # or npm, yarnImport and use the component:
<script lang="ts">
import { Markdown } from 'sveltedown';
</script>
<Markdown content="# Hello, world!" />When should I use this?
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.
API
This package exports two components and one function:
MarkdownMarkdownAsync(experimental, exported fromsveltedown/experimental-async)defaultUrlTransform
It also exports the following additional TypeScript types:
OptionsURLTransform- All of the types from
svehast
Components
Markdown
The 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!
MarkdownAsync
If 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.
Functions
defaultUrlTransform
By 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.
Types
This package exports a number of types.
Options
The options you can pass to Markdown and MarkdownAsync.
content: The markdown content.string | undefinedremarkPlugins: Remark plugins to run prior to transforming themdasttohastrehypePlugins: Rehype plugins to run prior to rendering thehastto the DOMremarkParseOptions: Options to pass toremark-parse(the plugin that parses your content tomdast)remarkRehypeOptions: Options to pass toremark-rehype(the plugin that translatesmdasttohast)skipHtml: Ignore HTML in markdown completely. Defaults tofalse.boolean | undefinedurlTransform: Transform URLs in HTML attributes (href, ping, src, etc.). Defaults todefaultUrlTransform.
Notes:
- Both
remarkPluginsandrehypePluginsare of typePluggableList. This means there are a few ways you can register them:- If the plugin receives no options, it can be passed in as a root-level array item:
[myOptionlessPlugin] - If the plugin takes options, you should pass a tuple of the plugin and the options:
[[myPlugin, myPluginOptions]] - If you have multiple plugins, it would look like this:
[myOptionlessPlugin, [myPlugin, myPluginOptions]]
- If the plugin receives no options, it can be passed in as a root-level array item:
- If all of the
remarkandrehypestuff is confusing, that's fine -- there's a section later explaining the markdown processing pipeline
URLTransform
Is 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.
Renderer
The 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.
RendererArg
The argument a custom renderer accepts:
tagNameis the HTML/SVG tag name to renderpropsare the props. Typically you should spread these onto the element you're renderingchildrenis the snippet you need to render as a child. It will beundefinedfor void elements like<img>.nodeis the original and unmodifiedhastnode
A 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.
Renderers
A map of all HTML/SVG tag names that Svelte can render to their corresponding Renderer definition.
Examples
Use custom renderers
<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><h1>
Neato <strong>burrito</strong>!
</h1>Plugins
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-remarkandawesome-rehype— selection of the most awesome projects- List of remark plugins and list of rehype plugins — list of all plugins
remark-pluginandrehype-plugintopics — any tagged repo on GitHub
Syntax
sveltedown 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.
Architecture
When you pass a string to Markdown, it passes through a pipeline before it becomes the content you see on the screen:
- First,
remark-parseparses your markdown string into amdast, the ast representation of markdown - Next, your remark plugins are run on this
mdast - Then,
remark-rehypeconverts yourmdasttohast, a html ast - Then, your
rehypeplugins run on thishast - Finally, the
hastis converted to Svelte, which renders it
