From 613a3dfc4cea16d401722951c01d0a7bea1fe253 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 6 Dec 2022 16:01:03 +0100 Subject: [PATCH 1/6] [breaking] add embedded option, turned off by default Closes #7940 Adds a new option which defaults to false. If true, events related to navigation etc are listened to on the target element rather the html root --- .changeset/popular-crabs-shop.md | 5 +++++ packages/kit/src/core/config/options.js | 2 ++ packages/kit/src/exports/vite/build/utils.js | 3 ++- packages/kit/src/exports/vite/index.js | 3 ++- packages/kit/src/runtime/client/client.js | 17 +++++++++-------- packages/kit/types/index.d.ts | 5 +++++ packages/kit/types/internal.d.ts | 1 + 7 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 .changeset/popular-crabs-shop.md diff --git a/.changeset/popular-crabs-shop.md b/.changeset/popular-crabs-shop.md new file mode 100644 index 000000000000..452168d186b0 --- /dev/null +++ b/.changeset/popular-crabs-shop.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[breaking] add embedded option, turned off by default diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index 29ec6d3d7b50..4d3aa71cc43a 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -129,6 +129,8 @@ const options = object( checkOrigin: boolean(true) }), + embedded: boolean(false), + // TODO: remove this for the 1.0 release endpointExtensions: error( (keypath) => `${keypath} has been renamed to config.kit.moduleExtensions` diff --git a/packages/kit/src/exports/vite/build/utils.js b/packages/kit/src/exports/vite/build/utils.js index 1dfc497a32bc..00877dc383d3 100644 --- a/packages/kit/src/exports/vite/build/utils.js +++ b/packages/kit/src/exports/vite/build/utils.js @@ -147,7 +147,8 @@ export function get_default_build_config({ config, input, ssr, outDir }) { __SVELTEKIT_APP_VERSION_FILE__: JSON.stringify(`${config.kit.appDir}/version.json`), __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: JSON.stringify(config.kit.version.pollInterval), __SVELTEKIT_BROWSER__: ssr ? 'false' : 'true', - __SVELTEKIT_DEV__: 'false' + __SVELTEKIT_DEV__: 'false', + __SVELTEKIT_EMBEDDED__: config.kit.embedded ? 'true' : 'false' }, publicDir: ssr ? false : config.kit.files.assets, resolve: { diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 179104d8c19b..78a721422a71 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -248,7 +248,8 @@ function kit() { define: { __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0', __SVELTEKIT_BROWSER__: config_env.ssrBuild ? 'false' : 'true', - __SVELTEKIT_DEV__: 'true' + __SVELTEKIT_DEV__: 'true', + __SVELTEKIT_EMBEDDED__: svelte_config.kit.embedded ? 'true' : 'false' }, publicDir: svelte_config.kit.files.assets, resolve: { diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 3957e2d57439..6d9dda9ce4b7 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -88,6 +88,7 @@ function check_for_removed_attributes() { * @returns {import('./types').Client} */ export function create_client({ target, base }) { + const container = __SVELTEKIT_EMBEDDED__ ? target : document.documentElement; /** @type {Array<((url: URL) => boolean)>} */ const invalidated = []; @@ -1194,7 +1195,7 @@ export function create_client({ target, base }) { /** @type {NodeJS.Timeout} */ let mousemove_timeout; - target.addEventListener('mousemove', (event) => { + container.addEventListener('mousemove', (event) => { const target = /** @type {Element} */ (event.target); clearTimeout(mousemove_timeout); @@ -1208,8 +1209,8 @@ export function create_client({ target, base }) { preload(/** @type {Element} */ (event.composedPath()[0]), 1); } - target.addEventListener('mousedown', tap); - target.addEventListener('touchstart', tap, { passive: true }); + container.addEventListener('mousedown', tap); + container.addEventListener('touchstart', tap, { passive: true }); const observer = new IntersectionObserver( (entries) => { @@ -1228,7 +1229,7 @@ export function create_client({ target, base }) { * @param {number} priority */ function preload(element, priority) { - const a = find_anchor(element, target); + const a = find_anchor(element, container); if (!a) return; const { url, external } = get_link_info(a, base); @@ -1248,7 +1249,7 @@ export function create_client({ target, base }) { function after_navigate() { observer.disconnect(); - for (const a of target.querySelectorAll('a')) { + for (const a of container.querySelectorAll('a')) { const { url, external } = get_link_info(a, base); if (external) continue; @@ -1452,14 +1453,14 @@ export function create_client({ target, base }) { } /** @param {MouseEvent} event */ - target.addEventListener('click', (event) => { + container.addEventListener('click', (event) => { // Adapted from https://github.com/visionmedia/page.js // MIT license https://github.com/visionmedia/page.js#license if (event.button || event.which !== 1) return; if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; if (event.defaultPrevented) return; - const a = find_anchor(/** @type {Element} */ (event.composedPath()[0]), target); + const a = find_anchor(/** @type {Element} */ (event.composedPath()[0]), container); if (!a) return; const { url, external, has } = get_link_info(a, base); @@ -1529,7 +1530,7 @@ export function create_client({ target, base }) { }); }); - target.addEventListener('submit', (event) => { + container.addEventListener('submit', (event) => { if (event.defaultPrevented) return; const form = /** @type {HTMLFormElement} */ ( diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index daf9de3795bc..58f3afe443da 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -325,6 +325,11 @@ export interface KitConfig { */ checkOrigin?: boolean; }; + /** + * Whether or not the app is embedded inside a larger app. If `true`, SvelteKit will add its event listeners related to navigation etc on the parent of `%sveltekit.body%` instead of `window`. + * @default false + */ + embedded?: boolean; /** * Environment variable configuration */ diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index dbcfc8be2243..d63bc7976eb1 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -385,4 +385,5 @@ declare global { const __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: number; const __SVELTEKIT_BROWSER__: boolean; const __SVELTEKIT_DEV__: boolean; + const __SVELTEKIT_EMBEDDED__: boolean; } From 87d4956c7eff815a28a7738d32d1e625261b09b1 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 6 Dec 2022 16:17:31 +0100 Subject: [PATCH 2/6] fix test --- packages/kit/src/core/config/index.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index f6da4aec9f32..12af12bf6e80 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -76,6 +76,7 @@ const get_defaults = (prefix = '') => ({ checkOrigin: true }, endpointExtensions: undefined, + embedded: false, env: { dir: process.cwd(), publicPrefix: 'PUBLIC_' From b31c6ebb01b9689232c821263ecfe3e9f884ead5 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 6 Dec 2022 20:57:17 +0100 Subject: [PATCH 3/6] support #4935 --- packages/kit/src/runtime/client/client.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 6d9dda9ce4b7..c3e8da80a672 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1640,6 +1640,12 @@ export function create_client({ target, base }) { const url = new URL(location.href); + if (!__SVELTEKIT_EMBEDDED__) { + // See https://github.com/sveltejs/kit/pull/4935#issuecomment-1328093358 for one motivation + // of determining the params on the client side. + params = get_navigation_intent(url, false)?.params || {}; + } + /** @type {import('./types').NavigationFinished | undefined} */ let result; From c87daed5a2992c8a0ba1a163a1d900841b6d7e82 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 7 Dec 2022 11:50:39 +0100 Subject: [PATCH 4/6] tests --- packages/kit/test/apps/basics/test/client.test.js | 4 ++-- .../routing/link-outside-app-target/source/+page.svelte | 8 ++++++++ .../pages/routing/link-outside-app-target/state.js | 5 +++++ .../routing/link-outside-app-target/target/+page.svelte | 5 +++++ packages/kit/test/apps/options/source/template.html | 1 + packages/kit/test/apps/options/svelte.config.js | 1 + packages/kit/test/apps/options/test/test.js | 9 +++++++++ 7 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/source/+page.svelte create mode 100644 packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/state.js create mode 100644 packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/target/+page.svelte diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js index b81213171568..0d0395b2db95 100644 --- a/packages/kit/test/apps/basics/test/client.test.js +++ b/packages/kit/test/apps/basics/test/client.test.js @@ -809,11 +809,11 @@ test.describe('Routing', () => { expect(await page.textContent('h1')).toBe('hello'); }); - test('ignores clicks outside the app target', async ({ page }) => { + test('recognizes clicks outside the app target', async ({ page }) => { await page.goto('/routing/link-outside-app-target/source'); await page.click('[href="/routing/link-outside-app-target/target"]'); - expect(await page.textContent('h1')).toBe('target: 0'); + await expect(page.locator('h1')).toHaveText('target: 1'); }); test('responds to
submission without reload', async ({ page }) => { diff --git a/packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/source/+page.svelte b/packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/source/+page.svelte new file mode 100644 index 000000000000..8324f9b0fabc --- /dev/null +++ b/packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/source/+page.svelte @@ -0,0 +1,8 @@ + + +

