diff --git a/.changeset/ten-rats-spend.md b/.changeset/ten-rats-spend.md new file mode 100644 index 000000000000..8e5a0b35b408 --- /dev/null +++ b/.changeset/ten-rats-spend.md @@ -0,0 +1,6 @@ +--- +'@sveltejs/kit': patch +--- + +fix: `afterNavigate` callback not running after hydration when experimental async is enabled +fix: Snapshot `restore` method not called after reload when experimental async is enabled diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 768ee347ee4c..c33a2be38b14 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -545,7 +545,7 @@ async function _preload_code(url) { * @param {HTMLElement} target * @param {boolean} hydrate */ -function initialize(result, target, hydrate) { +async function initialize(result, target, hydrate) { if (DEV && result.state.error && document.querySelector('vite-error-overlay')) return; current = result.state; @@ -563,6 +563,10 @@ function initialize(result, target, hydrate) { sync: false }); + // Wait for a microtask in case svelte experimental async is enabled, + // which causes component script blocks to run asynchronously + void (await Promise.resolve()); + restore_snapshot(current_navigation_index); if (hydrate) { @@ -1696,7 +1700,7 @@ async function navigate({ update(navigation_result.props.page); has_navigated = true; } else { - initialize(navigation_result, target, false); + await initialize(navigation_result, target, false); } const { activeElement } = document; @@ -2799,7 +2803,7 @@ async function _hydrate( result.props.page.state = {}; } - initialize(result, target, hydrate); + await initialize(result, target, hydrate); } /** diff --git a/packages/kit/test/apps/async/package.json b/packages/kit/test/apps/async/package.json new file mode 100644 index 000000000000..4fc2f1636248 --- /dev/null +++ b/packages/kit/test/apps/async/package.json @@ -0,0 +1,24 @@ +{ + "name": "test-async", + "private": true, + "version": "0.0.1", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync", + "check": "svelte-kit sync && tsc && svelte-check", + "test": "pnpm test:dev && pnpm test:build", + "test:dev": "DEV=true playwright test", + "test:build": "playwright test" + }, + "devDependencies": { + "@sveltejs/kit": "workspace:^", + "@sveltejs/vite-plugin-svelte": "catalog:", + "svelte": "catalog:", + "svelte-check": "catalog:", + "typescript": "^5.5.4", + "vite": "catalog:" + }, + "type": "module" +} diff --git a/packages/kit/test/apps/async/playwright.config.js b/packages/kit/test/apps/async/playwright.config.js new file mode 100644 index 000000000000..33d36b651014 --- /dev/null +++ b/packages/kit/test/apps/async/playwright.config.js @@ -0,0 +1 @@ +export { config as default } from '../../utils.js'; diff --git a/packages/kit/test/apps/async/src/app.html b/packages/kit/test/apps/async/src/app.html new file mode 100644 index 000000000000..79d946ed86a3 --- /dev/null +++ b/packages/kit/test/apps/async/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/packages/kit/test/apps/async/src/routes/+layout.svelte b/packages/kit/test/apps/async/src/routes/+layout.svelte new file mode 100644 index 000000000000..18102623b5bc --- /dev/null +++ b/packages/kit/test/apps/async/src/routes/+layout.svelte @@ -0,0 +1,9 @@ + + +{@render children()} diff --git a/packages/kit/test/apps/async/src/routes/+page.svelte b/packages/kit/test/apps/async/src/routes/+page.svelte new file mode 100644 index 000000000000..0a88878c34c2 --- /dev/null +++ b/packages/kit/test/apps/async/src/routes/+page.svelte @@ -0,0 +1 @@ +

home

diff --git a/packages/kit/test/apps/async/src/routes/after_navigate/+page.svelte b/packages/kit/test/apps/async/src/routes/after_navigate/+page.svelte new file mode 100644 index 000000000000..2e7d06b97444 --- /dev/null +++ b/packages/kit/test/apps/async/src/routes/after_navigate/+page.svelte @@ -0,0 +1,10 @@ + + +
{runs}
diff --git a/packages/kit/test/apps/async/src/routes/snapshot/+page.svelte b/packages/kit/test/apps/async/src/routes/snapshot/+page.svelte new file mode 100644 index 000000000000..160079383b48 --- /dev/null +++ b/packages/kit/test/apps/async/src/routes/snapshot/+page.svelte @@ -0,0 +1,14 @@ + + +
{JSON.stringify(restored)}
diff --git a/packages/kit/test/apps/async/static/favicon.png b/packages/kit/test/apps/async/static/favicon.png new file mode 100644 index 000000000000..825b9e65af7c Binary files /dev/null and b/packages/kit/test/apps/async/static/favicon.png differ diff --git a/packages/kit/test/apps/async/svelte.config.js b/packages/kit/test/apps/async/svelte.config.js new file mode 100644 index 000000000000..04b3a15e32bb --- /dev/null +++ b/packages/kit/test/apps/async/svelte.config.js @@ -0,0 +1,11 @@ +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: {}, + compilerOptions: { + experimental: { + async: true + } + } +}; + +export default config; diff --git a/packages/kit/test/apps/async/test/test.js b/packages/kit/test/apps/async/test/test.js new file mode 100644 index 000000000000..3fce3a062373 --- /dev/null +++ b/packages/kit/test/apps/async/test/test.js @@ -0,0 +1,20 @@ +import { expect } from '@playwright/test'; +import { test } from '../../../utils.js'; + +/** @typedef {import('@playwright/test').Response} Response */ + +test.skip(({ javaScriptEnabled }) => !javaScriptEnabled); + +test.describe.configure({ mode: 'parallel' }); + +test('afterNavigate runs after hydration', async ({ page }) => { + await page.goto('/after_navigate'); + + expect(await page.innerText('pre')).toBe('1'); +}); + +test('snapshots are restored after reload', async ({ page }) => { + await page.goto('/snapshot'); + await page.reload(); + expect(await page.innerText('pre')).toBe('{"restored":"yes"}'); +}); diff --git a/packages/kit/test/apps/async/tsconfig.json b/packages/kit/test/apps/async/tsconfig.json new file mode 100644 index 000000000000..1d665886266b --- /dev/null +++ b/packages/kit/test/apps/async/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "noEmit": true, + "resolveJsonModule": true + }, + "extends": "./.svelte-kit/tsconfig.json" +} diff --git a/packages/kit/test/apps/async/vite.config.js b/packages/kit/test/apps/async/vite.config.js new file mode 100644 index 000000000000..9640847b2704 --- /dev/null +++ b/packages/kit/test/apps/async/vite.config.js @@ -0,0 +1,21 @@ +import * as path from 'node:path'; +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').UserConfig} */ +const config = { + build: { + minify: false + }, + clearScreen: false, + plugins: [sveltekit()], + server: { + fs: { + allow: [path.resolve('../../../src')] + } + }, + optimizeDeps: { + exclude: ['svelte'] + } +}; + +export default config; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74004538fd1d..deec0387ba63 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -618,6 +618,27 @@ importers: specifier: 'catalog:' version: 6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + packages/kit/test/apps/async: + devDependencies: + '@sveltejs/kit': + specifier: workspace:^ + version: link:../../.. + '@sveltejs/vite-plugin-svelte': + specifier: 'catalog:' + version: 6.0.0-next.3(svelte@5.39.8)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)) + svelte: + specifier: 'catalog:' + version: 5.39.8 + svelte-check: + specifier: 'catalog:' + version: 4.1.1(picomatch@4.0.3)(svelte@5.39.8)(typescript@5.8.3) + typescript: + specifier: ^5.5.4 + version: 5.8.3 + vite: + specifier: 'catalog:' + version: 6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0) + packages/kit/test/apps/basics: devDependencies: '@opentelemetry/api':