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:
Introducing https://t.co/dI1dDbLlg2
— Hamilton Greene (@SIRHAMY) October 7, 2022
* You: Boop
* It: ++
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>— 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>— 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>— 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>— 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:
- markdown-it - parse markdown -> html
- front-matter - parse frontmatter from markdown
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.