@sveltejs/kit 1.0.0-next.572 → 1.0.0-next.573

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.572",
3
+ "version": "1.0.0-next.573",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -28,7 +28,7 @@
28
28
  "@types/connect": "^3.4.35",
29
29
  "@types/marked": "^4.0.7",
30
30
  "@types/mime": "^3.0.1",
31
- "@types/node": "^16.18.3",
31
+ "@types/node": "^16.18.6",
32
32
  "@types/sade": "^1.7.4",
33
33
  "@types/set-cookie-parser": "^2.4.2",
34
34
  "marked": "^4.2.3",
@@ -11,6 +11,11 @@ import { logger } from '../utils.js';
11
11
  import { load_config } from '../config/index.js';
12
12
  import { get_route_segments } from '../../utils/routing.js';
13
13
  import { get_option } from '../../runtime/server/utils.js';
14
+ import {
15
+ validate_common_exports,
16
+ validate_page_server_exports,
17
+ validate_server_exports
18
+ } from '../../utils/exports.js';
14
19
 
15
20
  const [, , client_out_dir, manifest_path, results_path, verbose, env] = process.argv;
16
21
 
@@ -357,34 +362,46 @@ export async function prerender() {
357
362
  }
358
363
 
359
364
  for (const route of manifest._.routes) {
360
- try {
361
- if (route.endpoint) {
362
- const mod = await route.endpoint();
363
- if (mod.prerender !== undefined) {
364
- if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
365
- throw new Error(
366
- `Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})`
367
- );
368
- }
369
-
370
- prerender_map.set(route.id, mod.prerender);
365
+ if (route.endpoint) {
366
+ const mod = await route.endpoint();
367
+ if (mod.prerender !== undefined) {
368
+ validate_server_exports(mod, route.id);
369
+
370
+ if (mod.prerender && (mod.POST || mod.PATCH || mod.PUT || mod.DELETE)) {
371
+ throw new Error(
372
+ `Cannot prerender a +server file with POST, PATCH, PUT, or DELETE (${route.id})`
373
+ );
371
374
  }
375
+
376
+ prerender_map.set(route.id, mod.prerender);
372
377
  }
378
+ }
379
+
380
+ if (route.page) {
381
+ const nodes = await Promise.all(
382
+ [...route.page.layouts, route.page.leaf].map((n) => {
383
+ if (n !== undefined) return manifest._.nodes[n]();
384
+ })
385
+ );
386
+
387
+ const layouts = nodes.slice(0, -1);
388
+ const page = nodes.at(-1);
373
389
 
374
- if (route.page) {
375
- const nodes = await Promise.all(
376
- [...route.page.layouts, route.page.leaf].map((n) => {
377
- if (n !== undefined) return manifest._.nodes[n]();
378
- })
379
- );
380
- const prerender = get_option(nodes, 'prerender') ?? false;
390
+ for (const layout of layouts) {
391
+ if (layout) {
392
+ validate_common_exports(layout.server, route.id);
393
+ validate_common_exports(layout.shared, route.id);
394
+ }
395
+ }
381
396
 
382
- prerender_map.set(route.id, prerender);
397
+ if (page) {
398
+ validate_page_server_exports(page.server, route.id);
399
+ validate_common_exports(page.shared, route.id);
383
400
  }
384
- } catch (e) {
385
- // We failed to import the module, which indicates it can't be prerendered
386
- // TODO should we catch these? It's almost certainly a bug in the app
387
- console.error(e);
401
+
402
+ const prerender = get_option(nodes, 'prerender') ?? false;
403
+
404
+ prerender_map.set(route.id, prerender);
388
405
  }
389
406
  }
390
407
 
@@ -486,10 +486,11 @@ function prevent_conflicts(routes) {
486
486
  const matcher = split[i];
487
487
  const next = split[i + 1];
488
488
 
489
- permutations = [
490
- ...permutations.map((x) => x + next),
491
- ...permutations.map((x) => x + `<${matcher}>${next}`)
492
- ];
489
+ permutations = permutations.reduce((a, b) => {
490
+ a.push(b + next);
491
+ if (!(matcher === '*' && b.endsWith('//'))) a.push(b + `<${matcher}>${next}`);
492
+ return a;
493
+ }, /** @type {string[]} */ ([]));
493
494
  }
494
495
 
495
496
  for (const permutation of permutations) {
@@ -401,7 +401,7 @@ function process_node(node, outdir, is_page, proxies, all_pages_have_load = true
401
401
  ? `./proxy${replace_ext_with_js(basename)}`
402
402
  : path_to_original(outdir, node.server);
403
403
 
404
- type = `Expand<Kit.AwaitedActions<typeof import('${from}').actions>> | undefined`;
404
+ type = `Expand<Kit.AwaitedActions<typeof import('${from}').actions>> | null`;
405
405
  }
406
406
  }
407
407
  exports.push(`export type ActionData = ${type};`);
@@ -31,6 +31,9 @@ export async function dev(vite, vite_config, svelte_config) {
31
31
  // @ts-expect-error
32
32
  globalThis.__SVELTEKIT_BROWSER__ = false;
33
33
 
34
+ // @ts-expect-error
35
+ globalThis.__SVELTEKIT_DEV__ = true;
36
+
34
37
  sync.init(svelte_config, vite_config.mode);
35
38
 
36
39
  /** @type {import('types').Respond} */
@@ -102,6 +105,7 @@ export async function dev(vite, vite_config, svelte_config) {
102
105
  module_nodes.push(module_node);
103
106
 
104
107
  result.shared = module;
108
+ result.shared_id = node.shared;
105
109
  }
106
110
 
107
111
  if (node.server) {
@@ -163,7 +167,8 @@ export async function dev(vite, vite_config, svelte_config) {
163
167
  const url = path.resolve(cwd, endpoint.file);
164
168
  return await vite.ssrLoadModule(url);
165
169
  }
166
- : null
170
+ : null,
171
+ endpoint_id: endpoint?.file
167
172
  };
168
173
  })
169
174
  ),
@@ -30,6 +30,7 @@ import { stores } from './singletons.js';
30
30
  import { unwrap_promises } from '../../utils/promises.js';
31
31
  import * as devalue from 'devalue';
32
32
  import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY } from './constants.js';
