Skip to content

Commit

Permalink
chore(perf): include a high-priority image in Link response headers, …
Browse files Browse the repository at this point in the history
…if available
  • Loading branch information
joeyAghion committed Dec 13, 2024
1 parent 0a03171 commit 41b2d27
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import fs from "fs"
import path from "path"
import { getWebpackEarlyHints } from "Server/getWebpackEarlyHints"
import { getEarlyHints } from "Server/getEarlyHints"

jest.mock("fs")
jest.mock("Server/config", () => ({ CDN_URL: "https://cdn.example.com" }))

const HINTS_PATH = path.join(process.cwd(), "dist", "early-hints.json")

describe("getWebpackEarlyHints", () => {
describe("getEarlyHints", () => {
const mockReadFileSync = fs.readFileSync as jest.Mock

afterEach(() => {
Expand All @@ -20,7 +20,7 @@ describe("getWebpackEarlyHints", () => {

mockReadFileSync.mockReturnValueOnce(JSON.stringify(mockChunkFiles))

const result = getWebpackEarlyHints()
const result = getEarlyHints("")

expect(fs.readFileSync).toHaveBeenCalledWith(HINTS_PATH, "utf-8")
expect(result.linkHeaders).toEqual([
Expand All @@ -32,23 +32,4 @@ describe("getWebpackEarlyHints", () => {
`<link rel="preload" as="script" href="https://cdn.example.com/chunk2.js">`,
])
})

it("should return link headers and preload tags without CDN URL in development", () => {
process.env.NODE_ENV = "development"
const mockChunkFiles = ["/chunk1.js", "/chunk2.js"]

mockReadFileSync.mockReturnValueOnce(JSON.stringify(mockChunkFiles))

const result = getWebpackEarlyHints()

expect(fs.readFileSync).toHaveBeenCalledWith(HINTS_PATH, "utf-8")
expect(result.linkHeaders).toEqual([
`</chunk1.js>; rel=preload; as=script`,
`</chunk2.js>; rel=preload; as=script`,
])
expect(result.linkPreloadTags).toEqual([
`<link rel="preload" as="script" href="/chunk1.js">`,
`<link rel="preload" as="script" href="/chunk2.js">`,
])
})
})
32 changes: 19 additions & 13 deletions src/Server/getWebpackEarlyHints.ts → src/Server/getEarlyHints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,20 @@ import path from "path"
import fs from "fs"

const HINTS_PATH = path.join(process.cwd(), "dist", "early-hints.json")
const PRIORITY_IMAGE_REGEX = /<img\s[^>]*fetchpriority="high"[^>]*>/gi
const IMAGE_SRC_REGEX = /\bsrc="([^"]+)"/i

export const getWebpackEarlyHints = (): {
const cdnUrl = (() => {
if (process.env.NODE_ENV === "development") {
return ""
}

return CDN_URL
})()

export const getEarlyHints = (
body: string
): {
linkHeaders: string[]
linkPreloadTags: string[]
} => {
Expand All @@ -13,25 +25,14 @@ export const getWebpackEarlyHints = (): {
try {
chunkFiles = JSON.parse(fs.readFileSync(HINTS_PATH, "utf-8"))
} catch (error) {
console.error(
"[getWebpackEarlyHints] Could not load webpack early-hints.json:",
error
)
console.error("[getEarlyHints] Could not load early-hints.json:", error)

return {
linkHeaders: [],
linkPreloadTags: [],
}
}

const cdnUrl = (() => {
if (process.env.NODE_ENV === "development") {
return ""
}

return CDN_URL
})()

const links = chunkFiles.reduce(
(acc, file) => {
acc.linkHeaders.push(`<${cdnUrl}${file}>; rel=preload; as=script`)
Expand All @@ -46,5 +47,10 @@ export const getWebpackEarlyHints = (): {
}
)

const mainImage = body.match(PRIORITY_IMAGE_REGEX)?.[0]
const mainImageSrc = mainImage?.match(IMAGE_SRC_REGEX)?.[1]
if (mainImageSrc)
links.linkHeaders.unshift(`<${mainImageSrc}>; rel=preload; as=image`)

return links
}
32 changes: 0 additions & 32 deletions src/Server/middleware/linkHeadersMiddleware.ts

This file was deleted.

23 changes: 20 additions & 3 deletions src/System/Router/renderServerApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { loadAssetManifest } from "Server/manifest"
import { ENABLE_SSR_STREAMING } from "Server/config"
import { getENV } from "Utils/getENV"
import { ServerAppResults } from "System/Router/serverRouter"
import { getWebpackEarlyHints } from "Server/getWebpackEarlyHints"
import { getEarlyHints } from "Server/getEarlyHints"
import { RenderToStreamResult } from "System/Router/Utils/renderToStream"
import { buildHtmlTemplate, HTMLProps } from "html"

// TODO: Use the same variables as the asset middleware. Both config and sharify
// have a default CDN_URL while this does not.
const { CDN_URL, NODE_ENV, GEMINI_CLOUDFRONT_URL } = process.env
const { CDN_URL, NODE_ENV, GEMINI_CLOUDFRONT_URL, WEBFONT_URL } = process.env

const MANIFEST = loadAssetManifest("dist/manifest.json")

Expand Down Expand Up @@ -48,7 +48,7 @@ export const renderServerApp = ({

const scripts = extractScriptTags?.()

const { linkPreloadTags } = getWebpackEarlyHints()
const { linkHeaders, linkPreloadTags } = getEarlyHints(html ?? "")

const options: HTMLProps = {
cdnUrl: NODE_ENV === "production" ? (CDN_URL as string) : "",
Expand Down Expand Up @@ -84,6 +84,8 @@ export const renderServerApp = ({
},
}

setLinkHeaders(linkHeaders, res)

const statusCode = getENV("statusCode") ?? code

const htmlShell = buildHtmlTemplate(options)
Expand All @@ -107,3 +109,18 @@ export const renderServerApp = ({
res.status(statusCode).send(htmlShell)
}
}

const setLinkHeaders = (linkHeaders: string[], res: ArtsyResponse) => {
if (!res.headersSent) {
res.header("Link", [
`<${CDN_URL}>; rel=preconnect; crossorigin`,
`<${GEMINI_CLOUDFRONT_URL}>; rel=preconnect;`,
`<${WEBFONT_URL}>; rel=preconnect; crossorigin`,
`<${WEBFONT_URL}/all-webfonts.css>; rel=preload; as=style`,
`<${WEBFONT_URL}/ll-unica77_regular.woff2>; rel=preload; as=font; crossorigin`,
`<${WEBFONT_URL}/ll-unica77_medium.woff2>; rel=preload; as=font; crossorigin`,
`<${WEBFONT_URL}/ll-unica77_italic.woff2>; rel=preload; as=font; crossorigin`,
...linkHeaders,
])
}
}
4 changes: 0 additions & 4 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
import { morganMiddleware } from "./Server/middleware/morgan"
import { ensureSslMiddleware } from "./Server/middleware/ensureSsl"
import { hstsMiddleware } from "./Server/middleware/hsts"
import { linkHeadersMiddleware } from "./Server/middleware/linkHeadersMiddleware"
import { ipFilter } from "./Server/middleware/ipFilter"
import { sessionMiddleware } from "./Server/middleware/session"
import { assetMiddleware } from "./Server/middleware/assetMiddleware"
Expand Down Expand Up @@ -175,9 +174,6 @@ export function initializeMiddleware(app) {
registerFeatureFlagService(UnleashService, UnleashFeatureFlagService)
app.use(featureFlagMiddleware(UnleashService))

// Preconnect or preload associated links
app.use(linkHeadersMiddleware)

/**
* Routes for pinging system time and up
*/
Expand Down

0 comments on commit 41b2d27

Please sign in to comment.