Table of Contents
- The problem
- The solution
- First attempt
- First obstacle
- Second attempt
- Third attempt
- Moment of truth
- Conclusion
The problem
The other day I was thinking of an easy straightforward way of storing markdown
blog posts. Long story short, I decided to test out GitHub gists as a CDN
. A nice-to-have feature of a good blog post is syntax highlighting. For that, the options are Prism
and highlight.js
among other libs. However, I also wanted to include custom styling per blog post (or at least I want ot have that as an option). Ok, so store .md
blog posts and a css
file per post, on GitHub gists.
The solution
To test out this plan, I stored some borrowed CSS in a gist. Let’s see how we can use this in a webpage.
First attempt
import 'https://gist.githubusercontent.com/o-az/eb41ae192797f424f8053ffad98cc10b/raw/306a5b678779e79b842b09e54f166a7e349bb9a6/stylesheet.css'
First obstacle
Problematic MIME type:
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/plain".
Strict MIME type checking is enforced for module scripts per HTML spec.
This is apparently by design, "text/plain"
MIME type effectively prevents from using gists as a CDN.
Second attempt
Adding it as a <link>
:
<link
rel="stylesheet"
type="text/css"
href="https://gist.githubusercontent.com/o-az/eb41ae192797f424f8053ffad98cc10b/raw/306a5b678779e79b842b09e54f166a7e349bb9a6/stylesheet.css"
/>
Same story, even with type="text/css"
:
Failed to load module script … responded with a MIME type of "text/plain".
Third attempt
Fetch the content of the gist as plain text, create a <style>
element, insert plan text style to style element, append it to the document <head>
.
We’re going to add additional attributes to the <style>
element so that we can validate successful import. Specifically we’ll add two data-attribute
s. Final form should look like this:
<style
data-title="EXTERNAL_CSS"
data-url="https://gist....sheet.css"
>
/* ... */
</style>
The setup for the third attempt:
// url
const gistURL =
'https://gist.githubusercontent.com/o-az/eb41ae192797f424f8053ffad98cc10b/raw/6070da8ac2821ffc94de51f1a3bc1b30d862643c/stylesheet.css'
const attributes = [
['data-title', 'EXTERNAL_CSS'],
['data-url', gistURL],
]
Fetch the content of the gist as plain text, create a <style>
element, insert plain text style to style element, set the attirbutes we defined above, append it to the document <head>
.
async function fetchGistContent(url) {
const response = await fetch(url)
return await response.text()
}
async function importGistCSS(url) {
const plainTextCSS = await fetchGistContent(url)
const style = document.createElement('style')
style.textContent = plainTextCSS
for (const [name, value] of attributes) {
style.setAttribute(name, value)
}
document.head.append(style)
}
Moment of truth
To test this, I put it all together in a <script>
tag. You probably want better error handling and code org. in a real world scenario.
<script type="module">
const gistURL =
'https://gist.githubusercontent.com/o-az/eb41ae192797f424f8053ffad98cc10b/raw/6070da8ac2821ffc94de51f1a3bc1b30d862643c/stylesheet.css'
const attributes = [
['data-title', 'EXTERNAL_CSS'],
['data-url', gistURL],
]
async function fetchGistContent(url) {
const response = await fetch(url)
return await response.text()
}
async function importGistCSS(url) {
const plainTextCSS = await fetchGistContent(url)
const style = document.createElement('style')
style.textContent = plainTextCSS // set style
// set attributes
attributes.forEach(([name, value]) => style.setAttribute(name, value))
// append new style to head
document.head.appendChild(style)
}
importGistCSS(gistURL)
.then(() => {
const { head } = document
// access the last style element in head, that would be our style
const lastStyle = [...head.getElementsByTagName('style')].pop()
// check if the style element has the `data-attribute`s we defined above
for (const [attribute, value] of attributes) {
const attributeValue = lastStyle.getAttribute(attribute)
if (attributeValue !== value) {
console.log(`❌ could not find ${attribute} with value ${value}`)
return
}
console.log(`✅ found ${attribute} with value ${value}`)
}
console.log('✅ all attributes found. Stylesheet successfully imported.')
})
.catch(console.error)
</script>
<body>
<pre class="language-js">
<code>
const code = `const snippet = "cool markdown highlighting!";`
</code>
</pre>
</body>
Nice, that worked:
Conclusion
Now I also want to do the same but for a few <script>
tags in addition to the <style>
tag.
The problem is, the more DOM manipulation, access/read/write, the worse the performance. Can we improve this? Enter DocumentFragment.
A blog post on DocumentFragment
is cooking :fire: