Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions bifrost/lib/turbolinks/mergeHead.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@ const allHeadScriptsEverRun: { [outerHTML: string]: ElementDetails } = {};
let firstMerge = true;
let lastTrackedScriptSignature: string;

export const recordExistingHeadScripts = instrument("recordExistingHeadScripts", function recordExistingHeadScripts(categorizedHead?: CategorizedHead) {
categorizedHead ||= categorizeHead(document.head);
// record all existing head scripts as having been run, because they were run by browser, not mergeHead
for (const element of categorizedHead.scripts) {
// Scripts already in the document were run by the browser, so mergeHead must never run them again (e.g. when restoring a snapshot)
export const recordExistingHeadScripts = instrument("recordExistingHeadScripts", function recordExistingHeadScripts() {
for (const element of categorizeHead(document.head).scripts) {
allHeadScriptsEverRun[element.outerHTML] = {
tracked: elementIsTracked(element),
};
}
});

// Only a wrapped SSR page defines the tracked-element baseline. A vite page has none, so its first merge into a wrapped page must never reload.
export function recordTrackedElementBaseline() {
lastTrackedScriptSignature =
lastTrackedScriptSignature || trackedElementSignature(categorizedHead);
lastTrackedScriptSignature ||
trackedElementSignature(categorizeHead(document.head));
firstMerge = false;
});
}

// Returns function which resolves when all new blocking head scripts have loaded
export function mergeHead(head: HTMLHeadElement) {
Expand Down
8 changes: 7 additions & 1 deletion bifrost/renderer/bifrost/onBeforeRenderClient.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { PageContextClient } from "vike/types";
import { Turbolinks } from "../../lib/turbolinks";
import { setBodyAttributes } from "../../lib/elementUtils";
import { recordExistingHeadScripts } from "../../lib/turbolinks/mergeHead";
import { instrument } from "../../lib/diagnostic.client";

export default instrument("bifrostOnBeforeRenderClient", async function bifrostOnBeforeRenderClient(
pageContext: PageContextClient
) {
if (!pageContext.isHydration && !pageContext.errorWhileRendering) {
if (pageContext.isHydration) {
// Snapshots clone the whole head, so anything already here (including the inline Turbolinks stub) would re-run on restore
recordExistingHeadScripts();
return;
}
if (!pageContext.errorWhileRendering) {
pageContext._shouldEmitBeforeRender = true;
await Turbolinks._vikeBeforeRender(pageContext._turbolinksVisit);

Expand Down
2 changes: 1 addition & 1 deletion bifrost/renderer/headHtmlBegin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

// Register turbolinks global for ios to hook into
// Also allows Turbolinks.visit / Turbolinks.controller.viewWillRender to be called before Turbolinks is started (Vike loads async)
const turbolinksIOSCompat = `window.Turbolinks = {controller:{restorationIdentifier: '',started:false,viewWillRender:(...a)=>{addEventListener("turbolinks:start", () => {window.Turbolinks.controller.viewWillRender(...a)})}},visit:(...a)=>{addEventListener("turbolinks:start", () => {window.Turbolinks.visit(...a)})}};`;
const turbolinksIOSCompat = `if(!(window.Turbolinks&&window.Turbolinks.controller&&window.Turbolinks.controller.started)){window.Turbolinks = {controller:{restorationIdentifier: '',started:false,viewWillRender:(...a)=>{addEventListener("turbolinks:start", () => {window.Turbolinks.controller.viewWillRender(...a)})}},visit:(...a)=>{addEventListener("turbolinks:start", () => {window.Turbolinks.visit(...a)})}}};`;

/// emit turbolinks:load on DOMContentLoaded
const turbolinksLoadEvent = `addEventListener("DOMContentLoaded", () => {
Expand Down
2 changes: 2 additions & 0 deletions bifrost/renderer/wrapped/onBeforeRenderClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Turbolinks } from "../../lib/turbolinks";
import {
mergeHead,
recordExistingHeadScripts,
recordTrackedElementBaseline,
} from "../../lib/turbolinks/mergeHead";
import {
setBodyAttributes,
Expand Down Expand Up @@ -37,6 +38,7 @@ export default instrument("wrappedOnBeforeRenderClient", async function wrappedO
proxyLayoutInfo: pageContext.proxyLayoutInfo,
});
recordExistingHeadScripts();
recordTrackedElementBaseline();
return;
}

Expand Down
37 changes: 37 additions & 0 deletions tests/e2e/specs/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,43 @@ test.describe("back button restoration", () => {
await customProxy.goForward();
});

test("history nav keeps working after entering on a vite page", async ({
page,
}) => {
ensureAllNetworkSucceeds(page);
const first = {
title: "first page",
content: "first page body content",
links: [{ title: "second page", content: "second page body content" }],
};
const customProxy = new CustomProxyPage(page, first);

await page.goto("./vite-page", { waitUntil: "networkidle" });
await waitForTurbolinksInit(page);
await ensureNoBrowserNavigation(page, async () => {
const loaded = waitForConsoleLog(page, (m) => m.text() === T.load);
await page.evaluate(
(path) => (window as any).Turbolinks.visit(path),
toPath(first)
);
await loaded;
});
customProxy.pageData = first;
await expect(page).toHaveTitle("first page");

await customProxy.clickLink("second page");

await ensureNoBrowserNavigation(page, async () => {
await customProxy.goBack();
// restoring the snapshot must not re-run the vite page's inline Turbolinks stub
expect(
await page.evaluate("window.Turbolinks.controller.started")
).toBe(true);
await customProxy.goForward();
await customProxy.goBack();
});
});

test("restores layout", async ({ page, context }) => {
const customProxy = new CustomProxyPage(page, {
title: "first page",
Expand Down
Loading