Skip to content

Instantly share code, notes, and snippets.

@DylanPiercey
Last active October 1, 2022 09:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save DylanPiercey/6f945d4f75ae310c4173d5281f2f2c81 to your computer and use it in GitHub Desktop.
Save DylanPiercey/6f945d4f75ae310c4173d5281f2f2c81 to your computer and use it in GitHub Desktop.
Progressive HTML with fetch
const getWritableDOM = (() => {
const testDoc = document.implementation.createHTMLDocument();
testDoc.write("<script>");
// Safari and potentially other browsers strip script tags from detached documents.
// If that's the case we'll fallback to an iframe implementation.
if (testDoc.scripts.length) {
return target =>
toWritable(target, document.implementation.createHTMLDocument());
} else {
return target => {
const frame = document.createElement("iframe");
frame.src = "";
frame.style.display = "none";
target.appendChild(frame);
return toWritable(target, frame.contentDocument, () => frame.remove());
};
}
function toWritable(target, doc, onClose) {
doc.write("<!DOCTYPE html><body><template>");
const root = doc.body.firstElementChild.content;
const walker = doc.createTreeWalker(root);
const targetNodes = new WeakMap([[root, target]]);
let isBlocked = false;
let isClosed = false;
let pendingText;
let scanNode;
return {
close,
write(chunk) {
doc.write(chunk);
walk();
}
};
function walk() {
let node;
if (isBlocked) {
// If we are blocked, we walk ahead and preload
// any assets we can ahead of the last checked node.
const blockedNode = walker.currentNode;
if (scanNode) walker.currentNode = scanNode;
while ((node = walker.nextNode())) {
const link = getPreloadLink((scanNode = node));
if (link) {
link.onload = link.onerror = () => link.remove();
target.insertBefore(link, target.firstChild);
}
}
walker.currentNode = blockedNode;
} else {
while ((node = walker.nextNode())) {
if (pendingText) {
// The final text content must be added lazily since it can be in an incomplete state.
targetNodes
.get(pendingText.parentNode)
.appendChild(document.importNode(pendingText, false));
pendingText = null;
}
if (node.nodeType === Node.TEXT_NODE) {
pendingText = node;
continue;
}
const clone = document.importNode(node, false);
if (isBlocking(clone)) {
isBlocked = true;
clone.onload = clone.onerror = () => {
isBlocked = false;
walk(); // Continue the normal content injecting walk.
};
}
targetNodes.set(node, clone);
targetNodes.get(node.parentNode).appendChild(clone);
if (isBlocked) return walk(); // Start walking for preloads.
}
if (isClosed) close(); // Some blocking content prevented close, now we can close.
}
}
function close() {
isClosed = true;
if (!isBlocked) {
if (pendingText) {
// The final text content must be added lazily since it can be in an incomplete state.
targetNodes
.get(pendingText.parentNode)
.appendChild(document.importNode(pendingText, false));
}
doc.close();
if (onClose) onClose();
}
}
}
function isBlocking(node) {
return (
node.nodeType === Node.ELEMENT_NODE &&
((node.tagName === "SCRIPT" &&
node.src &&
!(
node.noModule ||
node.type === "module" ||
node.hasAttribute("async") ||
node.hasAttribute("defer")
)) ||
(node.tagName === "LINK" &&
node.rel === "stylesheet" &&
(!node.media || matchMedia(node.media).matches)))
);
}
function getPreloadLink(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
let link;
switch (node.tagName) {
case "SCRIPT":
if (node.src && !node.noModule) {
link = document.createElement("link");
link.href = node.src;
if (node.module) {
link.rel = "modulepreload";
} else {
link.rel = "preload";
link.as = "script";
}
}
break;
case "LINK":
if (
node.rel === "stylesheet" &&
(!node.media || matchMedia(node.media).matches)
) {
link = document.createElement("link");
link.href = node.href;
link.rel = "preload";
link.as = "style";
}
break;
case "IMG":
link = document.createElement("link");
link.rel = "preload";
link.as = "image";
if (node.srcset) {
link.imageSrcset = node.srcset;
link.imageSizes = node.sizes;
} else {
link.href = node.src;
}
break;
}
if (link) {
if (node.integrity) {
link.integrity = node.integrity;
}
if (node.crossOrigin) {
link.crossOrigin = node.crossOrigin;
}
return link;
}
}
}
})();
// Example usage.
async function streamInto(target, url) {
const res = await fetch(url);
const decoder = new TextDecoder();
const reader = res.body.getReader();
const writable = getWritableDOM(target);
try {
let value;
while (!({ value } = await reader.read()).done) {
writable.write(decoder.decode(value));
}
} finally {
writable.close();
}
}
@DylanPiercey
Copy link
Author

Usage:

streamInto(document.getElementById("app"), "http://ebay.com");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment