DEV Community

Chris Jackson
Chris Jackson

Posted on • Updated on • Originally published at chrsjxn.io

Building a blog with Svelte: Adding Markdown

I started building a personal blog with Svelte a few weeks ago, cross posting to dev.to for more reach and community.

And it sucks!

I've been manually building content with Svelte components, to get the look and feel I want on my site. And then I have to translate those posts into Markdown for posting elsewhere.

So I'm going to build Markdown support for my Svelte site, starting with this post. I'll tell you how to add support to your Svelte site, and I'll compare the output on both platforms when I'm done!

Adding markdown-it

The first thing we want to do is add a library for Markdown parsing. I'm going to use markdown-it. It's seeing active development and the README has examples of rendering markdown in code, which I will need!

Step 1: Installing dependencies

markdown-it is an easy install via npm, but it does assume that you have a full node environment to fall back on. So I needed to install a few extra dependencies.

npm install --save markdown-it punycode
npm install --save-dev @rollup/plugin-json
Enter fullscreen mode Exit fullscreen mode

Step 2: Updating rollup.config.js

With dependencies installed, we can import the library into our Svelte app and try rendering basic Markdown. But rollup is going to complain because it can't import JSON, and it needs to be configured to support adding punycode into the browser bundle.

So let's fix that now.

To support JSON, we just need to add the rollup plugin (@rollup/plugin-json) with its default settings:

// In imports:
import json from '@rollup/plugin-json'

// In rollup config:
export default {
    //...
    plugins: [
        //...
        json(),
        //...
    ]
}
Enter fullscreen mode Exit fullscreen mode

And we also need to tell rollup to include the punycode version we just installed into our browser bundle:

// In plugins:
resolve({
    browser: true,
    dedupe: ['svelte'],
    // Include our installed package, instead of the built in version
    preferBuiltins: false,
}),
Enter fullscreen mode Exit fullscreen mode

Step 3: Rendering some sample Markdown

With those config updates, we should now be able to render Markdown inside of our Svelte app. So let's build a Markdown component to render that content.

We'll take in our Markdown string as a prop (markdown) for now. That lets us test with a static string, and we can update the app to read Markdown from files or a CMS in the future.

And we need to use Svelte's @html feature to add our generated HTML to the page.

⚠️ Warning: Using @html with user submitted content could expose your users to an XSS vulnerability. markdown-it has documentation about its security features and recommendations, which you should read and understand if you need to support user submitted content. ⚠️

<!-- src/Markdown.svelte -->
<script>
  import MarkdownIt from 'markdown-it'

  export let markdown = ''

  // Initialize `markdown-it`
  const md = new MarkdownIt()

  // Render to an html string
  const rendered = md.render(markdown)
</script>

<!-- Render with the `@html` directive -->
<div>
  {@html rendered}
</div>
Enter fullscreen mode Exit fullscreen mode

And we'll need to add our Markdown component to test:

<script>
  import Markdown from './Markdown.svelte'
</script>

<Markdown markdown="# Hello from Markdown!" />
Enter fullscreen mode Exit fullscreen mode

Reading Markdown from a file

Now that we can render Markdown, we're going to set up our build to read Markdown from files. Authoring is much easier in separate files, and I can use my project's git repo for some basic versioning.

Step 4: Importing *.md

Our Markdown components renders content from a string, so we need to be able to read our Markdown content in that format. rollup will fail right now if we try to import an .md file, but we can fix that with another plugin: rollup-plugin-string.

npm install --save-dev rollup-plugin-string
Enter fullscreen mode Exit fullscreen mode

And when we add it to rollup.config.js, we need to configure it to read .md files:

// In imports:
import { string } from 'rollup-plugin-string'

