Skip to content

Commit d1b7a8a

Browse files
committedDec 13, 2024
fix: app.baseURL prerendering image resolving
Fixes #292
1 parent 78e292e commit d1b7a8a

12 files changed

+97
-9
lines changed
 

‎src/runtime/server/og-image/context.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import type {
77
} from '../../types'
88
import type ChromiumRenderer from './chromium/renderer'
99
import type SatoriRenderer from './satori/renderer'
10-
import { useNitroApp } from 'nitropack/runtime'
1110
import { htmlPayloadCache, prerenderOptionsCache } from '#og-image-cache'
1211
import { theme } from '#og-image-virtual/unocss-config.mjs'
12+
import { createSitePathResolver } from '#site-config/server/composables/utils'
1313
import { createGenerator } from '@unocss/core'
1414
import presetWind from '@unocss/preset-wind'
1515
import { defu } from 'defu'
1616
import { parse } from 'devalue'
1717
import { createError, getQuery } from 'h3'
18+
import { useNitroApp } from 'nitropack/runtime'
1819
import { hash } from 'ohash'
1920
import { parseURL, withoutLeadingSlash, withoutTrailingSlash, withQuery } from 'ufo'
2021
import { normalizeKey } from 'unstorage'
@@ -24,7 +25,6 @@ import { createNitroRouteRuleMatcher } from '../util/kit'
2425
import { logger } from '../util/logger'
2526
import { normaliseOptions } from '../util/options'
2627
import { useChromiumRenderer, useSatoriRenderer } from './instances'
27-
import { createSitePathResolver } from '#site-config/server/composables/utils'
2828

