diff --git a/src/lib/chunked.js b/src/lib/chunked.js index b5ec6c69..cbdffb3f 100644 --- a/src/lib/chunked.js +++ b/src/lib/chunked.js @@ -1,7 +1,7 @@ -import { renderToString } from '../index.js'; -import { CHILD_DID_SUSPEND, COMPONENT, PARENT } from './constants.js'; -import { Deferred } from './util.js'; -import { createInitScript, createSubtree } from './client.js'; +import { renderToString } from "../index.js"; +import { CHILD_DID_SUSPEND, COMPONENT, PARENT } from "./constants.js"; +import { Deferred } from "./util.js"; +import { createInitScript, createSubtree } from "./client.js"; /** * @param {VNode} vnode @@ -17,7 +17,7 @@ export async function renderToChunks(vnode, { context, onWrite, abortSignal }) { abortSignal, onWrite, onError: handleError, - suspended: [] + suspended: [], }; // Synchronously render the shell @@ -32,16 +32,14 @@ export async function renderToChunks(vnode, { context, onWrite, abortSignal }) { // and causes browsers to reject the content. Instead, we inject the deferred // content before the closing tags, then emit them last. const docSuffixIndex = getDocumentClosingTagsIndex(shell); - const hasHtmlTag = shell.trimStart().startsWith('' : ''; + const prefix = hasHtmlTag ? "" : ""; onWrite(prefix + initialWrite); - onWrite(''); if (docSuffixIndex !== -1) onWrite(shell.slice(docSuffixIndex)); } else { onWrite(shell); @@ -55,7 +53,7 @@ export async function renderToChunks(vnode, { context, onWrite, abortSignal }) { * @returns {number} */ function getDocumentClosingTagsIndex(html) { - return html.lastIndexOf(''); + return html.lastIndexOf(""); } async function forkPromises(renderer) { @@ -63,7 +61,7 @@ async function forkPromises(renderer) { const suspensions = [...renderer.suspended]; await Promise.all(renderer.suspended.map((s) => s.promise)); renderer.suspended = renderer.suspended.filter( - (s) => !suspensions.includes(s) + (s) => !suspensions.includes(s), ); await forkPromises(renderer); } @@ -91,7 +89,7 @@ function handleError(error, vnode, renderChild) { if (abortSignal) { // @ts-ignore 2554 - implicit undefined arg if (abortSignal.aborted) race.resolve(); - else abortSignal.addEventListener('abort', race.resolve); + else abortSignal.addEventListener("abort", race.resolve); } const promise = error.then( @@ -102,16 +100,18 @@ function handleError(error, vnode, renderChild) { }, // TODO: Abort and send hydration code snippet to client // to attempt to recover during hydration - this.onError + this.onError, ); this.suspended.push({ id, vnode, - promise: Promise.race([promise, race.promise]) + promise: Promise.race([promise, race.promise]), }); const fallback = renderChild(vnode.props.fallback); - return found ? '' : `${fallback}`; + return found + ? "" + : `${fallback}`; } diff --git a/src/lib/client.js b/src/lib/client.js index e7fe401d..0a354e10 100644 --- a/src/lib/client.js +++ b/src/lib/client.js @@ -1,52 +1,68 @@ /* eslint-disable no-var, key-spacing, object-curly-spacing, prefer-arrow-callback, semi, keyword-spacing */ // function initPreactIslandElement() { -// class PreactIslandElement extends HTMLElement { -// connectedCallback() { -// var d = this; -// if (!d.isConnected) return; - -// let i = this.getAttribute('data-target'); -// if (!i) return; +// let template = document.createElement("template"); +// template.innerHTML = ''; +// if (template.content.firstChild && template.content.firstChild.nodeType == 7) { +// return; +// } -// var s, -// e, -// c = document.createNodeIterator(document, 128); -// while (c.nextNode()) { -// let n = c.referenceNode; +// function findDirectives(id) { +// var s, +// e, +// c = document.createNodeIterator(document, 128); +// while (c.nextNode()) { +// let n = c.referenceNode; -// if (n.data == '$s:' + i) s = n; -// else if (n.data == '/$s:' + i) e = n; -// if (s && e) break; -// } -// if (s && e && s.parentNode !== document) { -// requestAnimationFrame(() => { -// var p = e.previousSibling; -// while (p != s) { -// if (!p || p == s) break; -// e.parentNode.removeChild(p); -// p = e.previousSibling; -// } +// if (n.data == '?start name="' + id + '"') s = n; +// else if (n.data == '?end name="' + id + '"') e = n; +// if (s && e) return [s, e]; +// } +// } -// c = s; -// while (d.firstChild) { -// s = d.firstChild; -// d.removeChild(s); -// c.after(s); -// c = s; +// let observer = new MutationObserver((mutations) => { +// for (let mutation of mutations) { +// for (let node of mutation.addedNodes) { +// if (node.nodeName === "TEMPLATE") { +// let id = node.getAttribute("for"); +// if (id) { +// let pair = findDirectives(id); +// if (pair) { +// requestAnimationFrame(() => { +// pair = findDirectives(node.getAttribute("for")); +// if (!pair) { +// node.remove(); +// return; +// } +// let [s, e] = pair; +// let p = e.previousSibling; +// while (p != s) { +// if (!p || p == s) break; +// e.parentNode.removeChild(p); +// p = e.previousSibling; +// } +// // TODO: flush this out to better polyfill the browser behavior, +// // this is pretty basic but works as a x-browser POC +// let n = document.createElement("template"); +// s.replaceWith(n); +// n.insertAdjacentHTML("beforebegin", node.innerHTML); +// n.remove(); +// e.remove(); +// node.remove(); +// }); +// } else { +// node.remove(); +// } // } - -// d.parentNode.removeChild(d); -// }); +// } // } // } -// } - -// customElements.define('preact-island', PreactIslandElement); +// }); +// observer.observe(document.body, { childList: true, subtree: true }); // } // To modify the INIT_SCRIPT, uncomment the above code, modify it, and paste it into https://try.terser.org/. -const INIT_SCRIPT = `class e extends HTMLElement{connectedCallback(){var e=this;if(!e.isConnected)return;let t=this.getAttribute("data-target");if(t){for(var r,a,i=document.createNodeIterator(document,128);i.nextNode();){let e=i.referenceNode;if(e.data=="$s:"+t?r=e:e.data=="/$s:"+t&&(a=e),r&&a)break}r&&a&&r.parentNode!==document&&requestAnimationFrame((()=>{for(var t=a.previousSibling;t!=r&&t&&t!=r;)a.parentNode.removeChild(t),t=a.previousSibling;for(i=r;e.firstChild;)r=e.firstChild,e.removeChild(r),i.after(r),i=r;e.parentNode.removeChild(e)}))}}}customElements.define("preact-island",e);`; +const INIT_SCRIPT = `let t=document.createElement("template");if(t.innerHTML='',t.content.firstChild&&7==t.content.firstChild.nodeType)return;let e=new MutationObserver(e=>{let t=e=>{for(var o,r,n=document.createNodeIterator(document,128);n.nextNode();){let l=n.referenceNode;if(l.data=='?start name="'+e+'"'?o=l:l.data=='?end name="'+e+'"'&&(r=l),o&&r)return[o,r]}};for(let o of e)for(let e of o.addedNodes)if("TEMPLATE"===e.nodeName){let o=e.getAttribute("for");if(o){let r=t(o);r?requestAnimationFrame(()=>{let o=t(e.getAttribute("for"));if(!o)return void e.remove();let[r,n]=o,l=n.previousSibling;for(;l!=r&&l&&l!=r;)n.parentNode.removeChild(l),l=n.previousSibling;let d=document.createElement("template");r.replaceWith(d),d.insertAdjacentHTML("beforebegin",e.innerHTML),d.remove(),n.remove(),e.remove()}):e.remove()}}});e.observe(document.body,{childList:!0,subtree:!0});`; export function createInitScript() { return ``; @@ -58,5 +74,5 @@ export function createInitScript() { * @returns {string} */ export function createSubtree(id, content) { - return ``; + return ``; } diff --git a/test/compat/render-chunked.test.jsx b/test/compat/render-chunked.test.jsx index f0699fca..3f218b22 100644 --- a/test/compat/render-chunked.test.jsx +++ b/test/compat/render-chunked.test.jsx @@ -34,11 +34,9 @@ describe('renderToChunks', () => { await promise; expect(result).to.deep.equal([ - '
loading...
', - '' ]); }); @@ -62,10 +60,8 @@ describe('renderToChunks', () => { suspended.resolve(); expect(result).to.deep.equal([ - '
loading...
', - '' ]); }); @@ -109,11 +105,9 @@ describe('renderToChunks', () => { } expect(result).to.deep.equal([ - '
loading...
', - '' ]); }); @@ -142,11 +136,9 @@ describe('renderToChunks', () => { await promise; expect(result).to.deep.equal([ - '

id: P0-0

loading...
', - '' ]); }); @@ -182,12 +174,10 @@ describe('renderToChunks', () => { await promise; expect(result).toEqual([ - '

id: P0-0

loading...loading...
', - '' ]); }); @@ -213,8 +203,8 @@ describe('renderToChunks', () => { const fullHtml = result.join(''); - // Deferred wrapper must appear before , not after - const deferredPos = fullHtml.indexOf('