Injecting arbitrary content with stylesheets
In many cases, we want to inject content onto a webpage when we don't necessarily know what that content will be. Most implementations of patterns like infinite-scroll will do something like make a request for the next page, grab the body content and append it to the infinite-scrolled area. Or in another case we might want to get the markup for a news widget and inject it into a sidebar. In cases like these, it's entirely possible that the arbitrary markup inserted onto the page will contain stylesheets.
These stylesheet link tags present a problem because after the initial rendering of the page the link tags no longer block rendering. If they don't block rendering, we'll see a FOUC which nobody wants for our poor, visually-sensitive users. So how do we go about protecting ourselves from this arbitrary content?
The solution: An injection script that manages asset loading
I came up with a novelty injector script that handles this issue in a nice way. It scans the content to be injected first and extracts all the stylesheet links. Once those stylesheets have been loaded, the rest of the content is injected normally. This avoids any FOUCs and keeps things slightly more organized.
function safe_injector (unsafe_content, target) {
var $unsafe_content = $(unsafe_content)
var stylesheets = []
var loaded_stylesheets = 0
// separate out all the stylesheet elements
$unsafe_content.find('link[type="text/css"]').each(function () {
stylesheets.push(this)
this.remove()
})
// append all the stylesheets to the `<head>`
stylesheets.forEach(function (link, index, array) {
link.onload = function () {
loaded_stylesheets += 1
// if *all* of the stylesheets have loaded, finally insert the content
if (loaded_stylesheets.length == stylesheets.length) {
$(target).append($unsafe_content)
}
}
$('head').appendChild( $(link) )
})
}
Extra credit
A more thorough version of the plugin would tackle some more interesting problems like script loading. The scripts would have to be injected after the content is injected on the page but what if there was a script that was intended to be injected before the content? Then we'd have to sort through the content, extract all stylesheets and scripts, inject and wait for stylesheets to load, inject and wait for some scripts to load, inject the content, and finally inject any scripts that can be deferred. And what happens if there are async
or deferred
scripts in the content just to throw a wrench into all that work?
It would seem that from every angle this is not a preferable way to do web development and makes for an unmaintainable mess of a website. But web developers run into problems like this every day and we don't always have an elegant way to solve problems with lots of constraints. I hope this hack/thought experiment helps somebody out in solving a problem they're facing.