Skip to content
Swizec Teller - a geek with a hatswizec.com

CodeWithSwiz: Privacy-focused embeds for YouTube, Twitter, et al

Friend, here's a new old experiment πŸ‘‰ regular livecoding. It's like a podcast with video, show notes, regular cadence, and we both learn something new.

For me it's a regular timebox – Wednesdays 6:30pm Pacific and Sundays 2pm Pacific – to play with new technology, explore ideas, work on open source, and hack.

For you it's a chance to hear about new libraries and technologies, peek behind the curtain, ask any and all questions you'd like, and with luck see cool things get built.

Show notes work like a short recap.


PS: you can read and share this online

When websites embed 3rd party content like YouTube, Twitter, Facebook, et all, those embeds come with a bunch of cruft. Shitload of JavaScript, heavy code in iframes, and tracking scripts.

You don't want that.

Privacy is important and so is performance. A blank page with a single YouTube embed makes 18 requests and loads 2.2 megs of data.

YouTube embed performance hit
YouTube embed performance hit

5 cookies 😳

cookies from a single YT embed
cookies from a single YT embed

What if instead of embedding with an iframe, you showed a thumbnail first? Load dynamic content when user interacts and shows intent. Says "Yes, I wanna see this"

The way TechLetter.App works for emails. But without taking you to a new page on click.

Here's the plan:

  • take existing AWS Lambda screenshot machinery
  • make a Gatsby plugin that turns embeds into thumbnails
  • inject vanilla JavaScript to turn into proper embeds

Started with a dirty proof of concept. It worked 🀘

Time to build a plugin! That did not go well.

First, we ran into issues with plugin building for Gatsby. You have to keep clearing cache, rebuilding the whole site, and waiting a lot.

When your computer is busy streaming, you're waiting a lot.

Should've written tests instead πŸ˜…

But fear not, we got the thumbnail part working. First for YouTube, later we can expand. It's a remark plugin like this:

export default async function (
{ cache, markdownAST },
pluginOptions: PluginOptions
) {
const transformations = [];
visit(markdownAST, "link", (linkNode) => {
const url = linkNode.url as string;
if (url.match(/^http(s)?:\/\/(www\.)?(youtube\.com|youtu\.be)/)) {
transformations.push(async () => embed(linkNode, "youtube"));
}
});
await Promise.all(transformations.map((t) => t()));
return markdownAST;
}

Visits all link nodes in your document, checks if they're YouTube, and turns them into a thumbnail embed.

Transformation to an embed looks like this:

async function embed(linkNode, embedType: "youtube") {
const originalUrl = linkNode.url;
const thumbnailUrl = await getThumbnail(originalUrl);
if (thumbnailUrl) {
linkNode.type = "image";
linkNode.url = thumbnailUrl;
linkNode.title = "A youtube video";
linkNode.alt = "A youtube video";
linkNode.children = null;
linkNode.data = {
...linkNode.data,
};
linkNode.data.hProperties = {
...linkNode.data.hProperties,
"data-embed-url": getYouTubeIFrameSrc(originalUrl),
"data-embed-type": embedType,
};
}
return linkNode;
}

linkNode.data.hProperties is how you can add HTML properties to your Markdown nodes. Didn't know that before, stole it from another plugin on stream ✌️

The getThumbnail method talks to my AWS Lambda running Chrome Puppeteer. Takes a screenshot of the YouTube video, saves it to S3, and returns the URL.

You can read about that in Serverless Handbook: Chrome puppeteer chapter.

We're using data-embed-url and data-embed-type to tell the injected JavaScript what to do. Meant to turn this thumbnail into an iframe when you click.

Gatsby lets us inject JavaScript on page load. Export an onRouteUpdate method from gatsby-browser.js.

exports.onRouteUpdate = () => {
if (document.querySelector("[data-embed-url]")) {
addJS(`
for (let embed of document.querySelectorAll('[data-embed-url]')) {
embed.addEventListener('click', (event) => {
const embed = event.currentTarget;
const type = embed.dataset.embedType;
const url = embed.dataset.embedUrl;
const dimensions = embed.getBoundingClientRect();
if (type === 'youtube') {
const el = document.createElement('p');
el.innerHTML = '<iframe title="Youtube embed" width="'+dimensions.width+'px" height="'+dimensions.height+'px" src="'+url+'" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>';
embed.replaceWith(el);
}
})
}
`);
}
};

Yeah writing code in a string sucks. You don't have React either. And I gotta say vanilla JavaScript has improved lots in the past few years.

This code looks for all elements with a data-embed-url property and adds a click listener. When you click, it replaces the element with an HTML string.

Adding it to the page is a matter of injecting a new <script> tag into document head.

function addJS(jsCode) {
const s = document.createElement(`script`);
s.type = `text/javascript`;
s.innerText = jsCode;
document.getElementsByTagName(`head`)[0].appendChild(s);
}

Where I'm stuck now is with Gatsby shadowing and moving all this code to the existing gatsby-remark-embedder plugin. Got the thumbnails to work, but not the in-browser JavaScript.

Gatsby shadowing says you can put gatsby-browser.js in your plugin and Gatsby will run that code. But it's not working for me 🀨

Next time!

Cheers, ~Swizec

Did you enjoy this article?

Published on August 27th, 2020 in CodeWithSwiz, Technical, Markdown, Livecoding

Learned something new?
Want to become a high value JavaScript expert?

Here's how it works πŸ‘‡

Leave your email and I'll send you an Interactive Modern JavaScript Cheatsheet πŸ“–right away. After that you'll get thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over my 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

Start with an interactive cheatsheet πŸ“–

Then get thoughtful letters πŸ’Œ on mindsets, tactics, and technical skills for your career.

"Man, love your simple writing! Yours is the only email I open from marketers and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. πŸ‘Œ"

~ Ashish Kumar

Join over 10,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

Have a burning question that you think I can answer?Β I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

Ready to Stop copy pasting D3 examples and create data visualizations of your own? Β Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization

Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first πŸ’° on the side with ServerlessReact.Dev

Want to brush up on your modern JavaScript syntax?Β Check out my interactive cheatsheet: es6cheatsheet.com

By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❀️