-
Notifications
You must be signed in to change notification settings - Fork 435
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit extends the Turbo cache API to allow for the use of different cache stores. The default store is still the in-memory store, but a new persistent cache store is now available. The disk store uses the [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage) to store and retrieve snapshots from the browser's cache. This allows for the snapshots to be persisted across different tabs, page reloads and even browser restarts. The disk store is not enabled by default. To enable it, you need to set the `Turbo.cache.store` property to `"disk"`. ```js Turbo.cache.store = "disk" ``` This is also a stepping stone to implement offline support with Service Workers. With a Service Worker in place, and the disk cache store enabled, we can still serve cached snapshots even when the browser is offline.
- Loading branch information
Alberto Fernández-Capel
authored
Sep 25, 2023
1 parent
3d30fe1
commit f86a376
Showing
11 changed files
with
286 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { PageSnapshot } from "../page_snapshot" | ||
|
||
export class DiskStore { | ||
_version = "v1" | ||
|
||
constructor() { | ||
if (typeof caches === "undefined") { | ||
throw new Error("windows.caches is undefined. CacheStore requires a secure context.") | ||
} | ||
|
||
this.storage = this.openStorage() | ||
} | ||
|
||
async has(location) { | ||
const storage = await this.openStorage() | ||
return (await storage.match(location)) !== undefined | ||
} | ||
|
||
async get(location) { | ||
const storage = await this.openStorage() | ||
const response = await storage.match(location) | ||
|
||
if (response && response.ok) { | ||
const html = await response.text() | ||
return PageSnapshot.fromHTMLString(html) | ||
} | ||
} | ||
|
||
async put(location, snapshot) { | ||
const storage = await this.openStorage() | ||
|
||
const response = new Response(snapshot.html, { | ||
status: 200, | ||
statusText: "OK", | ||
headers: { | ||
"Content-Type": "text/html" | ||
} | ||
}) | ||
await storage.put(location, response) | ||
return snapshot | ||
} | ||
|
||
async clear() { | ||
const storage = await this.openStorage() | ||
const keys = await storage.keys() | ||
await Promise.all(keys.map((key) => storage.delete(key))) | ||
} | ||
|
||
openStorage() { | ||
this.storage ||= caches.open(`turbo-${this.version}`) | ||
return this.storage | ||
} | ||
|
||
set version(value) { | ||
if (value !== this._version) { | ||
this._version = value | ||
this.storage ||= caches.open(`turbo-${this.version}`) | ||
} | ||
} | ||
|
||
get version() { | ||
return this._version | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { toCacheKey } from "../../url" | ||
|
||
export class MemoryStore { | ||
keys = [] | ||
snapshots = {} | ||
|
||
constructor(size) { | ||
this.size = size | ||
} | ||
|
||
async has(location) { | ||
return toCacheKey(location) in this.snapshots | ||
} | ||
|
||
async get(location) { | ||
if (await this.has(location)) { | ||
const snapshot = this.read(location) | ||
this.touch(location) | ||
return snapshot | ||
} | ||
} | ||
|
||
async put(location, snapshot) { | ||
this.write(location, snapshot) | ||
this.touch(location) | ||
return snapshot | ||
} | ||
|
||
async clear() { | ||
this.snapshots = {} | ||
} | ||
|
||
// Private | ||
|
||
read(location) { | ||
return this.snapshots[toCacheKey(location)] | ||
} | ||
|
||
write(location, snapshot) { | ||
this.snapshots[toCacheKey(location)] = snapshot | ||
} | ||
|
||
touch(location) { | ||
const key = toCacheKey(location) | ||
const index = this.keys.indexOf(key) | ||
if (index > -1) this.keys.splice(index, 1) | ||
this.keys.unshift(key) | ||
this.trim() | ||
} | ||
|
||
trim() { | ||
for (const key of this.keys.splice(this.size)) { | ||
delete this.snapshots[key] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,56 +1,35 @@ | ||
import { toCacheKey } from "../url" | ||
import { DiskStore } from "./cache_stores/disk_store" | ||
import { MemoryStore } from "./cache_stores/memory_store" | ||
|
||
export class SnapshotCache { | ||
keys = [] | ||
snapshots = {} | ||
|
||
constructor(size) { | ||
this.size = size | ||
static currentStore = new MemoryStore(10) | ||
|
||
static setStore(storeName) { | ||
switch (storeName) { | ||
case "memory": | ||
SnapshotCache.currentStore = new MemoryStore(10) | ||
break | ||
case "disk": | ||
SnapshotCache.currentStore = new DiskStore() | ||
break | ||
default: | ||
throw new Error(`Invalid store name: ${storeName}`) | ||
} | ||
} | ||
|
||
has(location) { | ||
return toCacheKey(location) in this.snapshots | ||
return SnapshotCache.currentStore.has(location) | ||
} | ||
|
||
get(location) { | ||
if (this.has(location)) { | ||
const snapshot = this.read(location) | ||
this.touch(location) | ||
return snapshot | ||
} | ||
return SnapshotCache.currentStore.get(location) | ||
} | ||
|
||
put(location, snapshot) { | ||
this.write(location, snapshot) | ||
this.touch(location) | ||
return snapshot | ||
return SnapshotCache.currentStore.put(location, snapshot) | ||
} | ||
|
||
clear() { | ||
this.snapshots = {} | ||
} | ||
|
||
// Private | ||
|
||
read(location) { | ||
return this.snapshots[toCacheKey(location)] | ||
} | ||
|
||
write(location, snapshot) { | ||
this.snapshots[toCacheKey(location)] = snapshot | ||
} | ||
|
||
touch(location) { | ||
const key = toCacheKey(location) | ||
const index = this.keys.indexOf(key) | ||
if (index > -1) this.keys.splice(index, 1) | ||
this.keys.unshift(key) | ||
this.trim() | ||
} | ||
|
||
trim() { | ||
for (const key of this.keys.splice(this.size)) { | ||
delete this.snapshots[key] | ||
} | ||
return SnapshotCache.currentStore.clear() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<!DOCTYPE html> | ||
<html id="html" data-skip-event-details="turbo:submit-start turbo:submit-end"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="csp-nonce" content="123"> | ||
<link rel="icon" href="data:;base64,iVBORw0KGgo="> | ||
<title>Turbo</title> | ||
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script> | ||
<script src="/src/tests/fixtures/test.js"></script> | ||
<script> | ||
Turbo.cache.store = "disk" | ||
|
||
document.addEventListener("turbo:load", async () => { | ||
await new Promise(resolve => setTimeout(resolve, 100)) | ||
|
||
const cachesList = document.getElementById("caches") | ||
|
||
const cache = await caches.open("turbo-v1") | ||
const keys = await cache.keys() | ||
cachesList.innerHTML = keys.map(key => `<li>${key.url}</li>`).join("") | ||
|
||
const clearCacheButton = document.getElementById("clear-cache") | ||
clearCacheButton.addEventListener("click", async (event) => { | ||
await Turbo.cache.clear() | ||
cachesList.innerHTML = "" | ||
}) | ||
}) | ||
</script> | ||
</head> | ||
<body> | ||
<h1>Cached pages:</h1> | ||
<ul id="caches"></ul> | ||
|
||
<h3>Links:</h3> | ||
<ul> | ||
<li> | ||
<a id="first-link" href="./disk_cache.html">First HTTP cached page</a> | ||
</li> | ||
<li> | ||
<a id="second-link" href="./disk_cache.html?page=2">Second HTTP cached page</a> | ||
</li> | ||
<li> | ||
<a id="third-link" href="./disk_cache.html?page=3">Third HTTP cached page</a> | ||
</li> | ||
</ul> | ||
|
||
<button id="clear-cache">Clear cache</button> | ||
</body> | ||
</html> |
Oops, something went wrong.