惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
WordPress大学
WordPress大学
小众软件
小众软件
Cloudbric
Cloudbric
AWS News Blog
AWS News Blog
腾讯CDC
量子位
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
Scott Helme
Scott Helme
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
Jina AI
Jina AI
Attack and Defense Labs
Attack and Defense Labs
S
SegmentFault 最新的问题
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Google Online Security Blog
Google Online Security Blog
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
罗磊的独立博客
L
LINUX DO - 最新话题
博客园 - Franky
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
The Last Watchdog
The Last Watchdog
J
Java Code Geeks
AI
AI
C
Cisco Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
I
Intezer
S
Securelist

Home on Alex Plescan

Just for fun: animating a mosaic of 90s GIFs Two computers, one monitor, zero fiddling Placeholder names should be bad and unique Rebuilding this site Okay, I really like WezTerm GNU Parallel, where have you been all my life? Timeseries with PostgreSQL Easy SVG sparklines Selling SaaS on Gumroad PDF: The Conjoined Triangles of Success Deploying Metabase to Fly.io The ".x" Files Xcode 8 managed signing: adding new device UUIDs to a provisioning profile Emojify your Wi-Fi (Netgear R6300 edition) How to use the San Francisco Mono typeface before macOS Sierra is released Disabling App Transport Security in your development environment Swift: A nicer way to tell if your app is running in Debug mode Development environment config overrides in Jekyll Setting up SwiftLint on Travis CI
Using Declarative Shadow DOM to embed HTML emails on a web page
2023-03-19 · via Home on Alex Plescan

Recently, I worked on embedding HTML emails into a web page for Mailgrip. I’d done something similar in the past using iFrames, but this time used the Declarative Shadow DOM instead. It resulted in a much easier implementation, with less reliance on client-side JavaScript.

This post provides an introduction to the Declarative Shadow DOM, and how it compares to the regular ol’ Shadow DOM.

a screenshot of mailgrip depicting an email embedded onto the page

Why not just insert the email’s HTML into the page?

First, an aside about the obvious anti-pattern here; taking the email’s HTML and inserting it as-is into the web page.

This would be very unreliable. Emails come with their own CSS styles (some inline, some via stylesheets) which would clash with the styles on the host page they’re being embedded into. Likewise, styles from the host page would cascade down into the email and style it in an undesired way.

So… enforcing separation between the email’s DOM and CSS and those of the host page’s is really really necessary.

Enter the Shadow realm DOM

Shadow DOM is a web standard that allows you to attach a self-contained DOM tree to an existing DOM tree. Styles in the Shadow DOM’s tree are totally separate from those on your host page, so it’s perfect for embedding HTML emails.

Typically to create a Shadow DOM you’d have to use JavaScript, and it’d look something like this:

<html>
  <head>
    <title>Host page</title>
    <style>
      * {
        background-color: lightblue;
        font-family: serif;
      }
    </style>
  </head>

  <body>
    Host page content.

    <div id="host"></div>

    <script>
      const hostElement = document.getElementById("host");
      const shadowRoot = hostElement.attachShadow({ mode: "open" });

      // Whatever CSS styles you apply won't cross the boundary between the host
      // page and the Shadow DOM.
      shadowRoot.innerHTML = `
        <h1>Inner content (DOMception)</h1>
        <style>
            * {
                background-color: salmon;
                font-family: monospace;
            }
        </style>
        `;
    </script>
  </body>
</html>

So, in this implementation we:

  1. Create our host page, which includes a container element to host the shadow DOM id="host" in this case,
  2. Designate that element as the root of the Shadow DOM by using Element.attachShadow(),
  3. Add content and styles to the shadow root by assigning innerHTML (for simplicity. Any DOM manipulation function would work).

… and that all comes together into a page that looks like this:

a screenshot showing a shadow dom embedded inside host dom

The blue-ish parts are the host DOM, and the salmon-ish parts are in the Shadow DOM. Note how the monospace styles from inside the Shadow DOM’s stylesheet don’t leak outside of its bounds.

(This is a very simplified example. Check out the MDN docs to go deeper.)

What does the Declarative Shadow DOM add?

It takes the Shadow DOM you already know and love and makes it more… declarative.

That means you can specify a shadow DOM directly in your existing HTML, without relying on JavaScript. This is a huge boon for simplicity, and a plus for server-side rendered apps that try to use as little JavaScript as possible (like Mailgrip).

Using Declarative Shadow DOM, the example from above becomes:

<html>
  <head>
    <title>Host page</title>
    <style>
      * {
        background-color: lightblue;
        font-family: serif;
      }
    </style>
  </head>

  <body>
    Host page content.

    <div id="host">
      <template shadowrootmode="open">
        <h1>Shadow DOM content (DOMception).</h1>

        <style>
          * {
            background-color: salmon;
            font-family: monospace;
          }
        </style>
      </template>
    </div>
  </body>
</html>

No more JavaScript here, but same result. Simply specifying a <template> element with the shadowrootmode attribute instructs the browser’s HTML parser that the children of that node should be part of a Shadow DOM tree.

(This too is a very simplified example. Check out the Chrome Developers article on Declarative Shadow DOM to learn more.)

What’s browser support like?

Ehh… not great yet. Shadow DOM itself has been around for a while and is well supported, but as of March 2023, Declarative Shadow DOM is a fresh feature that only works on Chrome.

The good news is there’s a very straight-forward polyfill you can use to add support to all modern browsers:

document.addEventListener("DOMContentLoaded", () => {
  polyfill();
});

function polyfill() {
  // Polyfill Declarative Shadow DOM
  // https://developer.chrome.com/articles/declarative-shadow-dom/#polyfill
  (function attachShadowRoots(root) {
    root.querySelectorAll("template[shadowrootmode]").forEach((template) => {
      const mode = template.getAttribute("shadowrootmode");
      const shadowRoot = template.parentNode.attachShadow({ mode });
      shadowRoot.appendChild(template.content);
      template.remove();
      // Recursive, so you could have a DOM in your DOM in your DOM in your DOM!
      attachShadowRoots(shadowRoot);
    });
  })(document);
}

Overall, using this approach cut down a fair bit of complexity with embedding HTML emails on Mailgrip. Although the polyfill still requires client-side JavaScript to work, I’m looking forward to wider browser support so I can start embedding emails without any client-side code at all.