Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pre populate cache with views based on links with turbo_preload attribute #552

Merged
merged 16 commits into from
Jun 18, 2022
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
54 changes: 54 additions & 0 deletions src/core/drive/preloader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Navigator } from "./navigator"
import { PageSnapshot } from "./page_snapshot"
import { SnapshotCache } from "./snapshot_cache"

export interface PreloaderDelegate {
readonly navigator: Navigator
}

export class Preloader {
readonly delegate: PreloaderDelegate
readonly selector: string = 'a[data-turbo-preload]'

constructor(delegate: PreloaderDelegate) {
this.delegate = delegate
}

get snapshotCache(): SnapshotCache {
return this.delegate.navigator.view.snapshotCache
}

start() {
if (document.readyState === 'loading') {
return document.addEventListener('DOMContentLoaded', () => {
this.preloadOnLoadLinksForView(document.body)
});
} else {
this.preloadOnLoadLinksForView(document.body)
}
}

preloadOnLoadLinksForView(element: Element) {
for (const link of element.querySelectorAll<HTMLAnchorElement>(this.selector)) {
this.preloadURL(link)
}
}

async preloadURL(link: HTMLAnchorElement) {
const location = new URL(link.href)

if (this.snapshotCache.has(location)) {
return
}

try {
const response = await fetch(location.toString(), { headers: { 'VND.PREFETCH': 'true', 'Accept': 'text/html' } })
const responseText = await response.text()
const snapshot = PageSnapshot.fromHTMLString(responseText)

this.snapshotCache.put(location, snapshot)
} catch(_) {
// If we cannot preload that is ok!
}
}
}
4 changes: 4 additions & 0 deletions src/core/frames/frame_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ export class FrameController

viewRenderedSnapshot(_snapshot: Snapshot, _isPreview: boolean) {}

preloadOnLoadLinksForView(element: Element) {
session.preloadOnLoadLinksForView(element)
}

viewInvalidated() {}

// Private
Expand Down
10 changes: 9 additions & 1 deletion src/core/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Visit, VisitOptions } from "./drive/visit"
import { PageSnapshot } from "./drive/page_snapshot"
import { FrameElement } from "../elements/frame_element"
import { FetchResponse } from "../http/fetch_response"
import { Preloader, PreloaderDelegate } from "./drive/preloader"

export type TimingData = unknown

Expand All @@ -28,10 +29,12 @@ export class Session
LinkClickObserverDelegate,
NavigatorDelegate,
PageObserverDelegate,
PageViewDelegate
PageViewDelegate,
PreloaderDelegate
{
readonly navigator = new Navigator(this)
readonly history = new History(this)
readonly preloader = new Preloader(this)
readonly view = new PageView(this, document.documentElement)
adapter: Adapter = new BrowserAdapter(this)

Expand Down Expand Up @@ -59,6 +62,7 @@ export class Session
this.streamObserver.start()
this.frameRedirector.start()
this.history.start()
this.preloader.start()
this.started = true
this.enabled = true
}
Expand Down Expand Up @@ -267,6 +271,10 @@ export class Session
this.notifyApplicationAfterRender()
}

preloadOnLoadLinksForView(element: Element) {
this.preloader.preloadOnLoadLinksForView(element)
}

viewInvalidated(reason: ReloadReason) {
this.adapter.pageInvalidated(reason)
}
Expand Down
2 changes: 2 additions & 0 deletions src/core/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getAnchor } from "./url"

export interface ViewDelegate<S extends Snapshot> {
allowsImmediateRender(snapshot: S, resume: (value: any) => void): boolean
preloadOnLoadLinksForView(element: Element): void
viewRenderedSnapshot(snapshot: S, isPreview: boolean): void
viewInvalidated(reason: ReloadReason): void
}
Expand Down Expand Up @@ -89,6 +90,7 @@ export abstract class View<