33
+ import { validate_common_exports } from '../../utils/exports.js';
33
34
 
34
35
  const routes = parse(nodes, server_loads, dictionary, matchers);
35
36
 
@@ -497,7 +498,7 @@ export function create_client({ target, base }) {
497
498
  route,
498
499
  status,
499
500
  url: new URL(url),
500
- form,
501
+ form: form ?? null,
501
502
  // The whole page store is updated, but this way the object reference stays the same
502
503
  data: data_changed ? data : page.data
503
504
  };
@@ -558,6 +559,10 @@ export function create_client({ target, base }) {
558
559
 
559
560
  const node = await loader();
560
561
 
562
+ if (__SVELTEKIT_DEV__) {
563
+ validate_common_exports(node.shared);
564
+ }
565
+
561
566
  if (node.shared?.load) {
562
567
  /** @param {string[]} deps */
563
568
  function depends(...deps) {
@@ -17,6 +17,12 @@ import { redirect_json_response, render_data } from './data/index.js';
17
17
  import { add_cookies_to_headers, get_cookies } from './cookie.js';
18
18
  import { create_fetch } from './fetch.js';
19
19
  import { Redirect } from '../control.js';
20
+ import {
21
+ validate_common_exports,
22
+ validate_page_server_exports,
23
+ validate_server_exports
24
+ } from '../../utils/exports.js';
25
+ import { error, json } from '../../exports/index.js';
20
26
 
21
27
  /* global __SVELTEKIT_ADAPTER_NAME__ */
22
28
 
@@ -40,9 +46,11 @@ export async function respond(request, options, state) {
40
46
  is_form_content_type(request);
41
47
 
42
48
  if (forbidden) {
43
- return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
44
- status: 403
45
- });
49
+ const csrf_error = error(403, `Cross-site ${request.method} form submissions are forbidden`);
50
+ if (request.headers.get('accept') === 'application/json') {
51
+ return json(csrf_error.body, { status: csrf_error.status });
52
+ }
53
+ return new Response(csrf_error.body.message, { status: csrf_error.status });
46
54
  }
47
55
  }
48
56
 
@@ -185,10 +193,31 @@ export async function respond(request, options, state) {
185
193
  options.manifest._.nodes[route.page.leaf]()
186
194
  ]);
187
195
 
196
+ if (__SVELTEKIT_DEV__) {
197
+ const layouts = nodes.slice(0, -1);
198
+ const page = nodes.at(-1);
199
+
200
+ for (const layout of layouts) {
201
+ if (layout) {
202
+ validate_common_exports(layout.server, /** @type {string} */ (layout.server_id));
203
+ validate_common_exports(layout.shared, /** @type {string} */ (layout.shared_id));
204
+ }
205
+ }
206
+
207
+ if (page) {
208
+ validate_page_server_exports(page.server, /** @type {string} */ (page.server_id));
209
+ validate_common_exports(page.shared, /** @type {string} */ (page.shared_id));
210
+ }
211
+ }
212
+
188
213
  trailing_slash = get_option(nodes, 'trailingSlash');
