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ย โค๏ธ

    Created bySwizecwith โค๏ธ