await this.renderSnapshot(renderer)
this.delegate.viewRenderedSnapshot(snapshot, isPreview)
this.delegate.preloadOnLoadLinksForView(this.element)
this.finishRenderingSnapshot(renderer)
} finally {
delete this.renderer
Expand Down
14 changes: 14 additions & 0 deletions src/tests/fixtures/frame_preloading.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title>Page With Preloading Frame</title>
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
</head>

<body>
<turbo-frame id="menu" src="/src/tests/fixtures/frames/preloading.html"></turbo-frame>
</body>

</html>
4 changes: 4 additions & 0 deletions src/tests/fixtures/frames/preloading.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<turbo-frame id="menu">
<a href="/src/tests/fixtures/preloaded.html" id="frame_preload_anchor" data-turbo-preload="true">Visit preloaded
page</a>
</turbo-frame>
14 changes: 14 additions & 0 deletions src/tests/fixtures/hot_preloading.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title>Page That Links to Preloading Page</title>
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
</head>

<body>
<a href="/src/tests/fixtures/preloading.html" id="hot_preload_anchor">Next page has preloading</a>
</body>

</html>
16 changes: 16 additions & 0 deletions src/tests/fixtures/preloaded.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title>Preloaded Page</title>
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
</head>

<body>
<div>
This page was hopefully preloaded
</div>
</body>

</html>
16 changes: 16 additions & 0 deletions src/tests/fixtures/preloading.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title>Preloading Page</title>
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
</head>

<body>
<a href="/src/tests/fixtures/preloaded.html" id="preload_anchor" data-turbo-preload="true">
Visit preloaded page
</a>
</body>

</html>
1 change: 1 addition & 0 deletions src/tests/functional/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from "./loading_tests"
export * from "./navigation_tests"
export * from "./pausable_rendering_tests"
export * from "./pausable_requests_tests"
export * from "./preloader_tests"
export * from "./rendering_tests"
export * from "./scroll_restoration_tests"
export * from "./stream_tests"
Expand Down
49 changes: 49 additions & 0 deletions src/tests/functional/preloader_tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { TurboDriveTestCase } from "../helpers/turbo_drive_test_case"

export class PreloaderTests extends TurboDriveTestCase {
async "test preloads snapshot on initial load"() {
// contains `a[rel="preload"][href="http://localhost:9000/src/tests/fixtures/preloaded.html"]`
await this.goToLocation("/src/tests/fixtures/preloading.html")
await this.nextBeat

this.assert.ok(await this.remote.execute(() => {
const preloadedUrl = "http://localhost:9000/src/tests/fixtures/preloaded.html"
const cache = window.Turbo.session.preloader.snapshotCache.snapshots

return preloadedUrl in cache
}))
}

async "test preloads snapshot on page visit"() {
// contains `a[rel="preload"][href="http://localhost:9000/src/tests/fixtures/preloading.html"]`
await this.goToLocation("/src/tests/fixtures/hot_preloading.html")

// contains `a[rel="preload"][href="http://localhost:9000/src/tests/fixtures/preloaded.html"]`
await this.clickSelector("#hot_preload_anchor")
await this.waitUntilSelector("#preload_anchor")
await this.nextBeat

this.assert.ok(await this.remote.execute(() => {
const preloadedUrl = "http://localhost:9000/src/tests/fixtures/preloaded.html"
const cache = window.Turbo.session.preloader.snapshotCache.snapshots

return preloadedUrl in cache
}))
}

async "test navigates to preloaded snapshot from frame"() {
// contains `a[rel="preload"][href="http://localhost:9000/src/tests/fixtures/preloaded.html"]`
await this.goToLocation("/src/tests/fixtures/frame_preloading.html")
await this.waitUntilSelector("#frame_preload_anchor")
await this.nextBeat

this.assert.ok(await this.remote.execute(() => {
const preloadedUrl = "http://localhost:9000/src/tests/fixtures/preloaded.html"
const cache = window.Turbo.session.preloader.snapshotCache.snapshots

return preloadedUrl in cache
}))
}
}

PreloaderTests.registerSuite()