From fbd042ff2a9463ef6016c3e5454e5d81886b86a5 Mon Sep 17 00:00:00 2001 From: Aaron Jensen Date: Fri, 9 Feb 2024 10:44:54 -0500 Subject: [PATCH] Dynamic stylesheets can be replaced in-place --- src/core/drive/page_renderer.js | 98 +++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/src/core/drive/page_renderer.js b/src/core/drive/page_renderer.js index c2789639b..cda85a134 100644 --- a/src/core/drive/page_renderer.js +++ b/src/core/drive/page_renderer.js @@ -77,9 +77,9 @@ export class PageRenderer extends Renderer { await mergedHeadElements await newStylesheetElements - if (this.willRender) { - this.removeUnusedDynamicStylesheetElements() - } + // if (this.willRender) { + // this.removeUnusedDynamicStylesheetElements() + // } } async replaceBody() { @@ -93,16 +93,92 @@ export class PageRenderer extends Renderer { return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature } + // async copyNewHeadStylesheetElements() { + // const loadingElements = [] + // + // for (const element of this.newHeadStylesheetElements) { + // loadingElements.push(waitForLoad(element)) + // + // document.head.appendChild(element) + // } + // + // await Promise.all(loadingElements) + // } + + removeFingerprint(url) { + if (!url) { return url } + return url.replace(/-[0-9a-f]{7,128}(\.digested)?\.css$/, ".css") + } + + stylesheetElementIsDynamic(element) { + return element.getAttribute("data-turbo-track") == "dynamic" && + element.getAttribute("href") != null + } + async copyNewHeadStylesheetElements() { const loadingElements = [] + const preloadElements = [] - for (const element of this.newHeadStylesheetElements) { - loadingElements.push(waitForLoad(element)) + const newStylesheetElements = this.newHeadStylesheetElements.filter(this.stylesheetElementIsDynamic) + const oldStylesheetElements = this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot).filter(this.stylesheetElementIsDynamic) - document.head.appendChild(element) + const oldElementIndex = Object.fromEntries(oldStylesheetElements.map(element => [this.removeFingerprint(element.getAttribute("href")), element])) + + let lastStylesheetElement = null + + for (const newElement of newStylesheetElements) { + const newHref = newElement.getAttribute("href") + const hrefWithoutFingerprint = this.removeFingerprint(newHref) + const oldElement = oldElementIndex[hrefWithoutFingerprint] + const oldHref = oldElement && oldElement.getAttribute("href") + + if (oldHref == newHref) { + console.log(`Skipping ${newHref}`) + delete oldElementIndex[hrefWithoutFingerprint] + continue + } + + // Convert the link tag to a preload so that it can be enabled at the same + // time as others (after everything has loaded) and any old stylesheets can + // be removed at the same time as well. + newElement.setAttribute("as", "style") + newElement.setAttribute("rel", "preload") + preloadElements.push(newElement) + + loadingElements.push(waitForLoad(newElement)) + + if (oldElement) { + console.log(`Updating ${oldElement.getAttribute("href")} to ${newHref}`) + + oldElement.after(newElement) + } else { + console.log(`Appending ${newHref} after`, lastStylesheetElement) + + // Ensure that the new elements maintain their order as much as possible + if (lastStylesheetElement) { + lastStylesheetElement.after(newElement) + } else { + document.head.appendChild(newElement) + } + } + + lastStylesheetElement = newElement } await Promise.all(loadingElements) + + for (const element of Object.values(oldElementIndex)) { + console.log(`Removing ${element.getAttribute("href")}`) + + element.remove() + } + + for (const element of preloadElements) { + console.log(`Converting preload ${element.getAttribute("href")}`) + + element.setAttribute("rel", "stylesheet") + element.removeAttribute("as") + } } copyNewHeadScriptElements() { @@ -111,11 +187,11 @@ export class PageRenderer extends Renderer { } } - removeUnusedDynamicStylesheetElements() { - for (const element of this.unusedDynamicStylesheetElements) { - document.head.removeChild(element) - } - } + // removeUnusedDynamicStylesheetElements() { + // for (const element of this.unusedDynamicStylesheetElements) { + // document.head.removeChild(element) + // } + // } async mergeProvisionalElements() { const newHeadElements = [...this.newHeadProvisionalElements]