Embed multiple third party scripts in Svelte markdown (.md / .svx) with mdsvex

Date: 2022-10-12 | svelte | sveltekit | javascript |

Overview

Embeds are the go-to way for inserting rich media in your website from data sources around the internet - a tweet, an Instagram post, a YouTube video.

MDsveX is a leading package for combining markdown and Svelte by preprocessing them into Svelte components.

Currently there is an issue when you embed multiple scripts in a single markdown file and attempt to preprocess them with mdsvex:

ParseError: A component can only have one instance-level <script> element

In this post I'll share how I got this working.

Problem

First let's reproduce the problem. We'll do this with a tweet.

Here's what the embed looks like:

Here's the code for that embed:

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Introducing <a href="https://t.co/dI1dDbLlg2">https://t.co/dI1dDbLlg2</a> <br><br>* You: Boop<br>* It: ++</p>&mdash; Hamilton Greene (@SIRHAMY) <a href="https://twitter.com/SIRHAMY/status/1578435991001600006?ref_src=twsrc%5Etfw">October 7, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

To reproduce our error, we can create a file that will be preprocessed by MDsveX and do > 1 embed:

// multi-embed-error.svx

// First embed
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Introducing <a href="https://t.co/dI1dDbLlg2">https://t.co/dI1dDbLlg2</a> <br><br>* You: Boop<br>* It: ++</p>&mdash; Hamilton Greene (@SIRHAMY) <a href="https://twitter.com/SIRHAMY/status/1578435991001600006?ref_src=twsrc%5Etfw">October 7, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

// Second embed
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Introducing <a href="https://t.co/dI1dDbLlg2">https://t.co/dI1dDbLlg2</a> <br><br>* You: Boop<br>* It: ++</p>&mdash; Hamilton Greene (@SIRHAMY) <a href="https://twitter.com/SIRHAMY/status/1578435991001600006?ref_src=twsrc%5Etfw">October 7, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

When I run this I get:

#11 7.206 error during build:
#11 7.206 ParseError: A component can only have one instance-level <script> element
#11 7.206     at error (file:///app/node_modules/svelte/compiler.mjs:17746:19)
#11 7.206     at Parser$1.error (file:///app/node_modules/svelte/compiler.mjs:17822:9)
#11 7.206     at parse$3 (file:///app/node_modules/svelte/compiler.mjs:17922:16)
#11 7.206     at compile (file:///app/node_modules/svelte/compiler.mjs:42999:17)
#11 7.206     at compileSvelte2 (file:///app/node_modules/@sveltejs/vite-plugin-svelte/dist/index.js:343:20)
#11 7.206     at async Object.transform (file:///app/node_modules/@sveltejs/vite-plugin-svelte/dist/index.js:1974:25)
#11 7.206     at async transform (file:///app/node_modules/vite/node_modules/rollup/dist/es/shared/rollup.js:21928:16)
#11 7.206     at async ModuleLoader.addModuleSource (file:///app/node_modules/vite/node_modules/rollup/dist/es/shared/rollup.js:22153:30)

Solution

A: The Simple Method

The simplest method I've found to resolve this is to wrap each embed in an @html tag (REPL example). This basically tells Svelte to dangerously render the html - so be careful what you put in there.

This is relatively simple to implement and remember while writing .svx / .md files and has a light, easy to (CTRL+F, Replace All) footprint which removes a bit of the risk of tech debt for when the API changes / you want to migrate to a different technology with a different API.

Example using `{@html HTML}:

{@html `
    <blockquote class="twitter-tweet"><p lang="en" dir="ltr">Introducing <a href="https://t.co/dI1dDbLlg2">https://t.co/dI1dDbLlg2</a> <br><br>* You: Boop<br>* It: ++</p>&mdash; Hamilton Greene (@SIRHAMY) <a href="https://twitter.com/SIRHAMY/status/1578435991001600006?ref_src=twsrc%5Etfw">October 7, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
`}

B: The Scale Solution

While the {@html} tag may be an okay stand-in while writing new files, it can be prohibitively expensive to use with a large backlog of existing markdown files. This was my case when I migrated to Svelte / Sveltekit - with almost 10 years and 500+ posts with many embeds among them. Wrapping each was not an option.

The core problem with multi embeds seems to lie within MDsveX. We can show this is not a Svelte-originating problem by taking the failing multi-embed and plopping them in a page. I did that - it doesn't fail.

The solution then is to... not use MDsveX. I was able to approximate its parsing capabilities using:

Together we get all the markdown parsing capaiblities while skipping MDsveX's parsing issue and can now render the entire output markdown blob using {@html MARKDOWN_BLOB_HERE}.

I still like the idea of writing svelte components with markdown so I've basically split my markdown in two types:

  • .svx - these are files that I want to be compiled to Svelte and will be processed by MDsveX
  • .md - these are files that I don't want to be compiled with Svelte (e.g. they have multiple embeds) so I compile them manually

Next Steps

Getting this fixed: since it seems multi embeds is natively possible in Svelte, we should be able to update MDsveX so it can handle multi-embeds directly.

Further reading

If you're just getting started building a markdown blog with Svelte / SvelteKit, I highly recommend Josh Collinsworth's Let's learn SvelteKit by building a static Markdown blog from scratch

If you already have a blog and want some analytics, check out Google Analytics with SvelteKit

Want more like this?

The best / easiest way to support my work is by subscribing for future updates and sharing with your network.