Skip to content

Commit

Permalink
Introduce client-side Turbo Cache Control API (#632)
Browse files Browse the repository at this point in the history
* Introduce client-side Turbo Cache Control API

This commit introduces a `Cache` class which exposes an API that can be
used to control the value of the `turbo-cache-control` meta element
form the client-side.

Most of the times you just need to the set the value of the
`turbo-cache-control` meta element from the server-side when you are
responding to your regular HTML request.

But there are some cases where you want to control the cache control
behavior from the client-side after a certain action happened on the
client (i.e. if you don't want to show remaining artefacts of an
finsihed animation).

You can achieve the same behavior by dynamically prepending a `<meta>`
element to the `<head>` via JavaScript, like:

```javascript
document.head.insertAdjacentHTML(
  'beforeend',
  '<meta name="turbo-cache-control" content="no-cache">'
)
```

Though, this feels very error-prone and doesn't account for duplicate
`<meta>` elements in the `<head>`.

This Pull Request aims to simplify this by exposing a client-side API to
control the value of the meta element.

The name of the exposed functions are kept in-line with the helpers in:
https://github.com/hotwired/turbo-rails/blob/43bf84a9b1d2cc78da6810b82f92be2c32c9dbfd/app/helpers/turbo/drive_helper.rb#L15

* Refactor to make use of util `getMetaContent()` throughout the codebase
  • Loading branch information
marcoroth authored Jul 17, 2022
1 parent 6eb2cde commit a54ac17
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 10 deletions.
30 changes: 30 additions & 0 deletions src/core/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Session } from "./session"
import { setMetaContent } from "../util"

export class Cache {
readonly session: Session

constructor(session: Session) {
this.session = session
}

clear() {
this.session.clearCache()
}

resetCacheControl() {
this.setCacheControl("")
}

exemptPageFromCache() {
this.setCacheControl("no-cache")
}

exemptPageFromPreview() {
this.setCacheControl("no-preview")
}

private setCacheControl(value: string) {
setMetaContent("turbo-cache-control", value)
}
}
7 changes: 1 addition & 6 deletions src/core/drive/form_submission.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FetchRequest, FetchMethod, fetchMethodFromString, FetchRequestHeaders } from "../../http/fetch_request"
import { FetchResponse } from "../../http/fetch_response"
import { expandURL } from "../url"
import { attributeTrue, dispatch } from "../../util"
import { attributeTrue, dispatch, getMetaContent } from "../../util"
import { StreamMessage } from "../streams/stream_message"

export interface FormSubmissionDelegate {
Expand Down Expand Up @@ -246,11 +246,6 @@ function getCookieValue(cookieName: string | null) {
}
}

function getMetaContent(name: string) {
const element: HTMLMetaElement | null = document.querySelector(`meta[name="${name}"]`)
return element && element.content
}

function responseSucceededWithoutRedirect(response: FetchResponse) {
return response.statusCode == 200 && !response.redirected
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/drive/progress_bar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { unindent } from "../../util"
import { unindent, getMetaContent } from "../../util"

export class ProgressBar {
static animationDuration = 300 /*ms*/
Expand Down Expand Up @@ -123,6 +123,6 @@ export class ProgressBar {
}

get cspNonce() {
return document.head.querySelector('meta[name="csp-nonce"]')?.getAttribute("content")
return getMetaContent("csp-nonce")
}
}
4 changes: 3 additions & 1 deletion src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Adapter } from "./native/adapter"
import { Session } from "./session"
import { Cache } from "./cache"
import { Locatable } from "./url"
import { StreamMessage } from "./streams/stream_message"
import { StreamSource } from "./types"
Expand All @@ -10,8 +11,9 @@ import { FrameRenderer } from "./frames/frame_renderer"
import { FormSubmission } from "./drive/form_submission"

const session = new Session()
const cache = new Cache(session)
const { navigator } = session
export { navigator, session, PageRenderer, PageSnapshot, FrameRenderer }
export { navigator, session, cache, PageRenderer, PageSnapshot, FrameRenderer }
export {
TurboBeforeCacheEvent,
TurboBeforeRenderEvent,
Expand Down
3 changes: 2 additions & 1 deletion src/core/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Bardo, BardoDelegate } from "./bardo"
import { Snapshot } from "./snapshot"
import { ReloadReason } from "./native/browser_adapter"
import { getMetaContent } from "../util"

type ResolvingFunctions<T = unknown> = {
resolve(value: T | PromiseLike<T>): void
Expand Down Expand Up @@ -106,7 +107,7 @@ export abstract class Renderer<E extends Element, S extends Snapshot<E> = Snapsh
}

get cspNonce() {
return document.head.querySelector('meta[name="csp-nonce"]')?.getAttribute("content")
return getMetaContent("csp-nonce")
}
}

Expand Down
24 changes: 24 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,27 @@ export function clearBusyState(...elements: Element[]) {
export function attributeTrue(element: Element, attributeName: string) {
return element.getAttribute(attributeName) === "true"
}

export function getMetaElement(name: string): HTMLMetaElement | null {
return document.querySelector(`meta[name="${name}"]`)
}

export function getMetaContent(name: string) {
const element = getMetaElement(name)
return element && element.content
}

export function setMetaContent(name: string, content: string) {
let element = getMetaElement(name)

if (!element) {
element = document.createElement("meta")
element.setAttribute("name", name)

document.head.appendChild(element)
}

element.setAttribute("content", content)

return element
}

0 comments on commit a54ac17

Please sign in to comment.