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/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_' 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/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/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/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/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..2374f1861cc1 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} */ ( @@ -1634,11 +1635,25 @@ 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); + 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 = {}, route = { id: null } } = get_navigation_intent(url, false) || {}); + } + /** @type {import('./types').NavigationFinished | undefined} */ let result; 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/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