diff --git a/.changeset/nasty-seahorses-know.md b/.changeset/nasty-seahorses-know.md new file mode 100644 index 000000000000..5a59f9633b44 --- /dev/null +++ b/.changeset/nasty-seahorses-know.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[breaking] replace transformPage with transformPageChunk diff --git a/documentation/docs/06-hooks.md b/documentation/docs/06-hooks.md index 31c9e583c0a1..a6b28d5d28f5 100644 --- a/documentation/docs/06-hooks.md +++ b/documentation/docs/06-hooks.md @@ -67,7 +67,7 @@ You can add call multiple `handle` functions with [the `sequence` helper functio `resolve` also supports a second, optional parameter that gives you more control over how the response will be rendered. That parameter is an object that can have the following fields: - `ssr: boolean` (default `true`) — if `false`, renders an empty 'shell' page instead of server-side rendering -- `transformPage(opts: { html: string }): string` — applies custom transforms to HTML +- `transformPageChunk(opts: { html: string, done: boolean }): MaybePromise` — applies custom transforms to HTML. If `done` is true, it's the final chunk. Chunks are not guaranteed to be well-formed HTML (they could include an element's opening tag but not its closing tag, for example) but they will always be split at sensible boundaries such as `%sveltekit.head%` or layout/page components. ```js /// file: src/hooks.js @@ -75,7 +75,7 @@ You can add call multiple `handle` functions with [the `sequence` helper functio export async function handle({ event, resolve }) { const response = await resolve(event, { ssr: !event.url.pathname.startsWith('/admin'), - transformPage: ({ html }) => html.replace('old', 'new') + transformPageChunk: ({ html }) => html.replace('old', 'new') }); return response; diff --git a/documentation/docs/17-seo.md b/documentation/docs/17-seo.md index 4eb0b4e8e627..5e807a09c425 100644 --- a/documentation/docs/17-seo.md +++ b/documentation/docs/17-seo.md @@ -105,15 +105,19 @@ const config = { export default config; ``` -...and transforming the HTML using `transformPage` along with `transform` imported from `@sveltejs/amp`: +...and transforming the HTML using `transformPageChunk` along with `transform` imported from `@sveltejs/amp`: ```js import * as amp from '@sveltejs/amp'; /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { + let buffer = ''; return resolve(event, { - transformPage: ({ html }) => amp.transform(html) + transformPageChunk: ({ html, done }) => { + buffer += html; + if (done) return amp.transform(html); + } }); } ``` diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 9a77c95dc479..04406081ea58 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -171,7 +171,7 @@ export async function respond(request, options, state) { /** @type {import('types').RequiredResolveOptions} */ let resolve_opts = { ssr: true, - transformPage: default_transform + transformPageChunk: default_transform }; // TODO match route before calling handle? @@ -181,9 +181,17 @@ export async function respond(request, options, state) { event, resolve: async (event, opts) => { if (opts) { + // TODO remove for 1.0 + // @ts-expect-error + if (opts.transformPage) { + throw new Error( + 'transformPage has been replaced by transformPageChunk — see https://github.com/sveltejs/kit/pull/5657 for more information' + ); + } + resolve_opts = { ssr: opts.ssr !== false, - transformPage: opts.transformPage || default_transform + transformPageChunk: opts.transformPageChunk || default_transform }; } diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index fb6bc1bb4eac..a9d38a19e6d1 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -291,9 +291,12 @@ export async function render_response({ const assets = options.paths.assets || (segments.length > 0 ? segments.map(() => '..').join('/') : '.'); - const html = await resolve_opts.transformPage({ - html: options.template({ head, body, assets, nonce: /** @type {string} */ (csp.nonce) }) - }); + // TODO flush chunks as early as we can + const html = + (await resolve_opts.transformPageChunk({ + html: options.template({ head, body, assets, nonce: /** @type {string} */ (csp.nonce) }), + done: true + })) || ''; const headers = new Headers({ 'content-type': 'text/html', diff --git a/packages/kit/test/apps/amp/src/hooks.js b/packages/kit/test/apps/amp/src/hooks.js index c75f6f29116a..ab885588423b 100644 --- a/packages/kit/test/apps/amp/src/hooks.js +++ b/packages/kit/test/apps/amp/src/hooks.js @@ -3,22 +3,28 @@ import * as amp from '@sveltejs/amp'; /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { + let buffer = ''; + const response = await resolve(event, { - transformPage: ({ html }) => { - html = amp.transform(html); + transformPageChunk: ({ html, done }) => { + buffer += html; + + if (done) { + const html = amp.transform(buffer); - // remove unused CSS - let css = ''; - const markup = html.replace( - /`; - } - ); + // remove unused CSS + let css = ''; + const markup = html.replace( + /`; + } + ); - css = purify(markup, css); - return markup.replace('', `${css}`); + css = purify(markup, css); + return markup.replace('', `${css}`); + } } }); diff --git a/packages/kit/test/apps/basics/src/hooks.js b/packages/kit/test/apps/basics/src/hooks.js index 2af673b90932..9189f0b3acd4 100644 --- a/packages/kit/test/apps/basics/src/hooks.js +++ b/packages/kit/test/apps/basics/src/hooks.js @@ -47,7 +47,7 @@ export const handle = sequence( const response = await resolve(event, { ssr: !event.url.pathname.startsWith('/no-ssr'), - transformPage: event.url.pathname.startsWith('/transform-page') + transformPageChunk: event.url.pathname.startsWith('/transform-page-chunk') ? ({ html }) => html.replace('__REPLACEME__', 'Worked!') : undefined }); diff --git a/packages/kit/test/apps/basics/src/routes/transform-page/index.svelte b/packages/kit/test/apps/basics/src/routes/transform-page-chunk/index.svelte similarity index 100% rename from packages/kit/test/apps/basics/src/routes/transform-page/index.svelte rename to packages/kit/test/apps/basics/src/routes/transform-page-chunk/index.svelte diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index a2fbed968578..4de2e65a0a28 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -1009,8 +1009,8 @@ test.describe('Page options', () => { } }); - test('transformPage can change the html output', async ({ page }) => { - await page.goto('/transform-page'); + test('transformPageChunk can change the html output', async ({ page }) => { + await page.goto('/transform-page-chunk'); expect(await page.getAttribute('meta[name="transform-page"]', 'content')).toBe('Worked!'); }); diff --git a/packages/kit/test/prerendering/basics/src/hooks.js b/packages/kit/test/prerendering/basics/src/hooks.js index 9e353b692da1..b8df4f86b0a8 100644 --- a/packages/kit/test/prerendering/basics/src/hooks.js +++ b/packages/kit/test/prerendering/basics/src/hooks.js @@ -2,13 +2,14 @@ import { prerendering } from '$app/env'; const initial_prerendering = prerendering; +/** @type {import('@sveltejs/kit').Handle} */ export const handle = async ({ event, resolve }) => { if (event.url.pathname === '/prerendering-true' && prerendering) { return await resolve(event, { - transformPage: ({ html }) => + transformPageChunk: ({ html }) => html - .replace('__INITIAL_PRERENDERING__', initial_prerendering) - .replace('__PRERENDERING__', prerendering) + .replace('__INITIAL_PRERENDERING__', String(initial_prerendering)) + .replace('__PRERENDERING__', String(prerendering)) }); } return await resolve(event); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index cb9f9fc16376..dbf012c41eb3 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -261,7 +261,7 @@ export interface RequestHandlerOutput { export interface ResolveOptions { ssr?: boolean; - transformPage?: ({ html }: { html: string }) => MaybePromise; + transformPageChunk?: (input: { html: string; done: boolean }) => MaybePromise; } export type ResponseBody = JSONValue | Uint8Array | ReadableStream | Error;