JSFiddle - React, Tailwind, and code Playground
HTML
<div>Streaming Detached Document with MutationObserver</div>
JavaScript
// Some context for this technique: https://www.youtube.com/watch?v=LLRig4s1_yA
const detachedDoc = document.implementation.createHTMLDocument('');
detachedDoc.open();
detachedDoc.write('<streaming-root>');
const streamingRoot = detachedDoc.querySelector('streaming-root');
document.body.appendChild(streamingRoot);
function insertScriptBroken(element) {
// The script may or may not be complete when MutationObserver returns it. It fires when the opening tag is detected along with anything else in that chunk.
insertScript(element);
}
const pendingScripts = [];
function insertScriptWorking(element) {
// Add the script to a queue that gets executed only when the next node arrives.
pendingScripts.push(element);
}
function processPendingScripts() {
while (pendingScripts.length) {
insertScript(pendingScripts.shift());
}
}
function insertScript(element) {
// Just inserting a script won't run it.
const script = document.createElement('script');
const text = element.textContent;
script.textContent = text;
document.body.appendChild(script);
}
// Install MutationObserver to move individual nodes to the main document
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
// Ignore #text nodes that might be inner fragments of scripts or elements
if (!node || node.nodeType !== Node.ELEMENT_NODE) continue;
processPendingScripts();
if (node.nodeName.toLowerCase() === 'script') {
insertScriptBroken(node);
// insertScriptWorking(node);
}
}
}
});
observer.observe(streamingRoot, {
subtree: true,
childList: true
});
/**
// Used if we are streaming from a URL.
const response = await fetch(url, {mode: 'same-origin'});
const reader = this.response.body.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
const...