2929
export function resolvePathCacheKey(e: H3Event, path?: string) {
3030
const siteConfig = e.context.siteConfig.get()
@@ -130,6 +130,7 @@ export async function resolveContext(e: H3Event): Promise<H3Error | OgImageRende
130130
key,
131131
renderer,
132132
isDebugJsonPayload,
133+
runtimeConfig,
133134
publicStoragePath: runtimeConfig.publicStoragePath,
134135
extension,
135136
basePath,
@@ -245,7 +246,8 @@ async function fetchPathHtmlAndExtractOptions(e: H3Event, path: string, key: str
245246
const err = handleNon200Response(htmlRes, path)
246247
if (err) {
247248
logger.warn(err)
248-
} else {
249+
}
250+
else {
249251
html = htmlRes._data || await htmlRes.text()
250252
_payload = getPayloadFromHtml(html)
251253
}

‎src/runtime/server/og-image/satori/plugins/imageSrc.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default defineSatoriTransformer([
2020
// fix <img src="">
2121
{
2222
filter: (node: VNode) => node.type === 'img' && node.props?.src,
23-
transform: async (node: VNode, { e, publicStoragePath }: OgImageRenderEventContext) => {
23+
transform: async (node: VNode, { e, publicStoragePath, runtimeConfig }: OgImageRenderEventContext) => {
2424
const src = node.props.src!
2525
const isRelative = src.startsWith('/')
2626
let dimensions
@@ -32,10 +32,11 @@ export default defineSatoriTransformer([
3232

3333
if (isRelative) {
3434
if (import.meta.prerender || import.meta.dev) {
35+
const srcWithoutBase = src.replace(runtimeConfig.app.baseURL, '')
3536
// try hydrating from storage
3637
// we need to read the file using unstorage
3738
// because we can't fetch public files using $fetch when prerendering
38-
imageBuffer = await resolveLocalFilePathImage(publicStoragePath, src)
39+
imageBuffer = await resolveLocalFilePathImage(publicStoragePath, srcWithoutBase)
3940
}
4041
else {
4142
// see if we can fetch it from a kv host if we're using an edge provider
@@ -96,14 +97,15 @@ export default defineSatoriTransformer([
9697
// fix style="background-image: url('')"
9798
{
9899
filter: (node: VNode) => node.props?.style?.backgroundImage?.includes('url('),
99-
transform: async (node: VNode, { e, publicStoragePath }: OgImageRenderEventContext) => {
100+
transform: async (node: VNode, { e, publicStoragePath, runtimeConfig }: OgImageRenderEventContext) => {
100101
// same as the above, need to swap out relative background images for absolute
101102
const backgroundImage = node.props.style!.backgroundImage!
102103
const src = backgroundImage.replace(/^url\(['"]?/, '').replace(/['"]?\)$/, '')
103104
const isRelative = src?.startsWith('/')
104105
if (isRelative) {
105106
if (import.meta.prerender || import.meta.dev) {
106-
const imageBuffer = await resolveLocalFilePathImage(publicStoragePath, src)
107+
const srcWithoutBase = src.replace(runtimeConfig.app.baseURL, '/')
108+
const imageBuffer = await resolveLocalFilePathImage(publicStoragePath, srcWithoutBase)
107109
if (imageBuffer) {
108110
const base64 = toBase64Image(Buffer.from(imageBuffer as ArrayBuffer))
109111
node.props.style!.backgroundImage = `url(${base64})`

‎src/runtime/server/plugins/prerender.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { defineNitroPlugin } from '#imports'
22
import { prerenderOptionsCache } from '#og-image-cache'
3+
import { createSitePathResolver } from '#site-config/server/composables/utils'
34
import { parseURL } from 'ufo'
45
import { isInternalRoute } from '../../pure'
56
import { extractAndNormaliseOgImageOptions, resolvePathCacheKey } from '../og-image/context'
@@ -26,7 +27,11 @@ export default defineNitroPlugin(async (nitro) => {
2627
].join('\n'))
2728
if (!options)
2829
return
29-
const key = resolvePathCacheKey(ctx.event)
30+
const resolvePathWithBase = createSitePathResolver(ctx.event, {
31+
absolute: false,
32+
withBase: true,
33+
})
34+
const key = resolvePathCacheKey(ctx.event, resolvePathWithBase(path))
3035
await prerenderOptionsCache!.setItem(key, options)
3136
// if we're prerendering then we don't need these options in the final HTML
3237
const index = html.bodyAppend.findIndex(script => script.includes('id="nuxt-og-image-options"'))

‎src/runtime/shared.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,11 @@ export function getOgImagePath(pagePath: string, _options?: Partial<OgImageOptio
4343
}
4444

4545
export function useOgImageRuntimeConfig() {
46-
return useRuntimeConfig()['nuxt-og-image'] as any as OgImageRuntimeConfig
46+
const c = useRuntimeConfig()
47+
return {
48+
...(c['nuxt-og-image'] as Record<string, any>),
49+
app: {
50+
baseURL: c.app.baseURL,
51+
},
52+
} as any as OgImageRuntimeConfig
4753
}

‎src/runtime/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface OgImageRenderEventContext {
1919
options: OgImageOptions
2020
isDebugJsonPayload: boolean
2121
publicStoragePath: string
22+
runtimeConfig: OgImageRuntimeConfig
2223
_nitro: NitroApp
2324
}
2425

@@ -43,6 +44,10 @@ export interface OgImageRuntimeConfig {
4344
isNuxtContentDocumentDriven: boolean
4445
strictNuxtContentPaths: boolean
4546
zeroRuntime: boolean
47+
48+
app: {
49+
baseURL: string
50+
}
4651
}
4752

4853
export interface OgImageComponent {
Loading
Loading
Loading
Loading
Loading
Loading

‎test/integration/base.test.ts

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { createResolver } from '@nuxt/kit'
2+
import { $fetch, setup } from '@nuxt/test-utils/e2e'
3+
import { toMatchImageSnapshot } from 'jest-image-snapshot'
4+
import { describe, expect, it } from 'vitest'
5+
6+
const { resolve } = createResolver(import.meta.url)
7+
8+
await setup({
9+
rootDir: resolve('../fixtures/basic'),
10+
server: true,
11+
build: true,
12+
nuxtConfig: {
13+
app: {
14+
baseURL: '/prefix/',
15+
},
16+
},
17+
})
18+
19+
expect.extend({ toMatchImageSnapshot })
20+
21+
describe('build', () => {
22+
it.runIf(process.env.HAS_CHROME)('chromium tests', async () => {
23+
const chromium: ArrayBuffer = await $fetch('/prefix/__og-image__/image/chromium/og.png', {
24+
responseType: 'arrayBuffer',
25+
})
26+
27+
expect(Buffer.from(chromium)).toMatchImageSnapshot()
28+
})
29+
it('static images', async () => {
30+
const customFont: ArrayBuffer = await $fetch('/prefix/__og-image__/static/satori/custom-font/og.png', {
31+
responseType: 'arrayBuffer',
32+
})
33+
34+
expect(Buffer.from(customFont)).toMatchImageSnapshot()
35+
36+
const image: ArrayBuffer = await $fetch('/prefix/__og-image__/static/satori/image/og.png', {
37+
responseType: 'arrayBuffer',
38+
})
39+
40+
expect(Buffer.from(image)).toMatchImageSnapshot()
41+
42+
const defaults: ArrayBuffer = await $fetch('/prefix/__og-image__/static/satori/og.png', {
43+
responseType: 'arrayBuffer',
44+
})
45+
46+
expect(Buffer.from(defaults)).toMatchImageSnapshot()
47+
}, 60000)
48+
49+
it('dynamic images', async () => {
50+
const inlineRouteRules: ArrayBuffer = await $fetch('/prefix/__og-image__/image/satori/route-rules/inline/og.png', {
51+
responseType: 'arrayBuffer',
52+
})
53+
54+
expect(Buffer.from(inlineRouteRules)).toMatchImageSnapshot()
55+
56+
const overrideRouteRules: ArrayBuffer = await $fetch('/prefix/__og-image__/image/satori/route-rules/config/og.png', {
57+
responseType: 'arrayBuffer',
58+
})
59+
60+
expect(Buffer.from(overrideRouteRules)).toMatchImageSnapshot()
61+
62+
const globalRouteRules: ArrayBuffer = await $fetch('/prefix/__og-image__/image/satori/route-rules/og.png', {
63+
responseType: 'arrayBuffer',
64+
})
65+
66+
expect(Buffer.from(globalRouteRules)).toMatchImageSnapshot()
67+
})
68+
})

0 commit comments

Comments
 (0)
Please sign in to comment.