Skip to content

Commit

Permalink
fix: made head replacement only target dynamic elements
Browse files Browse the repository at this point in the history
This avoids a FOUC (Flash of Unstyled Content) and unnecessary resource
re-requests on subsequent loads.

Fixes #182.
  • Loading branch information
arctic-hen7 committed Aug 29, 2022
1 parent fb00daf commit 73aa387
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 24 deletions.
8 changes: 4 additions & 4 deletions packages/perseus/src/server/html_shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<meta>` tag that will be used to wall off
// the constant document head from the interpolated document head
let head_end = format!(
r#"
<script type="module">{scripts_before_boundary}</script>
<!--PERSEUS_INTERPOLATED_HEAD_BEGINS-->
<meta itemprop="__perseus_head_boundary" content="">
{head_after_boundary}
<script>{scripts_after_boundary}</script>
"#,
Expand All @@ -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
"{}{}",
"{}<div>{}</div>",
&html_to_replace_double, self.content,
);
// Now interpolate that HTML into the HTML shell
Expand Down
41 changes: 21 additions & 20 deletions packages/perseus/src/utils/replace_head.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
use wasm_bindgen::JsCast;
use web_sys::Element;

/// Replaces the current document `<head>` 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 `<head>` 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 `<meta>` 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("<!--PERSEUS_INTERPOLATED_HEAD_BEGINS-->")
.collect();
let new_head = format!(
"{}\n<!--PERSEUS_INTERPOLATED_HEAD_BEGINS-->\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);
}

0 comments on commit 73aa387

Please sign in to comment.