189
214
  } else if (route.endpoint) {
190
215
  const node = await route.endpoint();
191
216
  trailing_slash = node.trailingSlash;
217
+
218
+ if (__SVELTEKIT_DEV__) {
219
+ validate_server_exports(node, /** @type {string} */ (route.endpoint_id));
220
+ }
192
221
  }
193
222
 
194
223
  const normalized = normalize_path(url.pathname, trailing_slash ?? 'never');
@@ -18,22 +18,31 @@ export function is_action_json_request(event) {
18
18
  /**
19
19
  * @param {import('types').RequestEvent} event
20
20
  * @param {import('types').SSROptions} options
21
- * @param {import('types').SSRNode['server']} server
21
+ * @param {import('types').SSRNode['server'] | undefined} server
22
22
  */
23
23
  export async function handle_action_json_request(event, options, server) {
24
- const actions = server.actions;
24
+ const actions = server?.actions;
25
25
 
26
26
  if (!actions) {
27
- maybe_throw_migration_error(server);
27
+ if (server) {
28
+ maybe_throw_migration_error(server);
29
+ }
28
30
  // TODO should this be a different error altogether?
29
- return new Response('POST method not allowed. No actions exist for this page', {
30
- status: 405,
31
- headers: {
32
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
33
- // "The server must generate an Allow header field in a 405 status code response"
34
- allow: 'GET'
31
+ const no_actions_error = error(405, 'POST method not allowed. No actions exist for this page');
32
+ return action_json(
33
+ {
34
+ type: 'error',
35
+ error: await handle_error_and_jsonify(event, options, no_actions_error)
36
+ },
37
+ {
38
+ status: no_actions_error.status,
39
+ headers: {
40
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
41
+ // "The server must generate an Allow header field in a 405 status code response"
42
+ allow: 'GET'
43
+ }
35
44
  }
36
- });
45
+ );
37
46
  }
38
47
 
39
48
  check_named_default_separate(actions);
@@ -40,9 +40,7 @@ export async function render_page(event, route, page, options, state, resolve_op
40
40
 
41
41
  if (is_action_json_request(event)) {
42
42
  const node = await options.manifest._.nodes[page.leaf]();
43
- if (node.server) {
44
- return handle_action_json_request(event, options, node.server);
45
- }
43
+ return handle_action_json_request(event, options, node?.server);
46
44
  }
47
45
 
48
46
  try {
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @param {string[]} expected
3
+ */
4
+ function validator(expected) {
5
+ const set = new Set(expected);
6
+
7
+ /**
8
+ * @param {any} module
9
+ * @param {string} [route_id]
10
+ */
11
+ function validate(module, route_id) {
12
+ if (!module) return;
13
+
14
+ for (const key in module) {
15
+ if (key[0] !== '_' && !set.has(key)) {
16
+ const valid = expected.join(', ');
17
+ throw new Error(
18
+ `Invalid export '${key}'${
19
+ route_id ? ` in ${route_id}` : ''
20
+ } (valid exports are ${valid}, or anything with a '_' prefix)`
21
+ );
22
+ }
23
+ }
24
+ }
25
+
26
+ return validate;
27
+ }
28
+
29
+ export const validate_common_exports = validator([
30
+ 'load',
31
+ 'prerender',
32
+ 'csr',
33
+ 'ssr',
34
+ 'trailingSlash'
35
+ ]);
36
+
37
+ export const validate_page_server_exports = validator([
38
+ 'load',
39
+ 'prerender',
40
+ 'csr',
41
+ 'ssr',
42
+ 'actions',
43
+ 'trailingSlash'
44
+ ]);
45
+
46
+ export const validate_server_exports = validator([
47
+ 'GET',
48
+ 'POST',
49
+ 'PATCH',
50
+ 'PUT',
51
+ 'DELETE',
52
+ 'prerender',
53
+ 'trailingSlash'
54
+ ]);
@@ -288,6 +288,7 @@ export interface SSRNode {
288
288
 
289
289
  // store this in dev so we can print serialization errors
290
290
  server_id?: string;
291
+ shared_id?: string;
291
292
  }
292
293
 
293
294
  export type SSRNodeLoader = () => Promise<SSRNode>;
@@ -346,6 +347,7 @@ export interface SSRRoute {
346
347
  params: RouteParam[];
347
348
  page: PageNodeIndexes | null;
348
349
  endpoint: (() => Promise<SSREndpoint>) | null;
350
+ endpoint_id?: string;
349
351
  }
350
352
 
351
353
  export interface SSRState {