diff --git a/.changeset/sweet-masks-leave.md b/.changeset/sweet-masks-leave.md new file mode 100644 index 000000000000..a21069748be9 --- /dev/null +++ b/.changeset/sweet-masks-leave.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Added template transform api, requested in the sapper repo #1695 #1642 diff --git a/documentation/docs/04-setup.md b/documentation/docs/04-setup.md index e7ea17c8f44a..c14889a4e683 100644 --- a/documentation/docs/04-setup.md +++ b/documentation/docs/04-setup.md @@ -2,9 +2,9 @@ title: Setup --- -An optional `src/setup.js` (or `src/setup.ts`, or `src/setup/index.js`) file exports two functions that run on the server — **prepare** and **getSession**. +An optional `src/setup.js` (or `src/setup.ts`, or `src/setup/index.js`) file exports three functions that run on the server — **prepare**, **transformTemplate** and **getSession**. -Both functions, if provided, run for every page or endpoint request SvelteKit receives. +These functions, if provided, run for every page or endpoint request SvelteKit receives. > The location of this file can be [configured](#configuration) as `config.kit.files.setup` @@ -65,17 +65,19 @@ export async function prepare(incoming) { headers['set-cookie'] = `session_id=${uuid()}; HttpOnly`; } + const darkMode = cookies.darkMode || false; + return { - headers + headers, + context: { -+ user: await db.get_user(cookies.session_id) ++ user: await db.get_user(cookies.session_id), ++ darkMode, + } }; } ``` - ### getSession This function takes the `context` returned from `prepare` and returns a `session` object that is safe to expose to the browser. @@ -101,4 +103,42 @@ export function getSession({ context }) { } ``` -> `session` must be serializable, which means it must not contain things like functions or custom classes, just built-in JavaScript data types \ No newline at end of file +> `session` must be serializable, which means it must not contain things like functions or custom classes, just built-in JavaScript data types + +### transformTemplate + +This function takes the `src/app.html template` and the `context` returned from `prepare`. It should return the template after transforming it. + +```js +/** + * @param {{ + * template: string + * context: any + * }} options + * @returns {string} + */ +export function transformTemplate({ context, template }) { + if (!context.darkMode) { + return template; + } + + return template.replace('%My.HtmlClass%', 'dark'); +} +``` + +> The corresponding `src/app.html` file would look like this: + +```html + + + + + + + %svelte.head% + + +
%svelte.body%
+ + +``` diff --git a/examples/sandbox/src/app.html b/examples/sandbox/src/app.html index 4b89e97a1e21..1bf90ce885b8 100644 --- a/examples/sandbox/src/app.html +++ b/examples/sandbox/src/app.html @@ -1,12 +1,12 @@ - - - - - - %svelte.head% - - -
%svelte.body%
- - \ No newline at end of file + + + + + + %svelte.head% + + +
%svelte.body%
+ + diff --git a/examples/sandbox/src/setup/index.js b/examples/sandbox/src/setup/index.js index d91b52c284fa..482f1429581c 100644 --- a/examples/sandbox/src/setup/index.js +++ b/examples/sandbox/src/setup/index.js @@ -1,7 +1,8 @@ export function prepare({ headers }) { return { context: { - answer: 42 + answer: 42, + darkMode: true }, headers: { 'x-foo': 'banana' @@ -12,3 +13,10 @@ export function prepare({ headers }) { export function getSession({ context }) { return context; } + +export function transformTemplate({ context, template }) { + if (context.darkMode) { + return template.replace('%svelte.htmlClass%', 'dark'); + } + return template; +} diff --git a/packages/kit/src/runtime/server/page.js b/packages/kit/src/runtime/server/page.js index 104ee43052f5..bd01e206874e 100644 --- a/packages/kit/src/runtime/server/page.js +++ b/packages/kit/src/runtime/server/page.js @@ -10,13 +10,14 @@ import { ssr } from './index.js'; * request: import('types.internal').Request; * options: import('types.internal').SSRRenderOptions; * $session: any; + * context: any; * route: import('types.internal').SSRPage; * status: number; * error: Error * }} opts * @returns {Promise} */ -async function get_response({ request, options, $session, route, status = 200, error }) { +async function get_response({ request, options, context, $session, route, status = 200, error }) { const host = options.host || request.headers[options.host_header]; /** @type {Record} */ @@ -181,7 +182,7 @@ async function get_response({ request, options, $session, route, status = 200, e const components = []; const props_promises = []; - let context = {}; + let loadContext = {}; let maxage; if (options.only_render_prerenderable_pages) { @@ -214,7 +215,7 @@ async function get_response({ request, options, $session, route, status = 200, e return $session; }, fetch: fetcher, - context: { ...context } + context: { ...loadContext } }); if (!loaded) return; @@ -241,6 +242,7 @@ async function get_response({ request, options, $session, route, status = 200, e options, $session, route, + context, status: loaded.status, error: loaded.error }); @@ -256,8 +258,8 @@ async function get_response({ request, options, $session, route, status = 200, e } if (loaded.context) { - context = { - ...context, + loadContext = { + ...loadContext, ...loaded.context }; } @@ -314,6 +316,7 @@ async function get_response({ request, options, $session, route, status = 200, e request, options, $session, + context, route, status: 500, error: e instanceof Error ? e : { name: 'Error', message: e.toString() } @@ -393,10 +396,16 @@ async function get_response({ request, options, $session, route, status = 200, e headers['cache-control'] = `${uses_credentials ? 'private' : 'public'}, max-age=${maxage}`; } + let template = options.template({ head, body }); + + if (options.setup.transformTemplate) { + template = (await options.setup.transformTemplate({ template, context })) || template; + } + return { status, headers, - body: options.template({ head, body }), + body: template, dependencies }; } @@ -424,6 +433,7 @@ export default async function render_page(request, route, context, options) { request, options, $session, + context, route, status: route ? 200 : 404, error: route ? null : new Error(`Not found: ${request.path}`) diff --git a/packages/kit/test/apps/template-transform/src/app.html b/packages/kit/test/apps/template-transform/src/app.html new file mode 100644 index 000000000000..279555a4f2ce --- /dev/null +++ b/packages/kit/test/apps/template-transform/src/app.html @@ -0,0 +1,11 @@ + + + + + + %svelte.head% + + + %svelte.body% + + diff --git a/packages/kit/test/apps/template-transform/src/routes/__tests__.js b/packages/kit/test/apps/template-transform/src/routes/__tests__.js new file mode 100644 index 000000000000..0a7ffb2274bd --- /dev/null +++ b/packages/kit/test/apps/template-transform/src/routes/__tests__.js @@ -0,0 +1,8 @@ +import * as assert from 'uvu/assert'; + +/** @type {import('../../../../types').TestMaker} */ +export default function (test) { + test('Should apply dark class to html element', '/', async ({ page }) => { + assert.equal(await page.evaluate(() => document.documentElement.className), 'dark'); + }); +} diff --git a/packages/kit/test/apps/template-transform/src/routes/index.svelte b/packages/kit/test/apps/template-transform/src/routes/index.svelte new file mode 100644 index 000000000000..32011979eb04 --- /dev/null +++ b/packages/kit/test/apps/template-transform/src/routes/index.svelte @@ -0,0 +1 @@ +Template Transform Test diff --git a/packages/kit/test/apps/template-transform/src/setup.js b/packages/kit/test/apps/template-transform/src/setup.js new file mode 100644 index 000000000000..adbb214a2fc8 --- /dev/null +++ b/packages/kit/test/apps/template-transform/src/setup.js @@ -0,0 +1,28 @@ +export function prepare() { + return { + context: { + darkMode: true, + answer: 42 + } + }; +} + +/** @param {any} context */ +export function getSession({ context }) { + return context; +} + +/** + * @param {{ + * template: string + * context: any + * }} options + * @returns {string} + */ +export function transformTemplate({ context, template }) { + if (!context.darkMode) { + return template; + } + + return template.replace('%svelte.htmlClass%', 'dark'); +} diff --git a/packages/kit/test/apps/template-transform/svelte.config.cjs b/packages/kit/test/apps/template-transform/svelte.config.cjs new file mode 100644 index 000000000000..f577cfed85ad --- /dev/null +++ b/packages/kit/test/apps/template-transform/svelte.config.cjs @@ -0,0 +1,11 @@ +module.exports = { + kit: { + hostHeader: 'x-forwarded-host', + vite: { + build: { + minify: false + }, + clearScreen: false + } + } +}; diff --git a/packages/kit/types.internal.d.ts b/packages/kit/types.internal.d.ts index 1576c3392584..8c86ab66a673 100644 --- a/packages/kit/types.internal.d.ts +++ b/packages/kit/types.internal.d.ts @@ -185,6 +185,7 @@ export type SSRRenderOptions = { headers?: Headers; }; getSession?: ({ context }: { context: any }) => any; + transformTemplate?: ({ context, template }: { context: any; template: string }) => string; }; dev?: boolean; amp?: boolean;