From 1985b5608107f9da3e05c7375d13c1631a5cfb59 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 25 Aug 2022 22:55:14 -0400 Subject: [PATCH 01/23] serialize initial data with devalue --- packages/kit/src/runtime/client/client.js | 28 ++++------- packages/kit/src/runtime/client/start.js | 2 + packages/kit/src/runtime/client/types.d.ts | 2 + packages/kit/src/runtime/server/index.js | 1 - packages/kit/src/runtime/server/page/index.js | 1 - .../kit/src/runtime/server/page/load_data.js | 46 +------------------ .../kit/src/runtime/server/page/render.js | 4 +- .../runtime/server/page/respond_with_error.js | 1 - .../src/routes/load/devalue/+page.svelte | 1 + .../routes/load/devalue/regex/+page.server.js | 6 +++ .../routes/load/devalue/regex/+page.svelte | 6 +++ packages/kit/test/apps/basics/test/test.js | 7 +++ ...vite.config.js.timestamp-1661481874774.mjs | 23 ++++++++++ 13 files changed, 60 insertions(+), 68 deletions(-) create mode 100644 packages/kit/test/apps/basics/src/routes/load/devalue/+page.svelte create mode 100644 packages/kit/test/apps/basics/src/routes/load/devalue/regex/+page.server.js create mode 100644 packages/kit/test/apps/basics/src/routes/load/devalue/regex/+page.svelte create mode 100644 packages/kit/test/apps/basics/vite.config.js.timestamp-1661481874774.mjs diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 0fa99bd6fecd..15260442d496 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1281,31 +1281,21 @@ export function create_client({ target, base, trailing_slash }) { }); }, - _hydrate: async ({ status, error, node_ids, params, routeId }) => { + _hydrate: async ({ + status, + error, + node_ids, + params, + routeId, + data: server_data_nodes, + errors: validation_errors + }) => { const url = new URL(location.href); /** @type {import('./types').NavigationFinished | undefined} */ let result; try { - /** - * @param {string} type - * @param {any} fallback - */ - const parse = (type, fallback) => { - const script = document.querySelector(`script[sveltekit\\:data-type="${type}"]`); - return script?.textContent ? JSON.parse(script.textContent) : fallback; - }; - /** - * @type {Array} - * On initial navigation, this will only consist of data nodes or `null`. - * A possible error is passed through the `error` property, in which case - * the last entry of `node_ids` is an error page and the last entry of - * `server_data_nodes` is `null`. - */ - const server_data_nodes = parse('server_data', []); - const validation_errors = parse('validation_errors', undefined); - const branch_promises = node_ids.map(async (n, i) => { return load_node({ loader: nodes[n], diff --git a/packages/kit/src/runtime/client/start.js b/packages/kit/src/runtime/client/start.js index 2c679c8a6cc2..78c245841616 100644 --- a/packages/kit/src/runtime/client/start.js +++ b/packages/kit/src/runtime/client/start.js @@ -20,6 +20,8 @@ export { set_public_env } from '../env-public.js'; * node_ids: number[]; * params: Record; * routeId: string | null; + * data: Array; + * errors: Record | null; * }; * }} opts */ diff --git a/packages/kit/src/runtime/client/types.d.ts b/packages/kit/src/runtime/client/types.d.ts index 1b32c231ca28..bc470bf60eee 100644 --- a/packages/kit/src/runtime/client/types.d.ts +++ b/packages/kit/src/runtime/client/types.d.ts @@ -27,6 +27,8 @@ export interface Client { node_ids: number[]; params: Record; routeId: string | null; + data: Array; + errors: Record; }) => Promise; _start_router: () => void; } diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index ee8fe42a27fb..f1f2b96cce20 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -272,7 +272,6 @@ export async function respond(request, options, state) { // == because it could be undefined (in dev) or null (in build, because of JSON.stringify) const node = n == undefined ? n : await options.manifest._.nodes[n](); return load_server_data({ - dev: options.dev, event, state, node, diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index 99911ae83897..838fe9342aba 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -166,7 +166,6 @@ export async function render_page(event, route, page, options, state, resolve_op } return await load_server_data({ - dev: options.dev, event, state, node, diff --git a/packages/kit/src/runtime/server/page/load_data.js b/packages/kit/src/runtime/server/page/load_data.js index 237808de5126..21a753e50b0d 100644 --- a/packages/kit/src/runtime/server/page/load_data.js +++ b/packages/kit/src/runtime/server/page/load_data.js @@ -3,7 +3,6 @@ import { disable_search, make_trackable } from '../../../utils/url.js'; /** * Calls the user's `load` function. * @param {{ - * dev: boolean; * event: import('types').RequestEvent; * state: import('types').SSRState; * node: import('types').SSRNode | undefined; @@ -11,7 +10,7 @@ import { disable_search, make_trackable } from '../../../utils/url.js'; * }} opts * @returns {Promise} */ -export async function load_server_data({ dev, event, state, node, parent }) { +export async function load_server_data({ event, state, node, parent }) { if (!node?.server) return null; const uses = { @@ -53,10 +52,6 @@ export async function load_server_data({ dev, event, state, node, parent }) { const data = result ? await unwrap_promises(result) : null; - if (dev) { - check_serializability(data, /** @type {string} */ (node.server_id), 'data'); - } - return { type: 'data', data, @@ -127,42 +122,3 @@ async function unwrap_promises(object) { return unwrapped; } - -/** - * Check that the data can safely be serialized to JSON - * @param {any} value - * @param {string} id - * @param {string} path - */ -function check_serializability(value, id, path) { - const type = typeof value; - - if (type === 'string' || type === 'boolean' || type === 'number' || type === 'undefined') { - // primitives are fine - return; - } - - if (type === 'object') { - // nulls are fine... - if (!value) return; - - // ...so are plain arrays... - if (Array.isArray(value)) { - value.forEach((child, i) => { - check_serializability(child, id, `${path}[${i}]`); - }); - return; - } - - // ...and objects - const tag = Object.prototype.toString.call(value); - if (tag === '[object Object]') { - for (const key in value) { - check_serializability(value[key], id, `${path}.${key}`); - } - return; - } - } - - throw new Error(`${path} returned from 'load' in ${id} cannot be serialized as JSON`); -} diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index d97f0f54c25b..a42e79c6fb80 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -191,7 +191,9 @@ export async function render_response({ error: ${error && serialize_error(error, e => e.stack)}, node_ids: [${branch.map(({ node }) => node.index).join(', ')}], params: ${devalue(event.params)}, - routeId: ${s(event.routeId)} + routeId: ${s(event.routeId)}, + data: ${devalue(branch.map(({ server_data }) => server_data))}, + errors: ${validation_errors ? devalue(validation_errors) : 'null'} }` : 'null'} }); `; diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js index 001fdd5a8cea..b0f87a5f5859 100644 --- a/packages/kit/src/runtime/server/page/respond_with_error.js +++ b/packages/kit/src/runtime/server/page/respond_with_error.js @@ -35,7 +35,6 @@ export async function respond_with_error({ event, options, state, status, error, const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout const server_data_promise = load_server_data({ - dev: options.dev, event, state, node: default_layout, diff --git a/packages/kit/test/apps/basics/src/routes/load/devalue/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/devalue/+page.svelte new file mode 100644 index 000000000000..d339ec6ff7d2 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/load/devalue/+page.svelte @@ -0,0 +1 @@ +/load/devalue/regex diff --git a/packages/kit/test/apps/basics/src/routes/load/devalue/regex/+page.server.js b/packages/kit/test/apps/basics/src/routes/load/devalue/regex/+page.server.js new file mode 100644 index 000000000000..a31da9c7128e --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/load/devalue/regex/+page.server.js @@ -0,0 +1,6 @@ +/** @type {import('./$types').PageServerLoad} */ +export function load() { + return { + regex: /hello/ + }; +} diff --git a/packages/kit/test/apps/basics/src/routes/load/devalue/regex/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/devalue/regex/+page.svelte new file mode 100644 index 000000000000..04f11dbe10a3 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/load/devalue/regex/+page.svelte @@ -0,0 +1,6 @@ + + +

{data.regex.test('hello')}

diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index 1cc532aaefbf..497a409a3fec 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -892,6 +892,13 @@ test.describe('Load', () => { expect(await page.textContent('h1')).toBe('foo.bar: Custom layout'); expect(await page.textContent('h2')).toBe('pagedata: pagedata'); }); + + test.only('Serializes non-JSON data', async ({ page, clicknav }) => { + await page.goto('/load/devalue'); + await clicknav('[href="/load/devalue/regex"]'); + + expect(await page.textContent('h1')).toBe(true); + }); }); test.describe('Method overrides', () => { diff --git a/packages/kit/test/apps/basics/vite.config.js.timestamp-1661481874774.mjs b/packages/kit/test/apps/basics/vite.config.js.timestamp-1661481874774.mjs new file mode 100644 index 000000000000..d0793c197a93 --- /dev/null +++ b/packages/kit/test/apps/basics/vite.config.js.timestamp-1661481874774.mjs @@ -0,0 +1,23 @@ +// vite.config.js +import * as path from "path"; +import { sveltekit } from "@sveltejs/kit/vite"; +var config = { + build: { + minify: false + }, + clearScreen: false, + optimizeDeps: { + include: ["cookie", "marked"] + }, + plugins: [sveltekit()], + server: { + fs: { + allow: [path.resolve("../../../src")] + } + } +}; +var vite_config_default = config; +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcuanMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvVXNlcnMvcmljaC9EZXZlbG9wbWVudC9TVkVMVEUvS0lUL2tpdC9wYWNrYWdlcy9raXQvdGVzdC9hcHBzL2Jhc2ljc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL1VzZXJzL3JpY2gvRGV2ZWxvcG1lbnQvU1ZFTFRFL0tJVC9raXQvcGFja2FnZXMva2l0L3Rlc3QvYXBwcy9iYXNpY3Mvdml0ZS5jb25maWcuanNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL1VzZXJzL3JpY2gvRGV2ZWxvcG1lbnQvU1ZFTFRFL0tJVC9raXQvcGFja2FnZXMva2l0L3Rlc3QvYXBwcy9iYXNpY3Mvdml0ZS5jb25maWcuanNcIjtpbXBvcnQgKiBhcyBwYXRoIGZyb20gJ3BhdGgnO1xuaW1wb3J0IHsgc3ZlbHRla2l0IH0gZnJvbSAnQHN2ZWx0ZWpzL2tpdC92aXRlJztcblxuLyoqIEB0eXBlIHtpbXBvcnQoJ3ZpdGUnKS5Vc2VyQ29uZmlnfSAqL1xuY29uc3QgY29uZmlnID0ge1xuXHRidWlsZDoge1xuXHRcdG1pbmlmeTogZmFsc2Vcblx0fSxcblx0Y2xlYXJTY3JlZW46IGZhbHNlLFxuXHRvcHRpbWl6ZURlcHM6IHtcblx0XHQvLyBmb3IgQ0ksIHdlIG5lZWQgdG8gZXhwbGljaXRseSBwcmVidW5kbGUgZGVwcywgc2luY2Vcblx0XHQvLyB0aGUgcmVsb2FkIGNvbmZ1c2VzIFBsYXl3cmlnaHRcblx0XHRpbmNsdWRlOiBbJ2Nvb2tpZScsICdtYXJrZWQnXVxuXHR9LFxuXHRwbHVnaW5zOiBbc3ZlbHRla2l0KCldLFxuXHRzZXJ2ZXI6IHtcblx0XHRmczoge1xuXHRcdFx0YWxsb3c6IFtwYXRoLnJlc29sdmUoJy4uLy4uLy4uL3NyYycpXVxuXHRcdH1cblx0fVxufTtcblxuZXhwb3J0IGRlZmF1bHQgY29uZmlnO1xuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUE4WCxZQUFZLFVBQVU7QUFDcFosU0FBUyxpQkFBaUI7QUFHMUIsSUFBTSxTQUFTO0FBQUEsRUFDZCxPQUFPO0FBQUEsSUFDTixRQUFRO0FBQUEsRUFDVDtBQUFBLEVBQ0EsYUFBYTtBQUFBLEVBQ2IsY0FBYztBQUFBLElBR2IsU0FBUyxDQUFDLFVBQVUsUUFBUTtBQUFBLEVBQzdCO0FBQUEsRUFDQSxTQUFTLENBQUMsVUFBVSxDQUFDO0FBQUEsRUFDckIsUUFBUTtBQUFBLElBQ1AsSUFBSTtBQUFBLE1BQ0gsT0FBTyxDQUFNLGFBQVEsY0FBYyxDQUFDO0FBQUEsSUFDckM7QUFBQSxFQUNEO0FBQ0Q7QUFFQSxJQUFPLHNCQUFROyIsCiAgIm5hbWVzIjogW10KfQo= From 24b1984b9d2f3ceafa1917facca9f6845fb48619 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 08:22:52 -0400 Subject: [PATCH 02/23] generate __data.js --- packages/kit/src/runtime/server/data/index.js | 132 ++++++++++++++ packages/kit/src/runtime/server/index.js | 161 +++--------------- packages/kit/src/runtime/server/page/index.js | 2 +- packages/kit/src/runtime/server/utils.js | 6 + 4 files changed, 159 insertions(+), 142 deletions(-) create mode 100644 packages/kit/src/runtime/server/data/index.js diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js new file mode 100644 index 000000000000..d13611c0a2c1 --- /dev/null +++ b/packages/kit/src/runtime/server/data/index.js @@ -0,0 +1,132 @@ +import { HttpError, Redirect } from '../../../index/private.js'; +import { normalize_error } from '../../../utils/error.js'; +import { once } from '../../../utils/functions.js'; +import { load_server_data } from '../page/load_data.js'; +import { error_to_pojo } from '../utils.js'; +import devalue from 'devalue'; + +/** + * @param {import('types').RequestEvent} event + * @param {import('types').SSRRoute} route + * @param {import('types').SSROptions} options + * @param {import('types').SSRState} state + * @returns {Promise} + */ +export async function render_data(event, route, options, state) { + if (!route.page) { + // requesting /__data.js should fail for a +server.js + return new Response(undefined, { + status: 404 + }); + } + + try { + const node_ids = [...route.page.layouts, route.page.leaf]; + + const invalidated = + event.request.headers.get('x-sveltekit-invalidated')?.split(',').map(Boolean) ?? + node_ids.map(() => true); + + let aborted = false; + + const functions = node_ids.map((n, i) => { + return once(async () => { + try { + if (aborted) { + return /** @type {import('types').ServerDataSkippedNode} */ ({ + type: 'skip' + }); + } + + // == because it could be undefined (in dev) or null (in build, because of JSON.stringify) + const node = n == undefined ? n : await options.manifest._.nodes[n](); + return load_server_data({ + event, + state, + node, + parent: async () => { + /** @type {Record} */ + const data = {}; + for (let j = 0; j < i; j += 1) { + const parent = /** @type {import('types').ServerDataNode | null} */ ( + await functions[j]() + ); + + if (parent) { + Object.assign(data, parent.data); + } + } + return data; + } + }); + } catch (e) { + aborted = true; + throw e; + } + }); + }); + + const promises = functions.map(async (fn, i) => { + if (!invalidated[i]) { + return /** @type {import('types').ServerDataSkippedNode} */ ({ + type: 'skip' + }); + } + + return fn(); + }); + + let length = promises.length; + const nodes = await Promise.all( + promises.map((p, i) => + p.catch((e) => { + const error = normalize_error(e); + + if (error instanceof Redirect) { + throw error; + } + + // Math.min because array isn't guaranteed to resolve in order + length = Math.min(length, i + 1); + + if (error instanceof HttpError) { + return /** @type {import('types').ServerErrorNode} */ ({ + type: 'error', + httperror: { ...error } + }); + } + + options.handle_error(error, event); + + return /** @type {import('types').ServerErrorNode} */ ({ + type: 'error', + error: error_to_pojo(error, options.get_stack) + }); + }) + ) + ); + + /** @type {import('types').ServerData} */ + const server_data = { + type: 'data', + nodes: nodes.slice(0, length) + }; + + return new Response(`window.__data = ${devalue(server_data)}`); + } catch (e) { + const error = normalize_error(e); + + if (error instanceof Redirect) { + /** @type {import('types').ServerData} */ + const server_data = { + type: 'redirect', + location: error.location + }; + + return new Response(`window.__data = ${devalue(server_data)}`); + } else { + // TODO make it clearer that this was an unexpected error + return new Response(`window.__data = ${devalue(error_to_pojo(error, options.get_stack))}`); + } + } +} diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index f1f2b96cce20..a333587a0a0f 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -2,19 +2,16 @@ import { render_endpoint } from './endpoint.js'; import { render_page } from './page/index.js'; import { render_response } from './page/render.js'; import { respond_with_error } from './page/respond_with_error.js'; -import { coalesce_to_error, normalize_error } from '../../utils/error.js'; -import { serialize_error, GENERIC_ERROR, error_to_pojo } from './utils.js'; +import { coalesce_to_error } from '../../utils/error.js'; +import { serialize_error, GENERIC_ERROR } from './utils.js'; import { decode_params, disable_search, normalize_path } from '../../utils/url.js'; import { exec } from '../../utils/routing.js'; import { negotiate } from '../../utils/http.js'; -import { HttpError, Redirect } from '../../index/private.js'; -import { load_server_data } from './page/load_data.js'; -import { json } from '../../index/index.js'; -import { once } from '../../utils/functions.js'; +import { render_data } from './data/index.js'; /* global __SVELTEKIT_ADAPTER_NAME__ */ -const DATA_SUFFIX = '/__data.json'; +const DATA_SUFFIX = '/__data.js'; /** @param {{ html: string }} opts */ const default_transform = ({ html }) => html; @@ -69,12 +66,7 @@ export async function respond(request, options, state) { } const is_data_request = decoded.endsWith(DATA_SUFFIX); - - if (is_data_request) { - const data_suffix_length = DATA_SUFFIX.length - (options.trailing_slash === 'always' ? 1 : 0); - decoded = decoded.slice(0, -data_suffix_length) || '/'; - url = new URL(url.origin + url.pathname.slice(0, -data_suffix_length) + url.search); - } + if (is_data_request) decoded = decoded.slice(0, -DATA_SUFFIX.length); if (!state.prerendering?.fallback) { const matchers = await options.manifest._.matchers(); @@ -92,26 +84,19 @@ export async function respond(request, options, state) { } } - if (route) { - if (route.page) { - const normalized = normalize_path(url.pathname, options.trailing_slash); - - if (normalized !== url.pathname && !state.prerendering?.fallback) { - return new Response(undefined, { - status: 301, - headers: { - 'x-sveltekit-normalize': '1', - location: - // ensure paths starting with '//' are not treated as protocol-relative - (normalized.startsWith('//') ? url.origin + normalized : normalized) + - (url.search === '?' ? '' : url.search) - } - }); - } - } else if (is_data_request) { - // requesting /__data.json should fail for a standalone endpoint + if (route?.page) { + const normalized = normalize_path(url.pathname, options.trailing_slash); + + if (normalized !== url.pathname && !state.prerendering?.fallback) { return new Response(undefined, { - status: 404 + status: 301, + headers: { + 'x-sveltekit-normalize': '1', + location: + // ensure paths starting with '//' are not treated as protocol-relative + (normalized.startsWith('//') ? url.origin + normalized : normalized) + + (url.search === '?' ? '' : url.search) + } }); } } @@ -250,115 +235,9 @@ export async function respond(request, options, state) { if (route) { /** @type {Response} */ let response; - if (is_data_request && route.page) { - try { - const node_ids = [...route.page.layouts, route.page.leaf]; - - const invalidated = - request.headers.get('x-sveltekit-invalidated')?.split(',').map(Boolean) ?? - node_ids.map(() => true); - - let aborted = false; - - const functions = node_ids.map((n, i) => { - return once(async () => { - try { - if (aborted) { - return /** @type {import('types').ServerDataSkippedNode} */ ({ - type: 'skip' - }); - } - - // == because it could be undefined (in dev) or null (in build, because of JSON.stringify) - const node = n == undefined ? n : await options.manifest._.nodes[n](); - return load_server_data({ - event, - state, - node, - parent: async () => { - /** @type {Record} */ - const data = {}; - for (let j = 0; j < i; j += 1) { - const parent = /** @type {import('types').ServerDataNode | null} */ ( - await functions[j]() - ); - - if (parent) { - Object.assign(data, parent.data); - } - } - return data; - } - }); - } catch (e) { - aborted = true; - throw e; - } - }); - }); - - const promises = functions.map(async (fn, i) => { - if (!invalidated[i]) { - return /** @type {import('types').ServerDataSkippedNode} */ ({ - type: 'skip' - }); - } - - return fn(); - }); - let length = promises.length; - const nodes = await Promise.all( - promises.map((p, i) => - p.catch((e) => { - const error = normalize_error(e); - - if (error instanceof Redirect) { - throw error; - } - - // Math.min because array isn't guaranteed to resolve in order - length = Math.min(length, i + 1); - - if (error instanceof HttpError) { - return /** @type {import('types').ServerErrorNode} */ ({ - type: 'error', - httperror: { ...error } - }); - } - - options.handle_error(error, event); - - return /** @type {import('types').ServerErrorNode} */ ({ - type: 'error', - error: error_to_pojo(error, options.get_stack) - }); - }) - ) - ); - - /** @type {import('types').ServerData} */ - const server_data = { - type: 'data', - nodes: nodes.slice(0, length) - }; - - response = json(server_data); - } catch (e) { - const error = normalize_error(e); - - if (error instanceof Redirect) { - /** @type {import('types').ServerData} */ - const server_data = { - type: 'redirect', - location: error.location - }; - - response = json(server_data); - } else { - response = json(error_to_pojo(error, options.get_stack), { status: 500 }); - } - } + if (is_data_request) { + response = await render_data(event, route, options, state); } else if (route.page) { response = await render_page(event, route, route.page, options, state, resolve_opts); } else if (route.endpoint) { @@ -370,7 +249,7 @@ export async function respond(request, options, state) { } if (!is_data_request) { - // we only want to set cookies on __data.json requests, we don't + // we only want to set cookies on __data.js requests, we don't // want to cache stuff erroneously etc for (const key in headers) { const value = headers[key]; diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index 838fe9342aba..e695f629f174 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -102,7 +102,7 @@ export async function render_page(event, route, page, options, state, resolve_op } const should_prerender_data = nodes.some((node) => node?.server); - const data_pathname = `${event.url.pathname.replace(/\/$/, '')}/__data.json`; + const data_pathname = `${event.url.pathname.replace(/\/$/, '')}/__data.js`; // it's crucial that we do this before returning the non-SSR response, otherwise // SvelteKit will erroneously believe that the path has been prerendered, diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 27fa3abebff5..1edb43f134b3 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -1,3 +1,4 @@ +import devalue from 'devalue'; import { HttpError } from '../../index/private.js'; /** @param {any} body */ @@ -114,3 +115,8 @@ export function allowed_methods(mod) { return allowed; } + +/** @param {any} data */ +export function data_response(data) { + return new Response(`window.__data = ${devalue(data)}`); +} From fe30204ffa9d6ce8f8cb61f5e398df36795f67bd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 10:40:09 -0400 Subject: [PATCH 03/23] wire things up --- packages/kit/src/runtime/client/client.js | 22 +++++++++---------- packages/kit/src/runtime/server/data/index.js | 16 ++++++++------ packages/kit/src/runtime/server/utils.js | 6 ++++- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 8dd494da3905..e290a1489c50 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -700,20 +700,20 @@ export function create_client({ target, base, trailing_slash }) { if (route.uses_server_data && invalid_server_nodes.some(Boolean)) { try { - const res = await native_fetch( - `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`, - { - headers: { - 'x-sveltekit-invalidated': invalid_server_nodes.map((x) => (x ? '1' : '')).join(',') - } - } + const data_url = new URL(url); + data_url.pathname += `${url.pathname.endsWith('/') ? '' : '/'}__data.js`; + data_url.searchParams.set( + '__invalid', + invalid_server_nodes.map((x) => (x ? 'y' : 'n')).join('') ); - server_data = /** @type {import('types').ServerData} */ (await res.json()); + await import(/* @vite-ignore */ data_url.href); - if (!res.ok) { - throw server_data; - } + // @ts-expect-error + server_data = /** @type {import('types').ServerData} */ (window.__sveltekit_data); + + // @ts-expect-error + delete window.__sveltekit_data; } catch (e) { // something went catastrophically wrong — bail and defer to the server native_navigation(url); diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js index d13611c0a2c1..72a2e1d340b4 100644 --- a/packages/kit/src/runtime/server/data/index.js +++ b/packages/kit/src/runtime/server/data/index.js @@ -1,8 +1,8 @@ -import { HttpError, Redirect } from '../../../index/private.js'; +import { HttpError, Redirect } from '../../control.js'; import { normalize_error } from '../../../utils/error.js'; import { once } from '../../../utils/functions.js'; import { load_server_data } from '../page/load_data.js'; -import { error_to_pojo } from '../utils.js'; +import { data_response, error_to_pojo } from '../utils.js'; import devalue from 'devalue'; /** @@ -24,8 +24,10 @@ export async function render_data(event, route, options, state) { const node_ids = [...route.page.layouts, route.page.leaf]; const invalidated = - event.request.headers.get('x-sveltekit-invalidated')?.split(',').map(Boolean) ?? - node_ids.map(() => true); + event.url.searchParams + .get('__invalid') + ?.split('') + .map((x) => x === 'y') ?? node_ids.map(() => true); let aborted = false; @@ -112,7 +114,7 @@ export async function render_data(event, route, options, state) { nodes: nodes.slice(0, length) }; - return new Response(`window.__data = ${devalue(server_data)}`); + return data_response(server_data); } catch (e) { const error = normalize_error(e); @@ -123,10 +125,10 @@ export async function render_data(event, route, options, state) { location: error.location }; - return new Response(`window.__data = ${devalue(server_data)}`); + return data_response(server_data); } else { // TODO make it clearer that this was an unexpected error - return new Response(`window.__data = ${devalue(error_to_pojo(error, options.get_stack))}`); + return data_response(error_to_pojo(error, options.get_stack)); } } } diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index dd9947182928..2416b38d9583 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -118,5 +118,9 @@ export function allowed_methods(mod) { /** @param {any} data */ export function data_response(data) { - return new Response(`window.__data = ${devalue(data)}`); + return new Response(`window.__sveltekit_data = ${devalue(data)}`, { + headers: { + 'content-type': 'application/javascript' + } + }); } From fb0621f3ae72b871e6d5d5483a1a38e5bb92badc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 11:08:23 -0400 Subject: [PATCH 04/23] fix --- packages/kit/src/{core => }/constants.js | 2 + packages/kit/src/core/env.js | 2 +- packages/kit/src/core/sync/write_ambient.js | 2 +- packages/kit/src/exports/vite/dev/index.js | 2 +- .../kit/src/exports/vite/preview/index.js | 2 +- packages/kit/src/runtime/client/client.js | 57 +++++++++++-------- packages/kit/src/runtime/server/data/index.js | 14 ++++- packages/kit/src/runtime/server/index.js | 3 +- packages/kit/test/apps/basics/test/test.js | 4 +- 9 files changed, 54 insertions(+), 34 deletions(-) rename packages/kit/src/{core => }/constants.js (87%) diff --git a/packages/kit/src/core/constants.js b/packages/kit/src/constants.js similarity index 87% rename from packages/kit/src/core/constants.js rename to packages/kit/src/constants.js index 8dc9aab27904..7d50f152432c 100644 --- a/packages/kit/src/core/constants.js +++ b/packages/kit/src/constants.js @@ -3,3 +3,5 @@ export const SVELTE_KIT_ASSETS = '/_svelte_kit_assets'; export const GENERATED_COMMENT = '// this file is generated — do not edit it\n'; + +export const DATA_SUFFIX = '/__data.js'; diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index 2d2006098283..789348a62cd7 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -1,4 +1,4 @@ -import { GENERATED_COMMENT } from './constants.js'; +import { GENERATED_COMMENT } from '../constants.js'; import { runtime_base } from './utils.js'; /** diff --git a/packages/kit/src/core/sync/write_ambient.js b/packages/kit/src/core/sync/write_ambient.js index 9672488bb2f2..9904a1cee043 100644 --- a/packages/kit/src/core/sync/write_ambient.js +++ b/packages/kit/src/core/sync/write_ambient.js @@ -1,6 +1,6 @@ import path from 'path'; import { get_env } from '../../exports/vite/utils.js'; -import { GENERATED_COMMENT } from '../constants.js'; +import { GENERATED_COMMENT } from '../../constants.js'; import { create_types } from '../env.js'; import { write_if_changed } from './utils.js'; diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 77cb0bfb9e86..a74cdddd20ed 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -8,7 +8,7 @@ import { installPolyfills } from '../../../exports/node/polyfills.js'; import { coalesce_to_error } from '../../../utils/error.js'; import { posixify } from '../../../utils/filesystem.js'; import { load_template } from '../../../core/config/index.js'; -import { SVELTE_KIT_ASSETS } from '../../../core/constants.js'; +import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import * as sync from '../../../core/sync/sync.js'; import { get_mime_lookup, runtime_base, runtime_prefix } from '../../../core/utils.js'; import { get_env, prevent_illegal_vite_imports, resolve_entry } from '../utils.js'; diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 54539d3e03c5..9f1a80298e77 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -4,7 +4,7 @@ import sirv from 'sirv'; import { pathToFileURL } from 'url'; import { getRequest, setResponse } from '../../../exports/node/index.js'; import { installPolyfills } from '../../../exports/node/polyfills.js'; -import { SVELTE_KIT_ASSETS } from '../../../core/constants.js'; +import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import { loadEnv } from 'vite'; /** @typedef {import('http').IncomingMessage} Req */ diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index e290a1489c50..3f4153e0e41c 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -700,20 +700,7 @@ export function create_client({ target, base, trailing_slash }) { if (route.uses_server_data && invalid_server_nodes.some(Boolean)) { try { - const data_url = new URL(url); - data_url.pathname += `${url.pathname.endsWith('/') ? '' : '/'}__data.js`; - data_url.searchParams.set( - '__invalid', - invalid_server_nodes.map((x) => (x ? 'y' : 'n')).join('') - ); - - await import(/* @vite-ignore */ data_url.href); - - // @ts-expect-error - server_data = /** @type {import('types').ServerData} */ (window.__sveltekit_data); - - // @ts-expect-error - delete window.__sveltekit_data; + server_data = await load_data(url, invalid_server_nodes); } catch (e) { // something went catastrophically wrong — bail and defer to the server native_navigation(url); @@ -868,19 +855,18 @@ export function create_client({ target, base, trailing_slash }) { if (node.server) { // TODO post-https://github.com/sveltejs/kit/discussions/6124 we can use // existing root layout data - const res = await native_fetch( - `${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`, - { - headers: { - 'x-sveltekit-invalidated': '1' - } - } - ); + try { + const server_data = await load_data(url, [true]); - const server_data_nodes = await res.json(); - server_data_node = server_data_nodes?.[0] ?? null; + if ( + server_data.type !== 'data' || + (server_data.nodes[0] && server_data.nodes[0].type !== 'data') + ) { + throw 0; + } - if (!res.ok || server_data_nodes?.type !== 'data') { + server_data_node = server_data.nodes[0] ?? null; + } catch { // at this point we have no choice but to fall back to the server native_navigation(url); @@ -1353,3 +1339,24 @@ export function create_client({ target, base, trailing_slash }) { } }; } + +/** + * @param {URL} url + * @param {boolean[]} invalid + * @returns {Promise} + */ +async function load_data(url, invalid) { + const data_url = new URL(url); + data_url.pathname += `${url.pathname.endsWith('/') ? '' : '/'}__data.js`; + data_url.searchParams.set('__invalid', invalid.map((x) => (x ? 'y' : 'n')).join('')); + + await import(/* @vite-ignore */ data_url.href); + + // @ts-expect-error + const server_data = window.__sveltekit_data; + + // @ts-expect-error + delete window.__sveltekit_data; + + return server_data; +} diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js index 72a2e1d340b4..9b09e49785b3 100644 --- a/packages/kit/src/runtime/server/data/index.js +++ b/packages/kit/src/runtime/server/data/index.js @@ -4,6 +4,8 @@ import { once } from '../../../utils/functions.js'; import { load_server_data } from '../page/load_data.js'; import { data_response, error_to_pojo } from '../utils.js'; import devalue from 'devalue'; +import { normalize_path } from '../../../utils/url.js'; +import { DATA_SUFFIX } from '../../../constants.js'; /** * @param {import('types').RequestEvent} event @@ -31,6 +33,16 @@ export async function render_data(event, route, options, state) { let aborted = false; + const url = new URL(event.url); + url.pathname = normalize_path( + url.pathname.slice(0, -DATA_SUFFIX.length), + options.trailing_slash + ); + + url.searchParams.delete('__invalid'); + + const new_event = { ...event, url }; + const functions = node_ids.map((n, i) => { return once(async () => { try { @@ -43,7 +55,7 @@ export async function render_data(event, route, options, state) { // == because it could be undefined (in dev) or null (in build, because of JSON.stringify) const node = n == undefined ? n : await options.manifest._.nodes[n](); return load_server_data({ - event, + event: new_event, state, node, parent: async () => { diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index a333587a0a0f..c87cf264a73e 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -8,11 +8,10 @@ import { decode_params, disable_search, normalize_path } from '../../utils/url.j import { exec } from '../../utils/routing.js'; import { negotiate } from '../../utils/http.js'; import { render_data } from './data/index.js'; +import { DATA_SUFFIX } from '../../constants.js'; /* global __SVELTEKIT_ADAPTER_NAME__ */ -const DATA_SUFFIX = '/__data.js'; - /** @param {{ html: string }} opts */ const default_transform = ({ html }) => html; diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index 497a409a3fec..a10a1ad9545c 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -893,11 +893,11 @@ test.describe('Load', () => { expect(await page.textContent('h2')).toBe('pagedata: pagedata'); }); - test.only('Serializes non-JSON data', async ({ page, clicknav }) => { + test('Serializes non-JSON data', async ({ page, clicknav }) => { await page.goto('/load/devalue'); await clicknav('[href="/load/devalue/regex"]'); - expect(await page.textContent('h1')).toBe(true); + expect(await page.textContent('h1')).toBe('true'); }); }); From 35de03c80d0633508c087a7dca0e5e34439a6a42 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 13:18:22 -0400 Subject: [PATCH 05/23] update devalue --- packages/kit/package.json | 2 +- packages/kit/src/runtime/server/data/index.js | 1 - packages/kit/src/runtime/server/page/render.js | 2 +- packages/kit/src/runtime/server/utils.js | 2 +- pnpm-lock.yaml | 8 ++++---- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/kit/package.json b/packages/kit/package.json index ec73fb650ddb..4a1b1fd3c6e5 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -12,7 +12,7 @@ "dependencies": { "@sveltejs/vite-plugin-svelte": "^1.0.1", "cookie": "^0.5.0", - "devalue": "^2.0.1", + "devalue": "^3.0.1", "kleur": "^4.1.4", "magic-string": "^0.26.2", "mime": "^3.0.0", diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js index 9b09e49785b3..32cb9baed05e 100644 --- a/packages/kit/src/runtime/server/data/index.js +++ b/packages/kit/src/runtime/server/data/index.js @@ -3,7 +3,6 @@ import { normalize_error } from '../../../utils/error.js'; import { once } from '../../../utils/functions.js'; import { load_server_data } from '../page/load_data.js'; import { data_response, error_to_pojo } from '../utils.js'; -import devalue from 'devalue'; import { normalize_path } from '../../../utils/url.js'; import { DATA_SUFFIX } from '../../../constants.js'; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index b801ee0509d7..3499b1a91561 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -1,4 +1,4 @@ -import devalue from 'devalue'; +import { devalue } from 'devalue'; import { readable, writable } from 'svelte/store'; import * as cookie from 'cookie'; import { hash } from '../../hash.js'; diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 2416b38d9583..4e18a8ba68e0 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -1,4 +1,4 @@ -import devalue from 'devalue'; +import { devalue } from 'devalue'; import { HttpError } from '../control.js'; /** @param {any} body */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0967ad4444da..e97070a7194c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -278,7 +278,7 @@ importers: '@types/sade': ^1.7.4 '@types/set-cookie-parser': ^2.4.2 cookie: ^0.5.0 - devalue: ^2.0.1 + devalue: ^3.0.1 kleur: ^4.1.4 magic-string: ^0.26.2 marked: ^4.0.16 @@ -298,7 +298,7 @@ importers: dependencies: '@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.48.0+vite@3.0.9 cookie: 0.5.0 - devalue: 2.0.1 + devalue: 3.0.1 kleur: 4.1.5 magic-string: 0.26.2 mime: 3.0.0 @@ -1748,8 +1748,8 @@ packages: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} - /devalue/2.0.1: - resolution: {integrity: sha512-I2TiqT5iWBEyB8GRfTDP0hiLZ0YeDJZ+upDxjBfOC2lebO5LezQMv7QvIUTzdb64jQyAKLf1AHADtGN+jw6v8Q==} + /devalue/3.0.1: + resolution: {integrity: sha512-c6cNErg/5JNdvh/EcgjQZ3h4Pyy5MXy/GMMv/dENJ1l5zwr+e3yKGZo5NmtpsDCcF5QW0u1ifxMnbH2GiEAIwg==} dev: false /diff/5.1.0: From 3e8ec03e1e9c32a5aa102a0e9ac654f9eb058ebc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 13:23:28 -0400 Subject: [PATCH 06/23] cachebust --- packages/kit/src/runtime/client/client.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 3f4153e0e41c..93d9142793b1 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1340,6 +1340,8 @@ export function create_client({ target, base, trailing_slash }) { }; } +let data_id = 1; + /** * @param {URL} url * @param {boolean[]} invalid @@ -1349,6 +1351,7 @@ async function load_data(url, invalid) { const data_url = new URL(url); data_url.pathname += `${url.pathname.endsWith('/') ? '' : '/'}__data.js`; data_url.searchParams.set('__invalid', invalid.map((x) => (x ? 'y' : 'n')).join('')); + data_url.searchParams.set('__id', String(data_id++)); await import(/* @vite-ignore */ data_url.href); From e30390024b30e4329bef201762528aaefbd01d4c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 14:17:33 -0400 Subject: [PATCH 07/23] get it working --- packages/kit/package.json | 2 +- packages/kit/src/runtime/server/data/index.js | 1 + .../kit/src/runtime/server/page/render.js | 23 +++++++++++++++++++ packages/kit/src/runtime/server/utils.js | 18 +++++++++++---- .../shadowed/serialization/+page.server.js | 8 ++++++- .../shadowed/serialization/+page.svelte | 2 +- packages/kit/test/apps/basics/test/test.js | 4 ++-- pnpm-lock.yaml | 8 +++---- 8 files changed, 52 insertions(+), 14 deletions(-) diff --git a/packages/kit/package.json b/packages/kit/package.json index 4a1b1fd3c6e5..715a294a8044 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -12,7 +12,7 @@ "dependencies": { "@sveltejs/vite-plugin-svelte": "^1.0.1", "cookie": "^0.5.0", - "devalue": "^3.0.1", + "devalue": "^3.1.0", "kleur": "^4.1.4", "magic-string": "^0.26.2", "mime": "^3.0.0", diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js index 32cb9baed05e..25296344fca5 100644 --- a/packages/kit/src/runtime/server/data/index.js +++ b/packages/kit/src/runtime/server/data/index.js @@ -39,6 +39,7 @@ export async function render_data(event, route, options, state) { ); url.searchParams.delete('__invalid'); + url.searchParams.delete('__id'); const new_event = { ...event, url }; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 3499b1a91561..a2574aeaf879 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -174,6 +174,29 @@ export async function render_response({ /** @param {string} path */ const prefixed = (path) => (path.startsWith('/') ? path : `${assets}/${path}`); + const serialized = { data: '', errors: 'null' }; + + try { + serialized.data = devalue(branch.map(({ server_data }) => server_data)); + } catch (e) { + // TODO if we wanted to get super fancy we could track down the origin of the `load` + // function, but it would mean passing more stuff around than we currently do + const error = /** @type {any} */ (e); + const match = /\[(\d+)\]\.data\.(.+)/.exec(error.path); + if (match) throw new Error(`${error.message} (data.${match[2]})`); + throw error; + } + + if (validation_errors) { + try { + serialized.errors = devalue(validation_errors); + } catch (e) { + const error = /** @type {any} */ (e); + if (error.path) throw new Error(`${error.message} (errors.${error.path})`); + throw error; + } + } + // prettier-ignore const init_app = ` import { set_public_env, start } from ${s(prefixed(entry.file))}; diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 4e18a8ba68e0..2c2b7a399ced 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -118,9 +118,17 @@ export function allowed_methods(mod) { /** @param {any} data */ export function data_response(data) { - return new Response(`window.__sveltekit_data = ${devalue(data)}`, { - headers: { - 'content-type': 'application/javascript' - } - }); + try { + return new Response(`window.__sveltekit_data = ${devalue(data)}`, { + headers: { + 'content-type': 'application/javascript' + } + }); + } catch (e) { + return new Response(`throw new Error(${JSON.stringify(e.message)})`, { + headers: { + 'content-type': 'application/javascript' + } + }); + } } diff --git a/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.server.js b/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.server.js index ceb16a88757e..47e76245eb40 100644 --- a/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.server.js +++ b/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.server.js @@ -1,5 +1,11 @@ +class Nope { + toString() { + return 'should not see me' + } +} + export function load() { return { - regex: /nope/ + nope: new Nope() }; } diff --git a/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.svelte b/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.svelte index a5a2d66e7841..f8a4718b5545 100644 --- a/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.svelte @@ -3,4 +3,4 @@ export let data; -

{data.regex.test('nope')}

+

{data.nope}

diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index a10a1ad9545c..c659740d2f98 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -291,7 +291,7 @@ test.describe('Shadowed pages', () => { expect(await page.textContent('h1')).toBe('500'); expect(await page.textContent('#message')).toBe( - 'This is your custom error page saying: "data.regex returned from \'load\' in src/routes/shadowed/serialization/+page.server.js cannot be serialized as JSON"' + 'This is your custom error page saying: "Cannot stringify arbitrary non-POJOs (data.nope)"' ); }); } @@ -579,7 +579,7 @@ test.describe('Errors', () => { expect(lines[1]).toContain('+page.server.js:4:8'); } - const error = read_errors('/errors/page-endpoint/get-implicit'); + const error = read_errors('/errors/page-endpoint/get-implicit/__data.js'); expect(error).toContain('oops'); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e97070a7194c..540d46a08b15 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -278,7 +278,7 @@ importers: '@types/sade': ^1.7.4 '@types/set-cookie-parser': ^2.4.2 cookie: ^0.5.0 - devalue: ^3.0.1 + devalue: ^3.1.0 kleur: ^4.1.4 magic-string: ^0.26.2 marked: ^4.0.16 @@ -298,7 +298,7 @@ importers: dependencies: '@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.48.0+vite@3.0.9 cookie: 0.5.0 - devalue: 3.0.1 + devalue: 3.1.0 kleur: 4.1.5 magic-string: 0.26.2 mime: 3.0.0 @@ -1748,8 +1748,8 @@ packages: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} - /devalue/3.0.1: - resolution: {integrity: sha512-c6cNErg/5JNdvh/EcgjQZ3h4Pyy5MXy/GMMv/dENJ1l5zwr+e3yKGZo5NmtpsDCcF5QW0u1ifxMnbH2GiEAIwg==} + /devalue/3.1.0: + resolution: {integrity: sha512-5Nb73BzNSGvDJNWCVXHv9R+rhNg0pN+OsgeJ/BRYCvMsEBCDfP9Ci/5cHjSPoOSMWBrh3MtUDQtChfdKYWLxeg==} dev: false /diff/5.1.0: From 401c02d0705f30adf0cfe9c119b96bf422aa256a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 14:20:57 -0400 Subject: [PATCH 08/23] huh --- ...vite.config.js.timestamp-1661481874774.mjs | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 packages/kit/test/apps/basics/vite.config.js.timestamp-1661481874774.mjs diff --git a/packages/kit/test/apps/basics/vite.config.js.timestamp-1661481874774.mjs b/packages/kit/test/apps/basics/vite.config.js.timestamp-1661481874774.mjs deleted file mode 100644 index d0793c197a93..000000000000 --- a/packages/kit/test/apps/basics/vite.config.js.timestamp-1661481874774.mjs +++ /dev/null @@ -1,23 +0,0 @@ -// vite.config.js -import * as path from "path"; -import { sveltekit } from "@sveltejs/kit/vite"; -var config = { - build: { - minify: false - }, - clearScreen: false, - optimizeDeps: { - include: ["cookie", "marked"] - }, - plugins: [sveltekit()], - server: { - fs: { - allow: [path.resolve("../../../src")] - } - } -}; -var vite_config_default = config; -export { - vite_config_default as default -}; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcuanMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvVXNlcnMvcmljaC9EZXZlbG9wbWVudC9TVkVMVEUvS0lUL2tpdC9wYWNrYWdlcy9raXQvdGVzdC9hcHBzL2Jhc2ljc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL1VzZXJzL3JpY2gvRGV2ZWxvcG1lbnQvU1ZFTFRFL0tJVC9raXQvcGFja2FnZXMva2l0L3Rlc3QvYXBwcy9iYXNpY3Mvdml0ZS5jb25maWcuanNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL1VzZXJzL3JpY2gvRGV2ZWxvcG1lbnQvU1ZFTFRFL0tJVC9raXQvcGFja2FnZXMva2l0L3Rlc3QvYXBwcy9iYXNpY3Mvdml0ZS5jb25maWcuanNcIjtpbXBvcnQgKiBhcyBwYXRoIGZyb20gJ3BhdGgnO1xuaW1wb3J0IHsgc3ZlbHRla2l0IH0gZnJvbSAnQHN2ZWx0ZWpzL2tpdC92aXRlJztcblxuLyoqIEB0eXBlIHtpbXBvcnQoJ3ZpdGUnKS5Vc2VyQ29uZmlnfSAqL1xuY29uc3QgY29uZmlnID0ge1xuXHRidWlsZDoge1xuXHRcdG1pbmlmeTogZmFsc2Vcblx0fSxcblx0Y2xlYXJTY3JlZW46IGZhbHNlLFxuXHRvcHRpbWl6ZURlcHM6IHtcblx0XHQvLyBmb3IgQ0ksIHdlIG5lZWQgdG8gZXhwbGljaXRseSBwcmVidW5kbGUgZGVwcywgc2luY2Vcblx0XHQvLyB0aGUgcmVsb2FkIGNvbmZ1c2VzIFBsYXl3cmlnaHRcblx0XHRpbmNsdWRlOiBbJ2Nvb2tpZScsICdtYXJrZWQnXVxuXHR9LFxuXHRwbHVnaW5zOiBbc3ZlbHRla2l0KCldLFxuXHRzZXJ2ZXI6IHtcblx0XHRmczoge1xuXHRcdFx0YWxsb3c6IFtwYXRoLnJlc29sdmUoJy4uLy4uLy4uL3NyYycpXVxuXHRcdH1cblx0fVxufTtcblxuZXhwb3J0IGRlZmF1bHQgY29uZmlnO1xuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUE4WCxZQUFZLFVBQVU7QUFDcFosU0FBUyxpQkFBaUI7QUFHMUIsSUFBTSxTQUFTO0FBQUEsRUFDZCxPQUFPO0FBQUEsSUFDTixRQUFRO0FBQUEsRUFDVDtBQUFBLEVBQ0EsYUFBYTtBQUFBLEVBQ2IsY0FBYztBQUFBLElBR2IsU0FBUyxDQUFDLFVBQVUsUUFBUTtBQUFBLEVBQzdCO0FBQUEsRUFDQSxTQUFTLENBQUMsVUFBVSxDQUFDO0FBQUEsRUFDckIsUUFBUTtBQUFBLElBQ1AsSUFBSTtBQUFBLE1BQ0gsT0FBTyxDQUFNLGFBQVEsY0FBYyxDQUFDO0FBQUEsSUFDckM7QUFBQSxFQUNEO0FBQ0Q7QUFFQSxJQUFPLHNCQUFROyIsCiAgIm5hbWVzIjogW10KfQo= From 31010dc136f11b3ed52f4a58415bc472cb31cf45 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 14:35:48 -0400 Subject: [PATCH 09/23] fixes --- packages/adapter-netlify/index.js | 2 +- packages/adapter-vercel/index.js | 2 +- packages/kit/src/runtime/server/page/index.js | 25 +++--- packages/kit/test/apps/options/test/test.js | 14 ++-- .../kit/test/prerendering/basics/test/test.js | 84 ++++++++++++------- .../prerendering/trailing-slash/test/test.js | 2 +- 6 files changed, 79 insertions(+), 50 deletions(-) diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js index 39bc32738954..4528b8a3c6a6 100644 --- a/packages/adapter-netlify/index.js +++ b/packages/adapter-netlify/index.js @@ -211,7 +211,7 @@ async function generate_lambda_functions({ builder, publish, split, esm }) { writeFileSync(`.netlify/functions-internal/${name}.js`, fn); redirects.push(`${pattern} /.netlify/functions/${name} 200`); - redirects.push(`${pattern}/__data.json /.netlify/functions/${name} 200`); + redirects.push(`${pattern}/__data.js /.netlify/functions/${name} 200`); } }; }); diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index 532d3534e20a..0d0ba443385c 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -224,7 +224,7 @@ export default function ({ external = [], edge, split } = {}) { sliced_pattern = '^/?'; } - const src = `${sliced_pattern}(?:/__data.json)?$`; // TODO adding /__data.json is a temporary workaround — those endpoints should be treated as distinct routes + const src = `${sliced_pattern}(?:/__data.js)?$`; // TODO adding /__data.js is a temporary workaround — those endpoints should be treated as distinct routes await generate_function(route.id || 'index', src, entry.generateManifest); } diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index 8c6d390afea4..e29b5eb6e8cc 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -1,3 +1,4 @@ +import { devalue } from 'devalue'; import { negotiate } from '../../../utils/http.js'; import { render_response } from './render.js'; import { respond_with_error } from './respond_with_error.js'; @@ -230,12 +231,14 @@ export async function render_page(event, route, page, options, state, resolve_op if (error instanceof Redirect) { if (state.prerendering && should_prerender_data) { + const body = `window.__sveltekit_data = ${JSON.stringify({ + type: 'redirect', + location: error.location + })}`; + state.prerendering.dependencies.set(data_pathname, { - response: new Response(undefined), - body: JSON.stringify({ - type: 'redirect', - location: error.location - }) + response: new Response(body), + body }); } @@ -293,12 +296,14 @@ export async function render_page(event, route, page, options, state, resolve_op } if (state.prerendering && should_prerender_data) { + const body = `window.__sveltekit_data = ${devalue({ + type: 'data', + nodes: branch.map((branch_node) => branch_node?.server_data) + })}`; + state.prerendering.dependencies.set(data_pathname, { - response: new Response(undefined), - body: JSON.stringify({ - type: 'data', - nodes: branch.map((branch_node) => branch_node?.server_data) - }) + response: new Response(body), + body }); } diff --git a/packages/kit/test/apps/options/test/test.js b/packages/kit/test/apps/options/test/test.js index cfd4e364652f..61cef9a29256 100644 --- a/packages/kit/test/apps/options/test/test.js +++ b/packages/kit/test/apps/options/test/test.js @@ -163,10 +163,14 @@ test.describe('trailingSlash', () => { expect(await r2.text()).toBe('hi'); }); - test('can fetch data from page-endpoint', async ({ request, baseURL }) => { - const r = await request.get('/path-base/page-endpoint/__data.json'); - expect(r.url()).toBe(`${baseURL}/path-base/page-endpoint/__data.json`); - expect(await r.json()).toEqual({ + test('can fetch data from page-endpoint', async ({ request }) => { + const r = await request.get('/path-base/page-endpoint/__data.js'); + const code = await r.text(); + + const window = {}; + new Function('window', code)(window); + + expect(window.__sveltekit_data).toEqual({ type: 'data', nodes: [null, { type: 'data', data: { message: 'hi' }, uses: {} }] }); @@ -197,7 +201,7 @@ test.describe('trailingSlash', () => { expect(requests.filter((req) => req.endsWith('.js')).length).toBeGreaterThan(0); } - expect(requests.includes(`${baseURL}/path-base/prefetching/prefetched/__data.json`)).toBe(true); + expect(requests.includes(`${baseURL}/path-base/prefetching/prefetched/__data.js`)).toBe(true); requests = []; await app.goto('/path-base/prefetching/prefetched'); diff --git a/packages/kit/test/prerendering/basics/test/test.js b/packages/kit/test/prerendering/basics/test/test.js index 2a450e9650b4..32d09321a2e2 100644 --- a/packages/kit/test/prerendering/basics/test/test.js +++ b/packages/kit/test/prerendering/basics/test/test.js @@ -25,14 +25,14 @@ test('renders a server-side redirect', () => { const html = read('redirect-server.html'); assert.equal(html, ''); - const json = read('redirect-server/__data.json'); - assert.equal( - json, - JSON.stringify({ - type: 'redirect', - location: 'https://example.com/redirected' - }) - ); + const code = read('redirect-server/__data.js'); + const window = {}; + new Function('window', `(${code})`)(window); + + assert.equal(window.__sveltekit_data, { + type: 'redirect', + location: 'https://example.com/redirected' + }); }); test('does not double-encode redirect locations', () => { @@ -77,31 +77,44 @@ test('loads a file with spaces in the filename', () => { assert.ok(content.includes('

answer: 42

'), content); }); -test('generates __data.json file for shadow endpoints', () => { - assert.equal( - read('__data.json'), - JSON.stringify({ - type: 'data', - nodes: [null, { type: 'data', data: { message: 'hello' }, uses: {} }] - }) - ); - assert.equal( - read('shadowed-get/__data.json'), - JSON.stringify({ - type: 'data', - nodes: [null, { type: 'data', data: { answer: 42 }, uses: {} }] - }) - ); +test('generates __data.js file for shadow endpoints', () => { + const window = {}; + + new Function('window', read('__data.js'))(window); + assert.equal(window.__sveltekit_data, { + type: 'data', + nodes: [ + null, + { + type: 'data', + data: { message: 'hello' }, + uses: { dependencies: undefined, params: undefined, parent: undefined, url: undefined } + } + ] + }); + + new Function('window', read('shadowed-get/__data.js'))(window); + assert.equal(window.__sveltekit_data, { + type: 'data', + nodes: [ + null, + { + type: 'data', + data: { answer: 42 }, + uses: { dependencies: undefined, params: undefined, parent: undefined, url: undefined } + } + ] + }); }); test('does not prerender page with shadow endpoint with non-load handler', () => { assert.ok(!fs.existsSync(`${build}/shadowed-post.html`)); - assert.ok(!fs.existsSync(`${build}/shadowed-post/__data.json`)); + assert.ok(!fs.existsSync(`${build}/shadowed-post/__data.js`)); }); test('does not prerender page with prerender = false in +page.server.js', () => { assert.ok(!fs.existsSync(`${build}/page-server-options.html`)); - assert.ok(!fs.existsSync(`${build}/page-server-options/__data.json`)); + assert.ok(!fs.existsSync(`${build}/page-server-options/__data.js`)); }); test('decodes paths when writing files', () => { @@ -175,13 +188,20 @@ test('prerenders binary data', async () => { }); test('fetches data from local endpoint', () => { - assert.equal( - read('origin/__data.json'), - JSON.stringify({ - type: 'data', - nodes: [null, { type: 'data', data: { message: 'hello' }, uses: {} }] - }) - ); + const window = {}; + new Function('window', read('origin/__data.js'))(window); + + assert.equal(window.__sveltekit_data, { + type: 'data', + nodes: [ + null, + { + type: 'data', + data: { message: 'hello' }, + uses: { dependencies: undefined, params: undefined, parent: undefined, url: undefined } + } + ] + }); assert.equal(read('origin/message.json'), JSON.stringify({ message: 'hello' })); }); diff --git a/packages/kit/test/prerendering/trailing-slash/test/test.js b/packages/kit/test/prerendering/trailing-slash/test/test.js index 8a7c6550a3f9..0344b61298ef 100644 --- a/packages/kit/test/prerendering/trailing-slash/test/test.js +++ b/packages/kit/test/prerendering/trailing-slash/test/test.js @@ -11,7 +11,7 @@ const read = (file) => fs.readFileSync(`${build}/${file}`, 'utf-8'); test('prerendered.paths omits trailing slashes for endpoints', () => { const content = read('service-worker.js'); - for (const path of ['/page/', '/page/__data.json', '/standalone-endpoint.json']) { + for (const path of ['/page/', '/page/__data.js', '/standalone-endpoint.json']) { assert.ok(content.includes(`"${path}"`), `Missing ${path}`); } }); From 978d0d41a8526ae286f8b19d8e14b344647f7264 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 14:38:02 -0400 Subject: [PATCH 10/23] lint --- .../basics/src/routes/shadowed/serialization/+page.server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.server.js b/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.server.js index 47e76245eb40..ee20c2cc975f 100644 --- a/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.server.js +++ b/packages/kit/test/apps/basics/src/routes/shadowed/serialization/+page.server.js @@ -1,6 +1,6 @@ class Nope { toString() { - return 'should not see me' + return 'should not see me'; } } From 8481a6a3a42888cf044cdfa7f0576dcf387aa0e1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 14:48:36 -0400 Subject: [PATCH 11/23] fixes --- packages/kit/package.json | 2 +- packages/kit/src/runtime/client/client.js | 26 ++++++++++++++++------ packages/kit/src/runtime/client/types.d.ts | 2 +- pnpm-lock.yaml | 8 +++---- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/kit/package.json b/packages/kit/package.json index 715a294a8044..5b2d59a246a0 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -12,7 +12,7 @@ "dependencies": { "@sveltejs/vite-plugin-svelte": "^1.0.1", "cookie": "^0.5.0", - "devalue": "^3.1.0", + "devalue": "^3.1.1", "kleur": "^4.1.4", "magic-string": "^0.26.2", "mime": "^3.0.0", diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 93d9142793b1..ba81b3922940 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -393,7 +393,7 @@ export function create_client({ target, base, trailing_slash }) { * status: number; * error: HttpError | Error | null; * routeId: string | null; - * validation_errors?: string | undefined; + * validation_errors?: Record | null; * }} opts */ async function get_navigation_result_from_branch({ @@ -1272,7 +1272,7 @@ export function create_client({ target, base, trailing_slash }) { _hydrate: async ({ status, - error, + error: original_error, // TODO get rid of this node_ids, params, routeId, @@ -1286,6 +1286,16 @@ export function create_client({ target, base, trailing_slash }) { try { const branch_promises = node_ids.map(async (n, i) => { + const server_data_node = server_data_nodes[i]; + + if (server_data_node?.type === 'error') { + if (server_data_node.httperror) { + throw error(server_data_node.httperror.status, server_data_node.httperror.message); + } + + throw server_data_node.error; + } + return load_node({ loader: nodes[n], url, @@ -1298,7 +1308,7 @@ export function create_client({ target, base, trailing_slash }) { } return data; }, - server_data_node: create_data_node(server_data_nodes[i]) + server_data_node: create_data_node(server_data_node) }); }); @@ -1307,13 +1317,15 @@ export function create_client({ target, base, trailing_slash }) { params, branch: await Promise.all(branch_promises), status, - error: /** @type {import('../server/page/types').SerializedHttpError} */ (error) + error: /** @type {import('../server/page/types').SerializedHttpError} */ (original_error) ?.__is_http_error ? new HttpError( - /** @type {import('../server/page/types').SerializedHttpError} */ (error).status, - error.message + /** @type {import('../server/page/types').SerializedHttpError} */ ( + original_error + ).status, + original_error.message ) - : error, + : original_error, validation_errors, routeId }); diff --git a/packages/kit/src/runtime/client/types.d.ts b/packages/kit/src/runtime/client/types.d.ts index af191e77176f..5c9757b17ad5 100644 --- a/packages/kit/src/runtime/client/types.d.ts +++ b/packages/kit/src/runtime/client/types.d.ts @@ -28,7 +28,7 @@ export interface Client { params: Record; routeId: string | null; data: Array; - errors: Record; + errors: Record | null; }) => Promise; _start_router: () => void; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 540d46a08b15..f4825e365c0e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -278,7 +278,7 @@ importers: '@types/sade': ^1.7.4 '@types/set-cookie-parser': ^2.4.2 cookie: ^0.5.0 - devalue: ^3.1.0 + devalue: ^3.1.1 kleur: ^4.1.4 magic-string: ^0.26.2 marked: ^4.0.16 @@ -298,7 +298,7 @@ importers: dependencies: '@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.48.0+vite@3.0.9 cookie: 0.5.0 - devalue: 3.1.0 + devalue: 3.1.1 kleur: 4.1.5 magic-string: 0.26.2 mime: 3.0.0 @@ -1748,8 +1748,8 @@ packages: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} - /devalue/3.1.0: - resolution: {integrity: sha512-5Nb73BzNSGvDJNWCVXHv9R+rhNg0pN+OsgeJ/BRYCvMsEBCDfP9Ci/5cHjSPoOSMWBrh3MtUDQtChfdKYWLxeg==} + /devalue/3.1.1: + resolution: {integrity: sha512-rq3zudzCv66+Kmt1iIVtdSl/czOhGsWrmgFuilyVCIZLHdK9n9Fg89YU+DPrhD9ZQBH+wAT6gIZPPS9wC7HV1w==} dev: false /diff/5.1.0: From 60c8d6620f81a6dc89abb422af549dc7201212c6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 14:50:39 -0400 Subject: [PATCH 12/23] fix --- packages/kit/package.json | 2 +- packages/kit/src/runtime/server/utils.js | 2 +- pnpm-lock.yaml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/kit/package.json b/packages/kit/package.json index 5b2d59a246a0..c276489c229d 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -12,7 +12,7 @@ "dependencies": { "@sveltejs/vite-plugin-svelte": "^1.0.1", "cookie": "^0.5.0", - "devalue": "^3.1.1", + "devalue": "^3.1.2", "kleur": "^4.1.4", "magic-string": "^0.26.2", "mime": "^3.0.0", diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 2c2b7a399ced..1f0382ca8f19 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -125,7 +125,7 @@ export function data_response(data) { } }); } catch (e) { - return new Response(`throw new Error(${JSON.stringify(e.message)})`, { + return new Response(`throw new Error(${JSON.stringify(/** @type {any} */ (e).message)})`, { headers: { 'content-type': 'application/javascript' } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4825e365c0e..45616a3e91a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -278,7 +278,7 @@ importers: '@types/sade': ^1.7.4 '@types/set-cookie-parser': ^2.4.2 cookie: ^0.5.0 - devalue: ^3.1.1 + devalue: ^3.1.2 kleur: ^4.1.4 magic-string: ^0.26.2 marked: ^4.0.16 @@ -298,7 +298,7 @@ importers: dependencies: '@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.48.0+vite@3.0.9 cookie: 0.5.0 - devalue: 3.1.1 + devalue: 3.1.2 kleur: 4.1.5 magic-string: 0.26.2 mime: 3.0.0 @@ -1748,8 +1748,8 @@ packages: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} - /devalue/3.1.1: - resolution: {integrity: sha512-rq3zudzCv66+Kmt1iIVtdSl/czOhGsWrmgFuilyVCIZLHdK9n9Fg89YU+DPrhD9ZQBH+wAT6gIZPPS9wC7HV1w==} + /devalue/3.1.2: + resolution: {integrity: sha512-wUXbMGPAsBx79UF14nsWSsJlC7RcwPlf2w3bGheODWxKx57e9n68ceoijbqCJCEbjyo0S79nqfPwQgyijwLaqw==} dev: false /diff/5.1.0: From 859f4cbd0d3028f482e782f160b68ce44a6ddb8e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 15:09:32 -0400 Subject: [PATCH 13/23] fix --- packages/kit/src/runtime/server/index.js | 2 +- packages/kit/test/prerendering/basics/test/test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index c87cf264a73e..5c03fb045901 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -83,7 +83,7 @@ export async function respond(request, options, state) { } } - if (route?.page) { + if (route?.page && !is_data_request) { const normalized = normalize_path(url.pathname, options.trailing_slash); if (normalized !== url.pathname && !state.prerendering?.fallback) { diff --git a/packages/kit/test/prerendering/basics/test/test.js b/packages/kit/test/prerendering/basics/test/test.js index 32d09321a2e2..29a62b3cebe0 100644 --- a/packages/kit/test/prerendering/basics/test/test.js +++ b/packages/kit/test/prerendering/basics/test/test.js @@ -27,7 +27,7 @@ test('renders a server-side redirect', () => { const code = read('redirect-server/__data.js'); const window = {}; - new Function('window', `(${code})`)(window); + new Function('window', code)(window); assert.equal(window.__sveltekit_data, { type: 'redirect', From 5a113b61f460049f425232639fa076385eebc1a4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 15:17:07 -0400 Subject: [PATCH 14/23] update docs --- documentation/docs/03-routing.md | 2 +- documentation/docs/05-load.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/docs/03-routing.md b/documentation/docs/03-routing.md index b085b4f9ac0f..5dff407cc72e 100644 --- a/documentation/docs/03-routing.md +++ b/documentation/docs/03-routing.md @@ -106,7 +106,7 @@ export async function load({ params }) { } ``` -During client-side navigation, SvelteKit will load this data using `fetch`, which means that the returned value must be serializable as JSON. +During client-side navigation, SvelteKit will load this data from the server, which means that the returned value must be serializable using [devalue](https://github.com/rich-harris/devalue). #### Actions diff --git a/documentation/docs/05-load.md b/documentation/docs/05-load.md index 040eaaf3c9a3..14f185a3519c 100644 --- a/documentation/docs/05-load.md +++ b/documentation/docs/05-load.md @@ -4,7 +4,7 @@ title: Loading data A [`+page.svelte`](/docs/routing#page-page-svelte) or [`+layout.svelte`](/docs/routing#layout-layout-svelte) gets its `data` from a `load` function. -If the `load` function is defined in `+page.js` or `+layout.js` it will run both on the server and in the browser. If it's instead defined in `+page.server.js` or `+layout.server.js` it will only run on the server, in which case it can (for example) make database calls and access private [environment variables](/docs/modules#$env-static-private), but can only return data that can be serialized as JSON. +If the `load` function is defined in `+page.js` or `+layout.js` it will run both on the server and in the browser. If it's instead defined in `+page.server.js` or `+layout.server.js` it will only run on the server, in which case it can (for example) make database calls and access private [environment variables](/docs/modules#$env-static-private), but can only return data that can be serialized with [devalue](https://github.com/rich-harris/devalue). ```js /// file: src/routes/+page.js From 74d595b37853e7cbba8ec1f4f7596361f4af6106 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 15:46:21 -0400 Subject: [PATCH 15/23] fix --- packages/kit/test/apps/options/test/test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/test/apps/options/test/test.js b/packages/kit/test/apps/options/test/test.js index 61cef9a29256..f3e17419a3ac 100644 --- a/packages/kit/test/apps/options/test/test.js +++ b/packages/kit/test/apps/options/test/test.js @@ -188,7 +188,7 @@ test.describe('trailingSlash', () => { /** @type {string[]} */ let requests = []; - page.on('request', (r) => requests.push(r.url())); + page.on('request', (r) => requests.push(new URL(r.url()).pathname)); // also wait for network processing to complete, see // https://playwright.dev/docs/network#network-events @@ -201,7 +201,7 @@ test.describe('trailingSlash', () => { expect(requests.filter((req) => req.endsWith('.js')).length).toBeGreaterThan(0); } - expect(requests.includes(`${baseURL}/path-base/prefetching/prefetched/__data.js`)).toBe(true); + expect(requests.includes(`/path-base/prefetching/prefetched/__data.js`)).toBe(true); requests = []; await app.goto('/path-base/prefetching/prefetched'); From 91115f6e6519190224ac6285c315fab3bddcec04 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 26 Aug 2022 16:04:55 -0400 Subject: [PATCH 16/23] changeset --- .changeset/tender-spiders-fail.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/tender-spiders-fail.md diff --git a/.changeset/tender-spiders-fail.md b/.changeset/tender-spiders-fail.md new file mode 100644 index 000000000000..2630783d2465 --- /dev/null +++ b/.changeset/tender-spiders-fail.md @@ -0,0 +1,7 @@ +--- +'@sveltejs/adapter-netlify': patch +'@sveltejs/adapter-vercel': patch +'@sveltejs/kit': patch +--- + +Use devalue to serialize server-only `load` return values From 5b4afb5bf2898be4cb7d62cc611c16f04233060f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 27 Aug 2022 07:37:12 -0400 Subject: [PATCH 17/23] fix --- packages/kit/src/runtime/client/client.js | 11 +++++++---- packages/kit/src/runtime/server/utils.js | 5 ++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index ba81b3922940..66a36f53a98a 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -701,10 +701,13 @@ export function create_client({ target, base, trailing_slash }) { if (route.uses_server_data && invalid_server_nodes.some(Boolean)) { try { server_data = await load_data(url, invalid_server_nodes); - } catch (e) { - // something went catastrophically wrong — bail and defer to the server - native_navigation(url); - return; + } catch (error) { + return load_root_error_page({ + status: 500, + error: /** @type {Error} */ (error), + url, + routeId: route.id + }); } if (server_data.type === 'redirect') { diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 1f0382ca8f19..09b36614ebc9 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -125,7 +125,10 @@ export function data_response(data) { } }); } catch (e) { - return new Response(`throw new Error(${JSON.stringify(/** @type {any} */ (e).message)})`, { + const error = /** @type {any} */ (e); + const match = /\[(\d+)\]\.data\.(.+)/.exec(error.path); + const message = match ? `${error.message} (data.${match[2]})` : error.message; + return new Response(`throw new Error(${JSON.stringify(message)})`, { headers: { 'content-type': 'application/javascript' } From ae423fd9abba9f6c04f0e718405b6f82b00f03aa Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 28 Aug 2022 11:59:20 -0400 Subject: [PATCH 18/23] comment --- packages/kit/src/runtime/client/client.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 66a36f53a98a..22f991a3f52b 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1368,6 +1368,13 @@ async function load_data(url, invalid) { data_url.searchParams.set('__invalid', invalid.map((x) => (x ? 'y' : 'n')).join('')); data_url.searchParams.set('__id', String(data_id++)); + // The __data.js file is generated by the server and looks like + // `window.__sveltekit_data = ${devalue(data)}`. We do this instead + // of `export const data` because modules are cached indefinitely, + // and that would cause memory leaks. + // + // The data is read and deleted in the same tick as the promise + // resolves, so it's not vulnerable to race conditions await import(/* @vite-ignore */ data_url.href); // @ts-expect-error From 3e3d05de1468f6541154c7f284d49649016d7041 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 28 Aug 2022 12:01:15 -0400 Subject: [PATCH 19/23] add comments --- packages/kit/src/runtime/server/page/render.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index a2574aeaf879..565365659ad2 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -179,6 +179,7 @@ export async function render_response({ try { serialized.data = devalue(branch.map(({ server_data }) => server_data)); } catch (e) { + // If we're here, the data could not be serialized with devalue // TODO if we wanted to get super fancy we could track down the origin of the `load` // function, but it would mean passing more stuff around than we currently do const error = /** @type {any} */ (e); @@ -191,6 +192,7 @@ export async function render_response({ try { serialized.errors = devalue(validation_errors); } catch (e) { + // If we're here, the data could not be serialized with devalue const error = /** @type {any} */ (e); if (error.path) throw new Error(`${error.message} (errors.${error.path})`); throw error; From b970d5cfafd076d943b8e8ce3eb1d42f9cc490ab Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 28 Aug 2022 12:01:27 -0400 Subject: [PATCH 20/23] use already-serialized data --- packages/kit/src/runtime/server/page/render.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 565365659ad2..9ed1d1e647a1 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -217,8 +217,8 @@ export async function render_response({ node_ids: [${branch.map(({ node }) => node.index).join(', ')}], params: ${devalue(event.params)}, routeId: ${s(event.routeId)}, - data: ${devalue(branch.map(({ server_data }) => server_data))}, - errors: ${validation_errors ? devalue(validation_errors) : 'null'} + data: ${serialized.data}, + errors: ${serialized.errors} }` : 'null'} }); `; From 358d0ceb323f9c53a2bfc6ef1acd9f742eadf1b5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 28 Aug 2022 12:04:36 -0400 Subject: [PATCH 21/23] use DATA_SUFFIX constant --- packages/kit/src/runtime/client/client.js | 3 ++- packages/kit/src/runtime/server/page/index.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 22f991a3f52b..2c44348eaa71 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -10,6 +10,7 @@ import Root from '__GENERATED__/root.svelte'; import { nodes, dictionary, matchers } from '__GENERATED__/client-manifest.js'; import { HttpError, Redirect } from '../control.js'; import { stores } from './singletons.js'; +import { DATA_SUFFIX } from '../../constants.js'; const SCROLL_KEY = 'sveltekit:scroll'; const INDEX_KEY = 'sveltekit:index'; @@ -1364,7 +1365,7 @@ let data_id = 1; */ async function load_data(url, invalid) { const data_url = new URL(url); - data_url.pathname += `${url.pathname.endsWith('/') ? '' : '/'}__data.js`; + data_url.pathname = url.pathname.replace(/\/$/, '') + DATA_SUFFIX; data_url.searchParams.set('__invalid', invalid.map((x) => (x ? 'y' : 'n')).join('')); data_url.searchParams.set('__id', String(data_id++)); diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index e29b5eb6e8cc..d350441a7158 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -9,6 +9,7 @@ import { error, json } from '../../../exports/index.js'; import { compact } from '../../../utils/array.js'; import { normalize_error } from '../../../utils/error.js'; import { load_data, load_server_data } from './load_data.js'; +import { DATA_SUFFIX } from '../../../constants.js'; /** * @typedef {import('./types.js').Loaded} Loaded @@ -103,7 +104,7 @@ export async function render_page(event, route, page, options, state, resolve_op } const should_prerender_data = nodes.some((node) => node?.server); - const data_pathname = `${event.url.pathname.replace(/\/$/, '')}/__data.js`; + const data_pathname = event.url.pathname.replace(/\/$/, '') + DATA_SUFFIX; // it's crucial that we do this before returning the non-SSR response, otherwise // SvelteKit will erroneously believe that the path has been prerendered, From b0bea0131eefbf1cc074014d53f628b3f1c26b8a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 29 Aug 2022 10:47:33 -0400 Subject: [PATCH 22/23] error nodes are not serialized for hydration --- packages/kit/src/runtime/client/client.js | 8 -------- packages/kit/src/runtime/client/types.d.ts | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 2c44348eaa71..db1ec271ade2 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1292,14 +1292,6 @@ export function create_client({ target, base, trailing_slash }) { const branch_promises = node_ids.map(async (n, i) => { const server_data_node = server_data_nodes[i]; - if (server_data_node?.type === 'error') { - if (server_data_node.httperror) { - throw error(server_data_node.httperror.status, server_data_node.httperror.message); - } - - throw server_data_node.error; - } - return load_node({ loader: nodes[n], url, diff --git a/packages/kit/src/runtime/client/types.d.ts b/packages/kit/src/runtime/client/types.d.ts index 5c9757b17ad5..e86bc3ae0eb2 100644 --- a/packages/kit/src/runtime/client/types.d.ts +++ b/packages/kit/src/runtime/client/types.d.ts @@ -27,7 +27,7 @@ export interface Client { node_ids: number[]; params: Record; routeId: string | null; - data: Array; + data: Array; errors: Record | null; }) => Promise; _start_router: () => void; From 5eafe2c0cc9b8ff8622d1ba16a119ae3b3dc2c9e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 29 Aug 2022 10:49:41 -0400 Subject: [PATCH 23/23] fix --- packages/kit/src/runtime/client/start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/client/start.js b/packages/kit/src/runtime/client/start.js index 78c245841616..aeadbc23d40f 100644 --- a/packages/kit/src/runtime/client/start.js +++ b/packages/kit/src/runtime/client/start.js @@ -20,7 +20,7 @@ export { set_public_env } from '../env-public.js'; * node_ids: number[]; * params: Record; * routeId: string | null; - * data: Array; + * data: Array; * errors: Record | null; * }; * }} opts