Skip to content

Commit

Permalink
pre populate cache with views based on links with turbo_preload att…
Browse files Browse the repository at this point in the history
…ribute (#552)

* (feat) add preloader

* (internal) add test stub

* (internal) restructure preloading into the view

* (fix) still handle cold boots

* (feat) add some test cases

* (fix) tweak tests so I can atleast run them (still failing)

* (feat) be asyncy awaity

* refine anchor type earlier so we know it will have a href attr

* change attribute from data-turbo-preload to rel='preload'

* feat almost working tests

* (fix) revert to turbo_preload

* (fix) properly handle preloader start

* simplify element selector

* (fix) hopefully test suite is now green

* (fix) oops

* make sure we wait sufficiently long enough for preloading to happen
  • Loading branch information
hey-leon authored Jun 18, 2022
1 parent f932cb4 commit acf23e8
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 1 deletion.
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 @@ -60,6 +63,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 @@ -272,6 +276,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()

0 comments on commit acf23e8

Please sign in to comment.