@sveltejs/kit 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/core/adapt/builder.js +57 -29
- package/src/core/adapt/index.js +2 -6
- package/src/core/postbuild/analyse.js +26 -0
- package/src/core/sync/write_root.js +6 -5
- package/src/core/sync/write_tsconfig.js +20 -10
- package/src/core/sync/write_types/index.js +19 -16
- package/src/runtime/client/client.js +74 -19
- package/src/runtime/client/constants.js +1 -0
- package/src/runtime/client/session-storage.js +25 -0
- package/src/runtime/server/page/render.js +1 -1
- package/src/utils/exports.js +6 -3
- package/types/index.d.ts +48 -3
- package/types/internal.d.ts +4 -0
- package/types/private.d.ts +7 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltejs/kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/sveltejs/kit",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"set-cookie-parser": "^2.5.1",
|
|
23
23
|
"sirv": "^2.0.2",
|
|
24
24
|
"tiny-glob": "^0.2.9",
|
|
25
|
-
"undici": "5.
|
|
25
|
+
"undici": "5.18.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@playwright/test": "^1.29.2",
|
|
@@ -18,13 +18,54 @@ const pipe = promisify(pipeline);
|
|
|
18
18
|
* config: import('types').ValidatedConfig;
|
|
19
19
|
* build_data: import('types').BuildData;
|
|
20
20
|
* server_metadata: import('types').ServerMetadata;
|
|
21
|
-
*
|
|
21
|
+
* route_data: import('types').RouteData[];
|
|
22
22
|
* prerendered: import('types').Prerendered;
|
|
23
|
+
* prerender_map: import('types').PrerenderMap;
|
|
23
24
|
* log: import('types').Logger;
|
|
24
25
|
* }} opts
|
|
25
26
|
* @returns {import('types').Builder}
|
|
26
27
|
*/
|
|
27
|
-
export function create_builder({
|
|
28
|
+
export function create_builder({
|
|
29
|
+
config,
|
|
30
|
+
build_data,
|
|
31
|
+
server_metadata,
|
|
32
|
+
route_data,
|
|
33
|
+
prerendered,
|
|
34
|
+
prerender_map,
|
|
35
|
+
log
|
|
36
|
+
}) {
|
|
37
|
+
/** @type {Map<import('types').RouteDefinition, import('types').RouteData>} */
|
|
38
|
+
const lookup = new Map();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Rather than exposing the internal `RouteData` type, which is subject to change,
|
|
42
|
+
* we expose a stable type that adapters can use to group/filter routes
|
|
43
|
+
*/
|
|
44
|
+
const routes = route_data.map((route) => {
|
|
45
|
+
const methods =
|
|
46
|
+
/** @type {import('types').HttpMethod[]} */
|
|
47
|
+
(server_metadata.routes.get(route.id)?.methods);
|
|
48
|
+
const config = server_metadata.routes.get(route.id)?.config;
|
|
49
|
+
|
|
50
|
+
/** @type {import('types').RouteDefinition} */
|
|
51
|
+
const facade = {
|
|
52
|
+
id: route.id,
|
|
53
|
+
segments: get_route_segments(route.id).map((segment) => ({
|
|
54
|
+
dynamic: segment.includes('['),
|
|
55
|
+
rest: segment.includes('[...'),
|
|
56
|
+
content: segment
|
|
57
|
+
})),
|
|
58
|
+
pattern: route.pattern,
|
|
59
|
+
prerender: prerender_map.get(route.id) ?? false,
|
|
60
|
+
methods,
|
|
61
|
+
config
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
lookup.set(facade, route);
|
|
65
|
+
|
|
66
|
+
return facade;
|
|
67
|
+
});
|
|
68
|
+
|
|
28
69
|
return {
|
|
29
70
|
log,
|
|
30
71
|
rimraf,
|
|
@@ -33,6 +74,7 @@ export function create_builder({ config, build_data, server_metadata, routes, pr
|
|
|
33
74
|
|
|
34
75
|
config,
|
|
35
76
|
prerendered,
|
|
77
|
+
routes,
|
|
36
78
|
|
|
37
79
|
async compress(directory) {
|
|
38
80
|
if (!existsSync(directory)) {
|
|
@@ -52,29 +94,12 @@ export function create_builder({ config, build_data, server_metadata, routes, pr
|
|
|
52
94
|
},
|
|
53
95
|
|
|
54
96
|
async createEntries(fn) {
|
|
55
|
-
/** @type {import('types').RouteDefinition[]} */
|
|
56
|
-
const facades = routes.map((route) => {
|
|
57
|
-
const methods =
|
|
58
|
-
/** @type {import('types').HttpMethod[]} */
|
|
59
|
-
(server_metadata.routes.get(route.id)?.methods);
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
id: route.id,
|
|
63
|
-
segments: get_route_segments(route.id).map((segment) => ({
|
|
64
|
-
dynamic: segment.includes('['),
|
|
65
|
-
rest: segment.includes('[...'),
|
|
66
|
-
content: segment
|
|
67
|
-
})),
|
|
68
|
-
pattern: route.pattern,
|
|
69
|
-
methods
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
|
|
73
97
|
const seen = new Set();
|
|
74
98
|
|
|
75
|
-
for (let i = 0; i <
|
|
76
|
-
const route =
|
|
77
|
-
|
|
99
|
+
for (let i = 0; i < route_data.length; i += 1) {
|
|
100
|
+
const route = route_data[i];
|
|
101
|
+
if (prerender_map.get(route.id) === true) continue;
|
|
102
|
+
const { id, filter, complete } = fn(routes[i]);
|
|
78
103
|
|
|
79
104
|
if (seen.has(id)) continue;
|
|
80
105
|
seen.add(id);
|
|
@@ -82,9 +107,10 @@ export function create_builder({ config, build_data, server_metadata, routes, pr
|
|
|
82
107
|
const group = [route];
|
|
83
108
|
|
|
84
109
|
// figure out which lower priority routes should be considered fallbacks
|
|
85
|
-
for (let j = i + 1; j <
|
|
86
|
-
if (
|
|
87
|
-
|
|
110
|
+
for (let j = i + 1; j < route_data.length; j += 1) {
|
|
111
|
+
if (prerender_map.get(routes[j].id) === true) continue;
|
|
112
|
+
if (filter(routes[j])) {
|
|
113
|
+
group.push(route_data[j]);
|
|
88
114
|
}
|
|
89
115
|
}
|
|
90
116
|
|
|
@@ -95,7 +121,7 @@ export function create_builder({ config, build_data, server_metadata, routes, pr
|
|
|
95
121
|
// TODO is this still necessary, given the new way of doing things?
|
|
96
122
|
filtered.forEach((route) => {
|
|
97
123
|
if (route.page) {
|
|
98
|
-
const endpoint =
|
|
124
|
+
const endpoint = route_data.find((candidate) => candidate.id === route.id + '.json');
|
|
99
125
|
|
|
100
126
|
if (endpoint) {
|
|
101
127
|
filtered.add(endpoint);
|
|
@@ -143,11 +169,13 @@ export function create_builder({ config, build_data, server_metadata, routes, pr
|
|
|
143
169
|
});
|
|
144
170
|
},
|
|
145
171
|
|
|
146
|
-
generateManifest: ({ relativePath }) => {
|
|
172
|
+
generateManifest: ({ relativePath, routes: subset }) => {
|
|
147
173
|
return generate_manifest({
|
|
148
174
|
build_data,
|
|
149
175
|
relative_path: relativePath,
|
|
150
|
-
routes
|
|
176
|
+
routes: subset
|
|
177
|
+
? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route)))
|
|
178
|
+
: route_data
|
|
151
179
|
});
|
|
152
180
|
},
|
|
153
181
|
|
package/src/core/adapt/index.js
CHANGED
|
@@ -18,13 +18,9 @@ export async function adapt(config, build_data, server_metadata, prerendered, pr
|
|
|
18
18
|
config,
|
|
19
19
|
build_data,
|
|
20
20
|
server_metadata,
|
|
21
|
-
|
|
22
|
-
if (!route.page && !route.endpoint) return false;
|
|
23
|
-
|
|
24
|
-
const prerender = prerender_map.get(route.id);
|
|
25
|
-
return prerender === false || prerender === undefined || prerender === 'auto';
|
|
26
|
-
}),
|
|
21
|
+
route_data: build_data.manifest_data.routes.filter((route) => route.page || route.endpoint),
|
|
27
22
|
prerendered,
|
|
23
|
+
prerender_map,
|
|
28
24
|
log
|
|
29
25
|
});
|
|
30
26
|
await adapt(builder);
|
|
@@ -67,6 +67,8 @@ async function analyse({ manifest_path, env }) {
|
|
|
67
67
|
|
|
68
68
|
/** @type {import('types').PrerenderOption | undefined} */
|
|
69
69
|
let prerender = undefined;
|
|
70
|
+
/** @type {any} */
|
|
71
|
+
let config = undefined;
|
|
70
72
|
|
|
71
73
|
if (route.endpoint) {
|
|
72
74
|
const mod = await route.endpoint();
|
|
@@ -87,6 +89,8 @@ async function analyse({ manifest_path, env }) {
|
|
|
87
89
|
if (mod.PUT) methods.add('PUT');
|
|
88
90
|
if (mod.PATCH) methods.add('PATCH');
|
|
89
91
|
if (mod.DELETE) methods.add('DELETE');
|
|
92
|
+
|
|
93
|
+
config = mod.config;
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
if (route.page) {
|
|
@@ -123,13 +127,35 @@ async function analyse({ manifest_path, env }) {
|
|
|
123
127
|
(should_prerender !== false && get_option(nodes, 'ssr') === false && !page?.server?.actions
|
|
124
128
|
? 'auto'
|
|
125
129
|
: should_prerender ?? false);
|
|
130
|
+
|
|
131
|
+
config = get_config(nodes);
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
metadata.routes.set(route.id, {
|
|
129
135
|
prerender,
|
|
136
|
+
config,
|
|
130
137
|
methods: Array.from(methods)
|
|
131
138
|
});
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
return metadata;
|
|
135
142
|
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Do a shallow merge (first level) of the config object
|
|
146
|
+
* @param {Array<import('types').SSRNode | undefined>} nodes
|
|
147
|
+
*/
|
|
148
|
+
function get_config(nodes) {
|
|
149
|
+
let current = {};
|
|
150
|
+
for (const node of nodes) {
|
|
151
|
+
const config = node?.universal?.config ?? node?.server?.config;
|
|
152
|
+
if (config) {
|
|
153
|
+
current = {
|
|
154
|
+
...current,
|
|
155
|
+
...config
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return Object.keys(current).length ? current : undefined;
|
|
161
|
+
}
|
|
@@ -21,16 +21,16 @@ export function write_root(manifest_data, output) {
|
|
|
21
21
|
|
|
22
22
|
let l = max_depth;
|
|
23
23
|
|
|
24
|
-
let pyramid = `<svelte:component this={components[${l}]} data={data_${l}} {form} />`;
|
|
24
|
+
let pyramid = `<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} {form} />`;
|
|
25
25
|
|
|
26
26
|
while (l--) {
|
|
27
27
|
pyramid = `
|
|
28
|
-
{#if
|
|
29
|
-
<svelte:component this={components[${l}]} data={data_${l}}>
|
|
28
|
+
{#if constructors[${l + 1}]}
|
|
29
|
+
<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}}>
|
|
30
30
|
${pyramid.replace(/\n/g, '\n\t\t\t\t\t')}
|
|
31
31
|
</svelte:component>
|
|
32
32
|
{:else}
|
|
33
|
-
<svelte:component this={components[${l}]} data={data_${l}} {form} />
|
|
33
|
+
<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} {form} />
|
|
34
34
|
{/if}
|
|
35
35
|
`
|
|
36
36
|
.replace(/^\t\t\t/gm, '')
|
|
@@ -49,7 +49,8 @@ export function write_root(manifest_data, output) {
|
|
|
49
49
|
export let stores;
|
|
50
50
|
export let page;
|
|
51
51
|
|
|
52
|
-
export let
|
|
52
|
+
export let constructors;
|
|
53
|
+
export let components = [];
|
|
53
54
|
export let form;
|
|
54
55
|
${levels.map((l) => `export let data_${l} = null;`).join('\n\t\t\t\t')}
|
|
55
56
|
|
|
@@ -85,19 +85,29 @@ export function get_tsconfig(kit, include_base_url) {
|
|
|
85
85
|
/** @param {string} file */
|
|
86
86
|
const config_relative = (file) => posixify(path.relative(kit.outDir, file));
|
|
87
87
|
|
|
88
|
-
const include =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
const include = new Set([
|
|
89
|
+
'ambient.d.ts',
|
|
90
|
+
'./types/**/$types.d.ts',
|
|
91
|
+
config_relative('vite.config.ts')
|
|
92
|
+
]);
|
|
93
|
+
// TODO(v2): find a better way to include all src files. We can't just use routes/lib only because
|
|
94
|
+
// people might have other folders/files in src that they want included.
|
|
95
|
+
const src_includes = [kit.files.routes, kit.files.lib, path.resolve('src')].filter((dir) => {
|
|
96
|
+
const relative = path.relative(path.resolve('src'), dir);
|
|
97
|
+
return !relative || relative.startsWith('..');
|
|
98
|
+
});
|
|
99
|
+
for (const dir of src_includes) {
|
|
100
|
+
include.add(config_relative(`${dir}/**/*.js`));
|
|
101
|
+
include.add(config_relative(`${dir}/**/*.ts`));
|
|
102
|
+
include.add(config_relative(`${dir}/**/*.svelte`));
|
|
94
103
|
}
|
|
104
|
+
|
|
95
105
|
// Test folder is a special case - we advocate putting tests in a top-level test folder
|
|
96
106
|
// and it's not configurable (should we make it?)
|
|
97
107
|
const test_folder = project_relative('tests');
|
|
98
|
-
include.
|
|
99
|
-
include.
|
|
100
|
-
include.
|
|
108
|
+
include.add(config_relative(`${test_folder}/**/*.js`));
|
|
109
|
+
include.add(config_relative(`${test_folder}/**/*.ts`));
|
|
110
|
+
include.add(config_relative(`${test_folder}/**/*.svelte`));
|
|
101
111
|
|
|
102
112
|
const exclude = [config_relative('node_modules/**'), './[!ambient.d.ts]**'];
|
|
103
113
|
if (path.extname(kit.files.serviceWorker)) {
|
|
@@ -135,7 +145,7 @@ export function get_tsconfig(kit, include_base_url) {
|
|
|
135
145
|
// TODO(v2): use the new flag verbatimModuleSyntax instead (requires support by Vite/Esbuild)
|
|
136
146
|
ignoreDeprecations: ts && Number(ts.version.split('.')[0]) >= 5 ? '5.0' : undefined
|
|
137
147
|
},
|
|
138
|
-
include,
|
|
148
|
+
include: [...include],
|
|
139
149
|
exclude
|
|
140
150
|
};
|
|
141
151
|
|
|
@@ -198,23 +198,26 @@ function update_types(config, routes, route, to_delete = new Set()) {
|
|
|
198
198
|
|
|
199
199
|
// These could also be placed in our public types, but it would bloat them unnecessarily and we may want to change these in the future
|
|
200
200
|
if (route.layout || route.leaf) {
|
|
201
|
-
// If T extends the empty object, void is also allowed as a return type
|
|
202
|
-
declarations.push(`type MaybeWithVoid<T> = {} extends T ? T | void : T;`);
|
|
203
|
-
// Returns the key of the object whose values are required.
|
|
204
201
|
declarations.push(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
202
|
+
// If T extends the empty object, void is also allowed as a return type
|
|
203
|
+
`type MaybeWithVoid<T> = {} extends T ? T | void : T;`,
|
|
204
|
+
|
|
205
|
+
// Returns the key of the object whose values are required.
|
|
206
|
+
`export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];`,
|
|
207
|
+
|
|
208
|
+
// Helper type to get the correct output type for load functions. It should be passed the parent type to check what types from App.PageData are still required.
|
|
209
|
+
// If none, void is also allowed as a return type.
|
|
210
|
+
`type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>`,
|
|
211
|
+
|
|
212
|
+
// null & {} == null, we need to prevent that in some situations
|
|
213
|
+
`type EnsureDefined<T> = T extends null | undefined ? {} : T;`,
|
|
214
|
+
|
|
215
|
+
// Takes a union type and returns a union type where each type also has all properties
|
|
216
|
+
// of all possible types (typed as undefined), making accessing them more ergonomic
|
|
217
|
+
`type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;`,
|
|
218
|
+
|
|
219
|
+
// Re-export `Snapshot` from @sveltejs/kit — in future we could use this to infer <T> from the return type of `snapshot.capture`
|
|
220
|
+
`export type Snapshot<T = any> = Kit.Snapshot<T>;`
|
|
218
221
|
);
|
|
219
222
|
}
|
|
220
223
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
is_external_url,
|
|
16
16
|
scroll_state
|
|
17
17
|
} from './utils.js';
|
|
18
|
+
import * as storage from './session-storage.js';
|
|
18
19
|
import {
|
|
19
20
|
lock_fetch,
|
|
20
21
|
unlock_fetch,
|
|
@@ -31,7 +32,7 @@ import { HttpError, Redirect } from '../control.js';
|
|
|
31
32
|
import { stores } from './singletons.js';
|
|
32
33
|
import { unwrap_promises } from '../../utils/promises.js';
|
|
33
34
|
import * as devalue from 'devalue';
|
|
34
|
-
import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY } from './constants.js';
|
|
35
|
+
import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY, SNAPSHOT_KEY } from './constants.js';
|
|
35
36
|
import { validate_common_exports } from '../../utils/exports.js';
|
|
36
37
|
import { compact } from '../../utils/array.js';
|
|
37
38
|
|
|
@@ -52,12 +53,10 @@ default_error_loader();
|
|
|
52
53
|
|
|
53
54
|
/** @typedef {{ x: number, y: number }} ScrollPosition */
|
|
54
55
|
/** @type {Record<number, ScrollPosition>} */
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// do nothing
|
|
60
|
-
}
|
|
56
|
+
const scroll_positions = storage.get(SCROLL_KEY) ?? {};
|
|
57
|
+
|
|
58
|
+
/** @type {Record<string, any[]>} */
|
|
59
|
+
const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
|
|
61
60
|
|
|
62
61
|
/** @param {number} index */
|
|
63
62
|
function update_scroll_positions(index) {
|
|
@@ -75,6 +74,14 @@ export function create_client({ target }) {
|
|
|
75
74
|
/** @type {Array<((url: URL) => boolean)>} */
|
|
76
75
|
const invalidated = [];
|
|
77
76
|
|
|
77
|
+
/**
|
|
78
|
+
* An array of the `+layout.svelte` and `+page.svelte` component instances
|
|
79
|
+
* that currently live on the page — used for capturing and restoring snapshots.
|
|
80
|
+
* It's updated/manipulated through `bind:this` in `Root.svelte`.
|
|
81
|
+
* @type {import('svelte').SvelteComponent[]}
|
|
82
|
+
*/
|
|
83
|
+
const components = [];
|
|
84
|
+
|
|
78
85
|
/** @type {{id: string, promise: Promise<import('./types').NavigationResult>} | null} */
|
|
79
86
|
let load_cache = null;
|
|
80
87
|
|
|
@@ -158,6 +165,20 @@ export function create_client({ target }) {
|
|
|
158
165
|
await update(intent, url, []);
|
|
159
166
|
}
|
|
160
167
|
|
|
168
|
+
/** @param {number} index */
|
|
169
|
+
function capture_snapshot(index) {
|
|
170
|
+
if (components.some((c) => c?.snapshot)) {
|
|
171
|
+
snapshots[index] = components.map((c) => c?.snapshot?.capture());
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** @param {number} index */
|
|
176
|
+
function restore_snapshot(index) {
|
|
177
|
+
snapshots[index]?.forEach((value, i) => {
|
|
178
|
+
components[i]?.snapshot?.restore(value);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
161
182
|
/**
|
|
162
183
|
* @param {string | URL} url
|
|
163
184
|
* @param {{ noScroll?: boolean; replaceState?: boolean; keepFocus?: boolean; state?: any; invalidateAll?: boolean }} opts
|
|
@@ -238,11 +259,20 @@ export function create_client({ target }) {
|
|
|
238
259
|
* @param {import('./types').NavigationIntent | undefined} intent
|
|
239
260
|
* @param {URL} url
|
|
240
261
|
* @param {string[]} redirect_chain
|
|
262
|
+
* @param {number} [previous_history_index]
|
|
241
263
|
* @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null}} [opts]
|
|
242
264
|
* @param {{}} [nav_token] To distinguish between different navigation events and determine the latest. Needed for example for redirects to keep the original token
|
|
243
265
|
* @param {() => void} [callback]
|
|
244
266
|
*/
|
|
245
|
-
async function update(
|
|
267
|
+
async function update(
|
|
268
|
+
intent,
|
|
269
|
+
url,
|
|
270
|
+
redirect_chain,
|
|
271
|
+
previous_history_index,
|
|
272
|
+
opts,
|
|
273
|
+
nav_token = {},
|
|
274
|
+
callback
|
|
275
|
+
) {
|
|
246
276
|
token = nav_token;
|
|
247
277
|
let navigation_result = intent && (await load_route(intent));
|
|
248
278
|
|
|
@@ -301,11 +331,28 @@ export function create_client({ target }) {
|
|
|
301
331
|
|
|
302
332
|
updating = true;
|
|
303
333
|
|
|
334
|
+
// `previous_history_index` will be undefined for invalidation
|
|
335
|
+
if (previous_history_index) {
|
|
336
|
+
update_scroll_positions(previous_history_index);
|
|
337
|
+
capture_snapshot(previous_history_index);
|
|
338
|
+
}
|
|
339
|
+
|
|
304
340
|
if (opts && opts.details) {
|
|
305
341
|
const { details } = opts;
|
|
306
342
|
const change = details.replaceState ? 0 : 1;
|
|
307
343
|
details.state[INDEX_KEY] = current_history_index += change;
|
|
308
344
|
history[details.replaceState ? 'replaceState' : 'pushState'](details.state, '', url);
|
|
345
|
+
|
|
346
|
+
if (!details.replaceState) {
|
|
347
|
+
// if we navigated back, then pushed a new state, we can
|
|
348
|
+
// release memory by pruning the scroll/snapshot lookup
|
|
349
|
+
let i = current_history_index + 1;
|
|
350
|
+
while (snapshots[i] || scroll_positions[i]) {
|
|
351
|
+
delete snapshots[i];
|
|
352
|
+
delete scroll_positions[i];
|
|
353
|
+
i += 1;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
309
356
|
}
|
|
310
357
|
|
|
311
358
|
// reset preload synchronously after the history state has been set to avoid race conditions
|
|
@@ -384,10 +431,12 @@ export function create_client({ target }) {
|
|
|
384
431
|
|
|
385
432
|
root = new Root({
|
|
386
433
|
target,
|
|
387
|
-
props: { ...result.props, stores },
|
|
434
|
+
props: { ...result.props, stores, components },
|
|
388
435
|
hydrate: true
|
|
389
436
|
});
|
|
390
437
|
|
|
438
|
+
restore_snapshot(current_history_index);
|
|
439
|
+
|
|
391
440
|
/** @type {import('types').AfterNavigate} */
|
|
392
441
|
const navigation = {
|
|
393
442
|
from: null,
|
|
@@ -445,7 +494,7 @@ export function create_client({ target }) {
|
|
|
445
494
|
},
|
|
446
495
|
props: {
|
|
447
496
|
// @ts-ignore Somehow it's getting SvelteComponent and SvelteComponentDev mixed up
|
|
448
|
-
|
|
497
|
+
constructors: compact(branch).map((branch_node) => branch_node.node.component)
|
|
449
498
|
}
|
|
450
499
|
};
|
|
451
500
|
|
|
@@ -804,7 +853,7 @@ export function create_client({ target }) {
|
|
|
804
853
|
// server_data_node is undefined if it wasn't reloaded from the server;
|
|
805
854
|
// and if current loader uses server data, we want to reuse previous data.
|
|
806
855
|
server_data_node === undefined && loader[0] ? { type: 'skip' } : server_data_node ?? null,
|
|
807
|
-
previous?.server
|
|
856
|
+
loader[0] ? previous?.server : undefined
|
|
808
857
|
)
|
|
809
858
|
});
|
|
810
859
|
});
|
|
@@ -1091,7 +1140,8 @@ export function create_client({ target }) {
|
|
|
1091
1140
|
return;
|
|
1092
1141
|
}
|
|
1093
1142
|
|
|
1094
|
-
|
|
1143
|
+
// store this before calling `accepted()`, which may change the index
|
|
1144
|
+
const previous_history_index = current_history_index;
|
|
1095
1145
|
|
|
1096
1146
|
accepted();
|
|
1097
1147
|
|
|
@@ -1105,6 +1155,7 @@ export function create_client({ target }) {
|
|
|
1105
1155
|
intent,
|
|
1106
1156
|
url,
|
|
1107
1157
|
redirect_chain,
|
|
1158
|
+
previous_history_index,
|
|
1108
1159
|
{
|
|
1109
1160
|
scroll,
|
|
1110
1161
|
keepfocus,
|
|
@@ -1385,12 +1436,10 @@ export function create_client({ target }) {
|
|
|
1385
1436
|
addEventListener('visibilitychange', () => {
|
|
1386
1437
|
if (document.visibilityState === 'hidden') {
|
|
1387
1438
|
update_scroll_positions(current_history_index);
|
|
1439
|
+
storage.set(SCROLL_KEY, scroll_positions);
|
|
1388
1440
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
} catch {
|
|
1392
|
-
// do nothing
|
|
1393
|
-
}
|
|
1441
|
+
capture_snapshot(current_history_index);
|
|
1442
|
+
storage.set(SNAPSHOT_KEY, snapshots);
|
|
1394
1443
|
}
|
|
1395
1444
|
});
|
|
1396
1445
|
|
|
@@ -1537,7 +1586,7 @@ export function create_client({ target }) {
|
|
|
1537
1586
|
});
|
|
1538
1587
|
});
|
|
1539
1588
|
|
|
1540
|
-
addEventListener('popstate', (event) => {
|
|
1589
|
+
addEventListener('popstate', async (event) => {
|
|
1541
1590
|
if (event.state?.[INDEX_KEY]) {
|
|
1542
1591
|
// if a popstate-driven navigation is cancelled, we need to counteract it
|
|
1543
1592
|
// with history.go, which means we end up back here, hence this check
|
|
@@ -1555,8 +1604,9 @@ export function create_client({ target }) {
|
|
|
1555
1604
|
}
|
|
1556
1605
|
|
|
1557
1606
|
const delta = event.state[INDEX_KEY] - current_history_index;
|
|
1607
|
+
let blocked = false;
|
|
1558
1608
|
|
|
1559
|
-
navigate({
|
|
1609
|
+
await navigate({
|
|
1560
1610
|
url: new URL(location.href),
|
|
1561
1611
|
scroll,
|
|
1562
1612
|
keepfocus: false,
|
|
@@ -1567,10 +1617,15 @@ export function create_client({ target }) {
|
|
|
1567
1617
|
},
|
|
1568
1618
|
blocked: () => {
|
|
1569
1619
|
history.go(-delta);
|
|
1620
|
+
blocked = true;
|
|
1570
1621
|
},
|
|
1571
1622
|
type: 'popstate',
|
|
1572
1623
|
delta
|
|
1573
1624
|
});
|
|
1625
|
+
|
|
1626
|
+
if (!blocked) {
|
|
1627
|
+
restore_snapshot(current_history_index);
|
|
1628
|
+
}
|
|
1574
1629
|
}
|
|
1575
1630
|
});
|
|
1576
1631
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a value from `sessionStorage`
|
|
3
|
+
* @param {string} key
|
|
4
|
+
*/
|
|
5
|
+
export function get(key) {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(sessionStorage[key]);
|
|
8
|
+
} catch {
|
|
9
|
+
// do nothing
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Write a value to `sessionStorage`
|
|
15
|
+
* @param {string} key
|
|
16
|
+
* @param {any} value
|
|
17
|
+
*/
|
|
18
|
+
export function set(key, value) {
|
|
19
|
+
const json = JSON.stringify(value);
|
|
20
|
+
try {
|
|
21
|
+
sessionStorage[key] = json;
|
|
22
|
+
} catch {
|
|
23
|
+
// do nothing
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -90,7 +90,7 @@ export async function render_response({
|
|
|
90
90
|
navigating: writable(null),
|
|
91
91
|
updated
|
|
92
92
|
},
|
|
93
|
-
|
|
93
|
+
constructors: await Promise.all(branch.map(({ node }) => node.component())),
|
|
94
94
|
form: form_value
|
|
95
95
|
};
|
|
96
96
|
|
package/src/utils/exports.js
CHANGED
|
@@ -31,7 +31,8 @@ export const validate_common_exports = validator([
|
|
|
31
31
|
'prerender',
|
|
32
32
|
'csr',
|
|
33
33
|
'ssr',
|
|
34
|
-
'trailingSlash'
|
|
34
|
+
'trailingSlash',
|
|
35
|
+
'config'
|
|
35
36
|
]);
|
|
36
37
|
|
|
37
38
|
export const validate_page_server_exports = validator([
|
|
@@ -40,7 +41,8 @@ export const validate_page_server_exports = validator([
|
|
|
40
41
|
'csr',
|
|
41
42
|
'ssr',
|
|
42
43
|
'actions',
|
|
43
|
-
'trailingSlash'
|
|
44
|
+
'trailingSlash',
|
|
45
|
+
'config'
|
|
44
46
|
]);
|
|
45
47
|
|
|
46
48
|
export const validate_server_exports = validator([
|
|
@@ -50,5 +52,6 @@ export const validate_server_exports = validator([
|
|
|
50
52
|
'PUT',
|
|
51
53
|
'DELETE',
|
|
52
54
|
'prerender',
|
|
53
|
-
'trailingSlash'
|
|
55
|
+
'trailingSlash',
|
|
56
|
+
'config'
|
|
54
57
|
]);
|
package/types/index.d.ts
CHANGED
|
@@ -7,13 +7,15 @@ import { CompileOptions } from 'svelte/types/compiler/interfaces';
|
|
|
7
7
|
import {
|
|
8
8
|
AdapterEntry,
|
|
9
9
|
CspDirectives,
|
|
10
|
+
HttpMethod,
|
|
10
11
|
Logger,
|
|
11
12
|
MaybePromise,
|
|
12
13
|
Prerendered,
|
|
13
14
|
PrerenderHttpErrorHandlerValue,
|
|
14
15
|
PrerenderMissingIdHandlerValue,
|
|
16
|
+
PrerenderOption,
|
|
15
17
|
RequestOptions,
|
|
16
|
-
|
|
18
|
+
RouteSegment,
|
|
17
19
|
UniqueInterface
|
|
18
20
|
} from './private.js';
|
|
19
21
|
import { SSRNodeLoader, SSRRoute, ValidatedConfig } from './internal.js';
|
|
@@ -85,10 +87,13 @@ export interface Builder {
|
|
|
85
87
|
config: ValidatedConfig;
|
|
86
88
|
/** Information about prerendered pages and assets, if any. */
|
|
87
89
|
prerendered: Prerendered;
|
|
90
|
+
/** An array of dynamic (not prerendered) routes */
|
|
91
|
+
routes: RouteDefinition[];
|
|
88
92
|
|
|
89
93
|
/**
|
|
90
94
|
* Create separate functions that map to one or more routes of your app.
|
|
91
95
|
* @param fn A function that groups a set of routes into an entry point
|
|
96
|
+
* @deprecated Use `builder.routes` instead
|
|
92
97
|
*/
|
|
93
98
|
createEntries(fn: (route: RouteDefinition) => AdapterEntry): Promise<void>;
|
|
94
99
|
|
|
@@ -101,7 +106,7 @@ export interface Builder {
|
|
|
101
106
|
* Generate a server-side manifest to initialise the SvelteKit [server](https://kit.svelte.dev/docs/types#public-types-server) with.
|
|
102
107
|
* @param opts a relative path to the base directory of the app and optionally in which format (esm or cjs) the manifest should be generated
|
|
103
108
|
*/
|
|
104
|
-
generateManifest(opts: { relativePath: string }): string;
|
|
109
|
+
generateManifest(opts: { relativePath: string; routes?: RouteDefinition[] }): string;
|
|
105
110
|
|
|
106
111
|
/**
|
|
107
112
|
* Resolve a path to the `name` directory inside `outDir`, e.g. `/path/to/.svelte-kit/my-adapter`.
|
|
@@ -515,7 +520,23 @@ export interface KitConfig {
|
|
|
515
520
|
config?: (config: Record<string, any>) => Record<string, any> | void;
|
|
516
521
|
};
|
|
517
522
|
/**
|
|
518
|
-
* Client-side navigation can be buggy if you deploy a new version of your app while people are using it. If the code for the new page is already loaded, it may have stale content; if it isn't, the app's route manifest may point to a JavaScript file that no longer exists.
|
|
523
|
+
* Client-side navigation can be buggy if you deploy a new version of your app while people are using it. If the code for the new page is already loaded, it may have stale content; if it isn't, the app's route manifest may point to a JavaScript file that no longer exists.
|
|
524
|
+
* SvelteKit helps you solve this problem through version management.
|
|
525
|
+
* If SvelteKit encounters an error while loading the page and detects that a new version has been deployed (using the `name` specified here, which defaults to a timestamp of the build) it will fall back to traditional full-page navigation.
|
|
526
|
+
* Not all navigations will result in an error though, for example if the JavaScript for the next page is already loaded. If you still want to force a full-page navigation in these cases, use techniques such as setting the `pollInverval` and then using `beforeNavigate`:
|
|
527
|
+
* ```html
|
|
528
|
+
* /// +layout.svelte
|
|
529
|
+
* <script>
|
|
530
|
+
* import { beforeNavigate } from '$app/navigation';
|
|
531
|
+
* import { updated } from '$app/stores';
|
|
532
|
+
*
|
|
533
|
+
* beforeNavigate(({ willUnload, to }) => {
|
|
534
|
+
* if ($updated && !willUnload && to?.url) {
|
|
535
|
+
* location.href = to.route.url.href;
|
|
536
|
+
* }
|
|
537
|
+
* });
|
|
538
|
+
* </script>
|
|
539
|
+
* ```
|
|
519
540
|
*
|
|
520
541
|
* If you set `pollInterval` to a non-zero value, SvelteKit will poll for new versions in the background and set the value of the [`updated`](/docs/modules#$app-stores-updated) store to `true` when it detects one.
|
|
521
542
|
*/
|
|
@@ -959,6 +980,15 @@ export interface ResolveOptions {
|
|
|
959
980
|
preload?(input: { type: 'font' | 'css' | 'js' | 'asset'; path: string }): boolean;
|
|
960
981
|
}
|
|
961
982
|
|
|
983
|
+
export interface RouteDefinition<Config = any> {
|
|
984
|
+
id: string;
|
|
985
|
+
pattern: RegExp;
|
|
986
|
+
prerender: PrerenderOption;
|
|
987
|
+
segments: RouteSegment[];
|
|
988
|
+
methods: HttpMethod[];
|
|
989
|
+
config: Config;
|
|
990
|
+
}
|
|
991
|
+
|
|
962
992
|
export class Server {
|
|
963
993
|
constructor(manifest: SSRManifest);
|
|
964
994
|
init(options: ServerInitOptions): Promise<void>;
|
|
@@ -1077,6 +1107,13 @@ export type Actions<
|
|
|
1077
1107
|
|
|
1078
1108
|
/**
|
|
1079
1109
|
* When calling a form action via fetch, the response will be one of these shapes.
|
|
1110
|
+
* ```svelte
|
|
1111
|
+
* <form method="post" use:enhance={() => {
|
|
1112
|
+
* return ({ result }) => {
|
|
1113
|
+
* // result is of type ActionResult
|
|
1114
|
+
* };
|
|
1115
|
+
* }}
|
|
1116
|
+
* ```
|
|
1080
1117
|
*/
|
|
1081
1118
|
export type ActionResult<
|
|
1082
1119
|
Success extends Record<string, unknown> | undefined = Record<string, any>,
|
|
@@ -1192,3 +1229,11 @@ export interface SubmitFunction<
|
|
|
1192
1229
|
}) => void)
|
|
1193
1230
|
>;
|
|
1194
1231
|
}
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* The type of `export const snapshot` exported from a page or layout component.
|
|
1235
|
+
*/
|
|
1236
|
+
export interface Snapshot<T = any> {
|
|
1237
|
+
capture: () => T;
|
|
1238
|
+
restore: (snapshot: T) => void;
|
|
1239
|
+
}
|
package/types/internal.d.ts
CHANGED
|
@@ -239,6 +239,7 @@ export interface ServerMetadata {
|
|
|
239
239
|
{
|
|
240
240
|
prerender: PrerenderOption | undefined;
|
|
241
241
|
methods: HttpMethod[];
|
|
242
|
+
config: any;
|
|
242
243
|
}
|
|
243
244
|
>;
|
|
244
245
|
}
|
|
@@ -279,6 +280,7 @@ export interface SSRNode {
|
|
|
279
280
|
ssr?: boolean;
|
|
280
281
|
csr?: boolean;
|
|
281
282
|
trailingSlash?: TrailingSlash;
|
|
283
|
+
config?: any;
|
|
282
284
|
};
|
|
283
285
|
|
|
284
286
|
server: {
|
|
@@ -288,6 +290,7 @@ export interface SSRNode {
|
|
|
288
290
|
csr?: boolean;
|
|
289
291
|
trailingSlash?: TrailingSlash;
|
|
290
292
|
actions?: Actions;
|
|
293
|
+
config?: any;
|
|
291
294
|
};
|
|
292
295
|
|
|
293
296
|
// store this in dev so we can print serialization errors
|
|
@@ -331,6 +334,7 @@ export interface PageNodeIndexes {
|
|
|
331
334
|
export type SSREndpoint = Partial<Record<HttpMethod, RequestHandler>> & {
|
|
332
335
|
prerender?: PrerenderOption;
|
|
333
336
|
trailingSlash?: TrailingSlash;
|
|
337
|
+
config?: any;
|
|
334
338
|
};
|
|
335
339
|
|
|
336
340
|
export interface SSRRoute {
|
package/types/private.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// but which cannot be imported from `@sveltejs/kit`. Care should
|
|
3
3
|
// be taken to avoid breaking changes when editing this file
|
|
4
4
|
|
|
5
|
+
import { RouteDefinition } from './index.js';
|
|
6
|
+
|
|
5
7
|
export interface AdapterEntry {
|
|
6
8
|
/**
|
|
7
9
|
* A string that uniquely identifies an HTTP service (e.g. serverless function) and is used for deduplication.
|
|
@@ -12,8 +14,11 @@ export interface AdapterEntry {
|
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* A function that compares the candidate route with the current route to determine
|
|
15
|
-
* if it should be
|
|
16
|
-
*
|
|
17
|
+
* if it should be grouped with the current route.
|
|
18
|
+
*
|
|
19
|
+
* Use cases:
|
|
20
|
+
* - Fallback pages: `/foo/[c]` is a fallback for `/foo/a-[b]`, and `/[...catchall]` is a fallback for all routes
|
|
21
|
+
* - Grouping routes that share a common `config`: `/foo` should be deployed to the edge, `/bar` and `/baz` should be deployed to a serverless function
|
|
17
22
|
*/
|
|
18
23
|
filter(route: RouteDefinition): boolean;
|
|
19
24
|
|
|
@@ -212,13 +217,6 @@ export interface RequestOptions {
|
|
|
212
217
|
platform?: App.Platform;
|
|
213
218
|
}
|
|
214
219
|
|
|
215
|
-
export interface RouteDefinition {
|
|
216
|
-
id: string;
|
|
217
|
-
pattern: RegExp;
|
|
218
|
-
segments: RouteSegment[];
|
|
219
|
-
methods: HttpMethod[];
|
|
220
|
-
}
|
|
221
|
-
|
|
222
220
|
export interface RouteSegment {
|
|
223
221
|
content: string;
|
|
224
222
|
dynamic: boolean;
|