From 73aa38714e3b0ecdbd625e7d555b5527aa23a226 Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Mon, 29 Aug 2022 20:34:05 +1000 Subject: [PATCH] fix: made head replacement only target dynamic elements This avoids a FOUC (Flash of Unstyled Content) and unnecessary resource re-requests on subsequent loads. Fixes #182. --- packages/perseus/src/server/html_shell.rs | 8 ++--- packages/perseus/src/utils/replace_head.rs | 41 +++++++++++----------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/perseus/src/server/html_shell.rs b/packages/perseus/src/server/html_shell.rs index 1040e297cc..fbfd0fba84 100644 --- a/packages/perseus/src/server/html_shell.rs +++ b/packages/perseus/src/server/html_shell.rs @@ -297,12 +297,12 @@ impl HtmlShell { impl fmt::Display for HtmlShell { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let head_start = self.head_before_boundary.join("\n"); - // We also inject a delimiter comment that will be used to wall off the constant - // document head from the interpolated document head + // We also inject a delimiter dummy `` tag that will be used to wall off + // the constant document head from the interpolated document head let head_end = format!( r#" - + {head_after_boundary} "#, @@ -329,7 +329,7 @@ impl fmt::Display for HtmlShell { let html_replacement = format!( // We give the content a specific ID so that it can be deleted if an error page needs // to be rendered on the client-side - "{}{}", + "{}
{}
", &html_to_replace_double, self.content, ); // Now interpolate that HTML into the HTML shell diff --git a/packages/perseus/src/utils/replace_head.rs b/packages/perseus/src/utils/replace_head.rs index b091c0a43c..6bc2e1612e 100644 --- a/packages/perseus/src/utils/replace_head.rs +++ b/packages/perseus/src/utils/replace_head.rs @@ -1,26 +1,27 @@ +use wasm_bindgen::JsCast; +use web_sys::Element; + /// Replaces the current document `` after the delimiter comment /// with something new. +/// +/// This will only touch the dynamic elements, thus avoiding re-requesting +/// any resources references in the component of the `` shared between +/// all pages, which may create a flash of unstyled content. pub(crate) fn replace_head(new: &str) { + let document = web_sys::window().unwrap().document().unwrap(); // Get the current head - let head_elem = web_sys::window() - .unwrap() - .document() - .unwrap() - .query_selector("head") - .unwrap() + let head_node = document.query_selector("head").unwrap().unwrap(); + let head_elem: Element = head_node.unchecked_into(); + // Get everything after the dummy `` tag we use as a delimiter + let els_to_remove = document + .query_selector_all(r#"meta[itemprop='__perseus_head_boundary'] ~ *"#) .unwrap(); - let head_html = head_elem.inner_html(); - // We'll assume that there's already previously interpolated head in - // addition to the hardcoded stuff, but it will be separated by the - // server-injected delimiter comment - // Thus, we replace the stuff after that delimiter comment with the - // new head - let head_parts: Vec<&str> = head_html - .split("") - .collect(); - let new_head = format!( - "{}\n\n{}", - head_parts[0], new - ); - head_elem.set_inner_html(&new_head); + // For some horrific reason, this isn't implemented as an iterator in `web_sys` + for idx in 0..(els_to_remove.length()) { + let el = els_to_remove.get(idx).unwrap(); + head_elem.remove_child(&el); + } + // And now append the new HTML to the head (yes, this position is untyped...) + // This position is inside the element, after its last child + head_elem.insert_adjacent_html("beforeend", new); }