title: HTML Includes That Work Today url: https://www.filamentgroup.com/lab/html-includes/ hash_url: 88df28660094efbc5a13bb09d70dfea6 archive_date: 2024-02-19 og_image: https://www.filamentgroup.com/images/icons/twittercard.png description: Read this page on the Filament Group website favicon: https://www.filamentgroup.com/images/icons/favicon-32x32.png language: en_US
Note: this post describes an experimental technique that we still need to test for performance implications. It may end up being a useful tool, and it may end up being a practice we won't recommend. Either way, it's fascinating to us that it works!
As long as I have been working on the web, I’ve desired a simple HTML-driven means of including the contents of another file directly into the page. For example, I often want to append additional HTML to a page after it is delivered, or embed the contents of an SVG file so that we can animate and style its elements. Typically here at Filament, we have achieved this embedding by either using JavaScript to fetch a file and append its contents to a particular element, or by including the file on the server side, but in many cases, neither of those approaches is quite what we want.
This week I was thinking about ways I might be able to achieve this using some of the new fetch
-related markup patterns, like rel="preload"
, or HTML imports, but I kept coming back to the same conclusion that none of these give you easy access to the contents of the fetched file. Then I thought, perhaps a good old iframe
could be a nice primitive for the pattern, assuming the browser would allow me to retrieve the iframe
’s contents in the parent document. As it turns out, it sure would!
Below is an inline (embedded) SVG graphic. It was loaded from an external file called signal.svg
.
To load and embed the SVG file, I used the following markup:
<iframe src="signal.svg" onload="this.before((this.contentDocument.body || this.contentDocument).children[0]);this.remove()"></iframe>
Despite the fact that this markup starts out as an iframe
, if you inspect the graphic above using developer tools, you’ll see the SVG markup for the icon, inlined right in the HTML DOM, with no iframe
element to be found. This is because the code uses an iframe
to load the file, and an onload
event to inject the iframe
’s content just before the iframe
in the HTML, before deleting the iframe
itself.
The approach also works with an object
element, which is commonly used to reference SVG anyway, so I think that’s particularly nice. With an object
, the src
attribute needs to be data
instead:
<object data="signal.svg" onload="this.before((this.contentDocument.body || this.contentDocument).children[0]);this.remove()"></object>
Maybe even more useful… here’s an example that uses HTML rather than SVG!
It was loaded using the following markup:
<iframe src="/images/includespost/htmlexample.html" onload="this.before((this.contentDocument.body||this.contentDocument).children[0]);this.remove()"></iframe>
One note about this one: you might have noticed that the markup snippet checks for either the contentDocument.body
or just the contentDocument
. This is a check to normalize between HTML and SVG includes. It’s necessary because even though the HTML file itself contains nothing more than a paragraph element, the browser creates a full HTML document to surround that paragraph, complete with an HTML element, a head, a body, etc. So the snippet tries to get the iframe
’s body
element if it exists, and if not, it goes for the entire document.
Notably, if you’re importing an HTML file that contains more than one element in it, I’d recommend wrapping it all in a div
to keep the iframe
markup able to simply look for the first childnode in the body
.
There are some nice benefits to this pattern over others we’ve used in the past:
iframe
.iframe
is designed to do. The JavaScript is there to move the iframe
’s content into the parent document, but if anythign fails, you’ll still see the included content.iframe
is deleted after it imports the content into the page. Note: you may want to give the iframe
a border:0;
or even safely hide it while loading (perhaps showing it again via an onerror event?).iframe
, but I think I can probably get IE support to work by adjusting the JS in the onload
handler, as it’s currently using syntax that IE doesn’t like. With a little tweaking, I expect IE support will be possible.It’s fun to think of other possible uses… Perhaps you could pull in HTML modules along with their relevant CSS link. Or embed a tweet or code examples in documentation or a blog post. It could probably even be used to load and apply a regular rel=stylesheet
link asynchronously, and at a low priority, which is otherwise surprisingly hard to do (note: I didn’t test this idea much to say for sure).
Another nice thing about using an iframe
for this pattern is that iframe
s will soon gain the native ability to lazy-load when they enter the viewport. This will be achieved using the loading="lazy"
attribute, which also will also work for img
elements. Here’s how that markup will look:
<iframe src="signal.svg" loading="lazy" onload="this.before((this.contentDocument.body||this.contentDocument).children[0]);this.remove()"></iframe>
Iframe
s are very commonly used on the web, but it’s possible that overusing them in a page could lead to a performance or memory consumption problem. It’s possible that using this for all the icons on a page would be too heavy, for example, but it could be nice to use for just the particular icons that need to be animated and styled. I’ll have to do more testing before I can say.
There’s also the possibility of XSS problems, though I’m not sure this is any different than other situations where you’d want to be careful with external content. The usual safechecks apply, and it’s probably best to consider it a same-domain technique, though again I’m not sure on that either.
For now, this approach feels promising as an improvement over prior methods we’ve used for including another file directly into a page.
We’ll be continuing to test this pattern and may post a followup soon if we find anything interesting. If you have any feedback or ideas, feel free to hit us up on twitter. Thanks for reading!
Note: This post is a followup and improvement on an earlier experiment I posted on Codepen.