// In rollup config:
export default {
    //...
    plugins: [
        //...
        string({
            include: ['**/*.md'],
        }),
        //...
    ]
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Updating our test to render from a file

First, let's create a new Markdown file to test, src/example.md:

# Hello from Markdown!

We can render *text*.
Enter fullscreen mode Exit fullscreen mode

And now import that markdown into your app:

<script>
  import Markdown from './Markdown.svelte'
  import exampleMarkdown from './example.md'
</script>

<Markdown markdown={exampleMarkdown} />
Enter fullscreen mode Exit fullscreen mode

Supporting syntax highlighting

Basic Markdown is great, but one of the killer features for blogging as an engineer is syntax highlighting. markdown-it supports highlighting via a library, so let's add that now.

Step 6: Install highlight.js

highlight.js will allow us to add syntax highlighting to a wide variety of languages (including Markdown 🤣), and is bundled with a wide variety of themes we can use. And markdown-it uses it in their example, so let's start there.

npm install --save highlight.js
Enter fullscreen mode Exit fullscreen mode

We don't need to update our rollup config for this step, but we will need to configure highlighting in our Markdown component:

<!-- src/Markdown.svelte -->
<script>
  import MarkdownIt from 'markdown-it'

  // NEW: Import `highlight.js`
  import hljs from 'highlight.js'

  export let markdown = ''

  // Initialize `markdown-it`
  // NEW: Configure highlight via constructor params!
  const md = new MarkdownIt({
      highlight: function (str, lang) {
      if (lang && hljs.getLanguage(lang)) {
        try {
          return hljs.highlight(lang, str).value
        } catch (e) {
          // eslint-disable-next-line no-console
          console.error('Failed to highlight string')
        }
      }
      return '' // use external default escaping
    },
  })

  // Render to an html string
  const rendered = md.render(markdown)
</script>

<!-- Render with the `@html` directive -->
<div>
  {@html rendered}
</div>
Enter fullscreen mode Exit fullscreen mode

Step 7: Import highlight.js themes

Adding a code block to the example markdown will render a code block, but we're not currently getting any styling for our highlighting. We can import styles directly from highlight.js styles, but we'll need to update our rollup config again for this to work.

We're going to add rollup-plugin-styles to handle our CSS imports.

npm install --save-dev rollup-plugin-styles
Enter fullscreen mode Exit fullscreen mode

And we can use its default configuration in rollup.config.js.

// In imports:
import styles from 'rollup-plugin-styles'

// In rollup config:
export default {
    //...
    plugins: [
        //...
        styles(),
        //...
    ]
}
Enter fullscreen mode Exit fullscreen mode

Once we've done that, we can import a stylesheet from highlight.js into our Markdown component to render those styles into our site. I'm going to use a11y-light for this example, but there are lots of options you can pick, depending on your site's color scheme.

<!-- src/Markdown.svelte -->
<script>
  import MarkdownIt from 'markdown-it'
  import 'highlight.js/styles/a11y-light.css'
  // ...
Enter fullscreen mode Exit fullscreen mode

Wrapping up and writing the post!

With all of these pieces in place, I can now write this blog post in Markdown! There is still some work to do, especially styling the rendered HTML to match the rest of my site. But I can create my content in Markdown and let these libraries worry about the HTML!

For a quick demo, here's a comparison of the markdown for this post rendered in my local development environment and in a draft post on dev.to:

Comparison of this post rendered in Svelte and on dev.to

If you want to see the final version of the rollup config, I have a completed copy of the tutorial posted on github!

And if you have suggestions for improvements, I'm happy to chat on twitter!

Top comments (4)

Collapse
 
jreinhold profile image
Jeppe Reinhold

This is great!
I guess a missing (next) step here, is to render pages/build components based on multiple markdown files in a folder?

Collapse
 
chrsjxn profile image
Chris Jackson

Mine are currently very simple, and very manual. The page component for this post just looks like this:

<script>
  import { Layout, MarkdownPost } from '../Components'
  import markdown from '../Markdown/AddingMarkdownToSvelte.md'
</script>

<Layout>
  <MarkdownPost {markdown} />
</Layout>
Enter fullscreen mode Exit fullscreen mode

I am still thinking about a build step that generates these components and their routes based on the structure of my markdown directory. My level of rollup knowledge isn't that high yet, though, so I'm just writing the extra glue by hand right now.

Collapse
 
swyx profile image
swyx

nice work :)

Collapse
 
sharu725 profile image
sharath Kumar

Awesome! This works fantastically well for simple blogs.
Any idea on how frontmatter can added and read?