source: {count}

diff --git a/packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/state.js b/packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/state.js new file mode 100644 index 000000000000..ab2ad88ec9e6 --- /dev/null +++ b/packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/state.js @@ -0,0 +1,5 @@ +export let count = 0; + +export function increment() { + count += 1; +} diff --git a/packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/target/+page.svelte b/packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/target/+page.svelte new file mode 100644 index 000000000000..b3b5da849999 --- /dev/null +++ b/packages/kit/test/apps/options/source/pages/routing/link-outside-app-target/target/+page.svelte @@ -0,0 +1,5 @@ + + +

target: {count}

diff --git a/packages/kit/test/apps/options/source/template.html b/packages/kit/test/apps/options/source/template.html index a60716011e6f..f88f7128c74c 100644 --- a/packages/kit/test/apps/options/source/template.html +++ b/packages/kit/test/apps/options/source/template.html @@ -8,5 +8,6 @@

I am in the template

%sveltekit.body%
+ outside app target diff --git a/packages/kit/test/apps/options/svelte.config.js b/packages/kit/test/apps/options/svelte.config.js index bf17940ba1b4..bae1cda492e7 100644 --- a/packages/kit/test/apps/options/svelte.config.js +++ b/packages/kit/test/apps/options/svelte.config.js @@ -2,6 +2,7 @@ const config = { extensions: ['.jesuslivesineveryone', '.whokilledthemuffinman', '.svelte.md', '.svelte'], kit: { + embedded: true, csp: { directives: { 'script-src': ['self'] diff --git a/packages/kit/test/apps/options/test/test.js b/packages/kit/test/apps/options/test/test.js index a60fdcb5520d..5904672d0dc4 100644 --- a/packages/kit/test/apps/options/test/test.js +++ b/packages/kit/test/apps/options/test/test.js @@ -229,3 +229,12 @@ test.describe('Vite options', () => { expect(await page.textContent('h2')).toBe(`${mode} === ${mode} === ${mode}`); }); }); + +test.describe('Routing', () => { + test('ignores clicks outside the app target', async ({ page }) => { + await page.goto('/path-base/routing/link-outside-app-target/source'); + + await page.click('[href="/path-base/routing/link-outside-app-target/target"]'); + await expect(page.locator('h2')).toHaveText('target: 0'); + }); +}); From 834b2a5695dfa770128111dd61e449cf98d73844 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 8 Dec 2022 18:22:09 -0500 Subject: [PATCH 5/6] docs --- packages/kit/types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 58f3afe443da..610f50eed716 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -326,7 +326,7 @@ export interface KitConfig { checkOrigin?: boolean; }; /** - * Whether or not the app is embedded inside a larger app. If `true`, SvelteKit will add its event listeners related to navigation etc on the parent of `%sveltekit.body%` instead of `window`. + * Whether or not the app is embedded inside a larger app. If `true`, SvelteKit will add its event listeners related to navigation etc on the parent of `%sveltekit.body%` instead of `window`, and will pass `params` from the server rather than inferring them from `location.pathname`. * @default false */ embedded?: boolean; From 9e7c889dd6e62b6fa321ff9aea605cfa9a199335 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 8 Dec 2022 18:44:59 -0500 Subject: [PATCH 6/6] filter start options --- .../src/exports/vite/build/build_server.js | 1 + packages/kit/src/exports/vite/dev/index.js | 1 + packages/kit/src/runtime/client/client.js | 12 +++++- .../kit/src/runtime/server/page/render.js | 43 +++++++++++++------ packages/kit/types/internal.d.ts | 1 + 5 files changed, 43 insertions(+), 15 deletions(-) diff --git a/packages/kit/src/exports/vite/build/build_server.js b/packages/kit/src/exports/vite/build/build_server.js index 6ecd90272331..a0c73bc1e781 100644 --- a/packages/kit/src/exports/vite/build/build_server.js +++ b/packages/kit/src/exports/vite/build/build_server.js @@ -66,6 +66,7 @@ export class Server { check_origin: ${s(config.kit.csrf.checkOrigin)}, }, dev: false, + embedded: ${config.kit.embedded}, handle_error: (error, event) => { return this.options.hooks.handleError({ error, diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 02056f176d0c..236016c76e6c 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -444,6 +444,7 @@ export async function dev(vite, vite_config, svelte_config) { check_origin: svelte_config.kit.csrf.checkOrigin }, dev: true, + embedded: svelte_config.kit.embedded, handle_error: async (error, event) => { const error_object = await hooks.handleError({ error: new Proxy(error, { diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index c3e8da80a672..2374f1861cc1 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1635,7 +1635,15 @@ export function create_client({ target, base }) { }); }, - _hydrate: async ({ status, error, node_ids, params, route, data: server_data_nodes, form }) => { + _hydrate: async ({ + status = 200, + error, + node_ids, + params, + route, + data: server_data_nodes, + form + }) => { hydrated = true; const url = new URL(location.href); @@ -1643,7 +1651,7 @@ export function create_client({ target, base }) { if (!__SVELTEKIT_EMBEDDED__) { // See https://github.com/sveltejs/kit/pull/4935#issuecomment-1328093358 for one motivation // of determining the params on the client side. - params = get_navigation_intent(url, false)?.params || {}; + ({ params = {}, route = { id: null } } = get_navigation_intent(url, false) || {}); } /** @type {import('./types').NavigationFinished | undefined} */ diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 18c6dd8073a3..5d1680e99575 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -266,24 +266,41 @@ export async function render_response({ } if (page_config.csr) { + const opts = [ + `env: ${s(options.public_env)}`, + `paths: ${s(options.paths)}`, + `target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode`, + `version: ${s(options.version)}` + ]; + + if (page_config.ssr) { + const hydrate = [ + `node_ids: [${branch.map(({ node }) => node.index).join(', ')}]`, + `data: ${serialized.data}`, + `form: ${serialized.form}` + ]; + + if (status !== 200) { + hydrate.push(`status: ${status}`); + } + + if (error) { + hydrate.push(`error: ${devalue.uneval(error)}`); + } + + if (options.embedded) { + hydrate.push(`params: ${devalue.uneval(event.params)}`, `route: ${s(event.route)}`); + } + + opts.push(`hydrate: {\n\t\t\t\t\t${hydrate.join(',\n\t\t\t\t\t')}\n\t\t\t\t}`); + } + // prettier-ignore const init_app = ` import { start } from ${s(prefixed(entry.file))}; start({ - env: ${s(options.public_env)}, - hydrate: ${page_config.ssr ? `{ - status: ${status}, - error: ${devalue.uneval(error)}, - node_ids: [${branch.map(({ node }) => node.index).join(', ')}], - params: ${devalue.uneval(event.params)}, - route: ${s(event.route)}, - data: ${serialized.data}, - form: ${serialized.form} - }` : 'null'}, - paths: ${s(options.paths)}, - target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode, - version: ${s(options.version)} + ${opts.join(',\n\t\t\t\t')} }); `; diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index d63bc7976eb1..5a3543bba7b2 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -298,6 +298,7 @@ export interface SSROptions { check_origin: boolean; }; dev: boolean; + embedded: boolean; handle_error(error: Error & { frame?: string }, event: RequestEvent): MaybePromise; hooks: ServerHooks; manifest: SSRManifest;