@sveltejs/kit 1.0.0-next.435 → 1.0.0-next.438

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.435",
3
+ "version": "1.0.0-next.438",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -39,7 +39,7 @@
39
39
  "tiny-glob": "^0.2.9",
40
40
  "typescript": "^4.7.4",
41
41
  "uvu": "^0.5.3",
42
- "vite": "^3.0.8"
42
+ "vite": "^3.0.9"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "svelte": "^3.44.0",
@@ -5,6 +5,7 @@ import { pipeline } from 'stream';
5
5
  import { promisify } from 'util';
6
6
  import { copy, rimraf, mkdirp } from '../../utils/filesystem.js';
7
7
  import { generate_manifest } from '../generate_manifest/index.js';
8
+ import { get_path } from '../../utils/routing.js';
8
9
 
9
10
  /**
10
11
  * Creates the Builder which is passed to adapters for building the application.
@@ -23,7 +24,7 @@ export function create_builder({ config, build_data, prerendered, log }) {
23
24
  /** @param {import('types').RouteData} route */
24
25
  // TODO routes should come pre-filtered
25
26
  function not_prerendered(route) {
26
- const path = route.page && !route.id.includes('[') && `/${route.id}`;
27
+ const path = route.page && get_path(route.id);
27
28
  if (path) {
28
29
  return !prerendered_paths.has(path) && !prerendered_paths.has(path + '/');
29
30
  }
@@ -9,6 +9,8 @@ import { crawl } from './crawl.js';
9
9
  import { escape_html_attr } from '../../utils/escape.js';
10
10
  import { logger } from '../utils.js';
11
11
  import { load_config } from '../config/index.js';
12
+ import { compact } from '../../utils/array.js';
13
+ import { get_path } from '../../utils/routing.js';
12
14
 
13
15
  /**
14
16
  * @typedef {import('types').PrerenderErrorHandler} PrerenderErrorHandler
@@ -341,11 +343,10 @@ export async function prerender() {
341
343
  if (config.prerender.enabled) {
342
344
  for (const entry of config.prerender.entries) {
343
345
  if (entry === '*') {
344
- /** @type {import('types').ManifestData} */
345
- const { routes } = (await import(pathToFileURL(manifest_path).href)).manifest._;
346
- const entries = routes
347
- .map((route) => (route.page && !route.id.includes('[') ? `/${route.id}` : ''))
348
- .filter(Boolean);
346
+ /** @type {import('types').SSRManifest} */
347
+ const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
348
+ const { routes } = manifest._;
349
+ const entries = compact(routes.map((route) => route.page && get_path(route.id)));
349
350
 
350
351
  for (const entry of entries) {
351
352
  enqueue(null, config.paths.base + entry); // TODO can we pre-normalize these?
@@ -82,6 +82,7 @@ function create_matchers(config, cwd) {
82
82
  * @param {string} fallback
83
83
  */
84
84
  function create_routes_and_nodes(cwd, config, fallback) {
85
+ /** @type {Map<string, import('types').RouteData>} */
85
86
  const route_map = new Map();
86
87
 
87
88
  /** @type {Map<string, import('./types').Part[][]>} */
@@ -1,6 +1,6 @@
1
1
  import { onMount, tick } from 'svelte';
2
2
  import { normalize_error } from '../../utils/error.js';
3
- import { LoadURL, decode_params, normalize_path } from '../../utils/url.js';
3
+ import { make_trackable, decode_params, normalize_path } from '../../utils/url.js';
4
4
  import { find_anchor, get_base_uri, get_href, scroll_state } from './utils.js';
5
5
  import { lock_fetch, unlock_fetch, initial_fetch, native_fetch } from './fetcher.js';
6
6
  import { parse } from './parse.js';
@@ -510,17 +510,14 @@ export function create_client({ target, base, trailing_slash }) {
510
510
  });
511
511
  }
512
512
 
513
- const load_url = new LoadURL(url);
514
-
515
513
  /** @type {import('types').LoadEvent} */
516
514
  const load_input = {
517
515
  routeId,
518
516
  params: uses_params,
519
517
  data: server_data_node?.data ?? null,
520
- get url() {
518
+ url: make_trackable(url, () => {
521
519
  uses.url = true;
522
- return load_url;
523
- },
520
+ }),
524
521
  async fetch(resource, init) {
525
522
  let requested;
526
523
 
@@ -4,7 +4,7 @@ import { render_response } from './page/render.js';
4
4
  import { respond_with_error } from './page/respond_with_error.js';
5
5
  import { coalesce_to_error, normalize_error } from '../../utils/error.js';
6
6
  import { serialize_error, GENERIC_ERROR, error_to_pojo } from './utils.js';
7
- import { decode_params, normalize_path } from '../../utils/url.js';
7
+ import { decode_params, disable_search, normalize_path } from '../../utils/url.js';
8
8
  import { exec } from '../../utils/routing.js';
9
9
  import { negotiate } from '../../utils/http.js';
10
10
  import { HttpError, Redirect } from '../../index/private.js';
@@ -122,21 +122,17 @@ export async function respond(request, options, state) {
122
122
  /** @type {string[]} */
123
123
  const cookies = [];
124
124
 
125
+ if (state.prerendering) disable_search(url);
126
+
125
127
  /** @type {import('types').RequestEvent} */
126
128
  const event = {
127
- get clientAddress() {
128
- if (!state.getClientAddress) {
129
+ getClientAddress:
130
+ state.getClientAddress ||
131
+ (() => {
129
132
  throw new Error(
130
133
  `${__SVELTEKIT_ADAPTER_NAME__} does not specify getClientAddress. Please raise an issue`
131
134
  );
132
- }
133
-
134
- Object.defineProperty(event, 'clientAddress', {
135
- value: state.getClientAddress()
136
- });
137
-
138
- return event.clientAddress;
139
- },
135
+ }),
140
136
  locals: {},
141
137
  params,
142
138
  platform: state.platform,
@@ -195,6 +191,7 @@ export async function respond(request, options, state) {
195
191
  };
196
192
 
197
193
  Object.defineProperties(event, {
194
+ clientAddress: removed('clientAddress', 'getClientAddress'),
198
195
  method: removed('method', 'request.method', details),
199
196
  headers: removed('headers', 'request.headers', details),
200
197
  origin: removed('origin', 'url.origin'),
@@ -277,6 +274,7 @@ export async function respond(request, options, state) {
277
274
  return load_server_data({
278
275
  dev: options.dev,
279
276
  event,
277
+ state,
280
278
  node,
281
279
  parent: async () => {
282
280
  /** @type {Record<string, any>} */
@@ -104,6 +104,27 @@ export async function render_page(event, route, page, options, state, resolve_op
104
104
  const should_prerender_data = nodes.some((node) => node?.server);
105
105
  const data_pathname = `${event.url.pathname.replace(/\/$/, '')}/__data.json`;
106
106
 
107
+ // it's crucial that we do this before returning the non-SSR response, otherwise
108
+ // SvelteKit will erroneously believe that the path has been prerendered,
109
+ // causing functions to be omitted from the manifesst generated later
110
+ const should_prerender =
111
+ leaf_node.shared?.prerender ?? leaf_node.server?.prerender ?? options.prerender.default;
112
+ if (should_prerender) {
113
+ const mod = leaf_node.server;
114
+ if (mod && (mod.POST || mod.PUT || mod.DELETE || mod.PATCH)) {
115
+ throw new Error('Cannot prerender pages that have endpoints with mutative methods');
116
+ }
117
+ } else if (state.prerendering) {
118
+ // if the page isn't marked as prerenderable (or is explicitly
119
+ // marked NOT prerenderable, if `prerender.default` is `true`),
120
+ // then bail out at this point
121
+ if (!should_prerender) {
122
+ return new Response(undefined, {
123
+ status: 204
124
+ });
125
+ }
126
+ }
127
+
107
128
  if (!resolve_opts.ssr) {
108
129
  return await render_response({
109
130
  branch: [],
@@ -123,24 +144,6 @@ export async function render_page(event, route, page, options, state, resolve_op
123
144
  });
124
145
  }
125
146
 
126
- const should_prerender =
127
- leaf_node.shared?.prerender ?? leaf_node.server?.prerender ?? options.prerender.default;
128
- if (should_prerender) {
129
- const mod = leaf_node.server;
130
- if (mod && (mod.POST || mod.PUT || mod.DELETE || mod.PATCH)) {
131
- throw new Error('Cannot prerender pages that have endpoints with mutative methods');
132
- }
133
- } else if (state.prerendering) {
134
- // if the page isn't marked as prerenderable (or is explicitly
135
- // marked NOT prerenderable, if `prerender.default` is `true`),
136
- // then bail out at this point
137
- if (!should_prerender) {
138
- return new Response(undefined, {
139
- status: 204
140
- });
141
- }
142
- }
143
-
144
147
  /** @type {Array<Loaded | null>} */
145
148
  let branch = [];
146
149
 
@@ -165,6 +168,7 @@ export async function render_page(event, route, page, options, state, resolve_op
165
168
  return await load_server_data({
166
169
  dev: options.dev,
167
170
  event,
171
+ state,
168
172
  node,
169
173
  parent: async () => {
170
174
  /** @type {Record<string, any>} */
@@ -1,16 +1,17 @@
1
- import { LoadURL, PrerenderingURL } from '../../../utils/url.js';
1
+ import { disable_search, make_trackable } from '../../../utils/url.js';
2
2
 
3
3
  /**
4
4
  * Calls the user's `load` function.
5
5
  * @param {{
6
6
  * dev: boolean;
7
7
  * event: import('types').RequestEvent;
8
+ * state: import('types').SSRState;
8
9
  * node: import('types').SSRNode | undefined;
9
10
  * parent: () => Promise<Record<string, any>>;
10
11
  * }} opts
11
12
  * @returns {Promise<import('types').ServerDataNode | null>}
12
13
  */
13
- export async function load_server_data({ dev, event, node, parent }) {
14
+ export async function load_server_data({ dev, event, state, node, parent }) {
14
15
  if (!node?.server) return null;
15
16
 
16
17
  const uses = {
@@ -20,39 +21,34 @@ export async function load_server_data({ dev, event, node, parent }) {
20
21
  url: false
21
22
  };
22
23
 
23
- /** @param {string[]} deps */
24
- function depends(...deps) {
25
- for (const dep of deps) {
26
- const { href } = new URL(dep, event.url);
27
- uses.dependencies.add(href);
28
- }
29
- }
30
-
31
- const params = new Proxy(event.params, {
32
- get: (target, key) => {
33
- uses.params.add(key);
34
- return target[/** @type {string} */ (key)];
35
- }
24
+ const url = make_trackable(event.url, () => {
25
+ uses.url = true;
36
26
  });
37
27
 
28
+ if (state.prerendering) {
29
+ disable_search(url);
30
+ }
31
+
38
32
  const result = await node.server.load?.call(null, {
39
- // can't use destructuring here because it will always
40
- // invoke event.clientAddress, which breaks prerendering
41
- get clientAddress() {
42
- return event.clientAddress;
33
+ ...event,
34
+ /** @param {string[]} deps */
35
+ depends: (...deps) => {
36
+ for (const dep of deps) {
37
+ const { href } = new URL(dep, event.url);
38
+ uses.dependencies.add(href);
39
+ }
43
40
  },
44
- depends,
45
- locals: event.locals,
46
- params,
41
+ params: new Proxy(event.params, {
42
+ get: (target, key) => {
43
+ uses.params.add(key);
44
+ return target[/** @type {string} */ (key)];
45
+ }
46
+ }),
47
47
  parent: async () => {
48
48
  uses.parent = true;
49
49
  return parent();
50
50
  },
51
- platform: event.platform,
52
- request: event.request,
53
- routeId: event.routeId,
54
- setHeaders: event.setHeaders,
55
- url: event.url
51
+ url
56
52
  });
57
53
 
58
54
  const data = result ? await unwrap_promises(result) : null;
@@ -85,15 +81,15 @@ export async function load_server_data({ dev, event, node, parent }) {
85
81
  * }} opts
86
82
  * @returns {Promise<Record<string, any> | null>}
87
83
  */
88
- export async function load_data({ event, fetcher, node, parent, server_data_promise, state }) {
84
+ export async function load_data({ event, fetcher, node, parent, server_data_promise }) {
89
85
  const server_data_node = await server_data_promise;
90
86
 
91
87
  if (!node?.shared?.load) {
92
88
  return server_data_node?.data ?? null;
93
89
  }
94
90
 
95
- const load_input = {
96
- url: state.prerendering ? new PrerenderingURL(event.url) : new LoadURL(event.url),
91
+ const load_event = {
92
+ url: event.url,
97
93
  params: event.params,
98
94
  data: server_data_node?.data ?? null,
99
95
  routeId: event.routeId,
@@ -104,7 +100,7 @@ export async function load_data({ event, fetcher, node, parent, server_data_prom
104
100
  };
105
101
 
106
102
  // TODO remove this for 1.0
107
- Object.defineProperties(load_input, {
103
+ Object.defineProperties(load_event, {
108
104
  session: {
109
105
  get() {
110
106
  throw new Error(
@@ -115,7 +111,7 @@ export async function load_data({ event, fetcher, node, parent, server_data_prom
115
111
  }
116
112
  });
117
113
 
118
- const data = await node.shared.load.call(null, load_input);
114
+ const data = await node.shared.load.call(null, load_event);
119
115
 
120
116
  return data ? unwrap_promises(data) : null;
121
117
  }
@@ -5,7 +5,6 @@ import { hash } from '../../hash.js';
5
5
  import { render_json_payload_script } from '../../../utils/escape.js';
6
6
  import { s } from '../../../utils/misc.js';
7
7
  import { Csp } from './csp.js';
8
- import { PrerenderingURL } from '../../../utils/url.js';
9
8
  import { serialize_error } from '../utils.js';
10
9
  import { HttpError } from '../../../index/private.js';
11
10
 
@@ -100,7 +99,7 @@ export async function render_response({
100
99
  params: /** @type {Record<string, any>} */ (event.params),
101
100
  routeId: event.routeId,
102
101
  status,
103
- url: state.prerendering ? new PrerenderingURL(event.url) : event.url,
102
+ url: event.url,
104
103
  data
105
104
  };
106
105
 
@@ -37,6 +37,7 @@ export async function respond_with_error({ event, options, state, status, error,
37
37
  const server_data_promise = load_server_data({
38
38
  dev: options.dev,
39
39
  event,
40
+ state,
40
41
  node: default_layout,
41
42
  parent: async () => ({})
42
43
  });
@@ -96,6 +96,15 @@ export function affects_path(segment) {
96
96
  return !/^\([^)]+\)$/.test(segment);
97
97
  }
98
98
 
99
+ /**
100
+ * Turns a route ID into a path, if possible
101
+ * @param {string} id
102
+ */
103
+ export function get_path(id) {
104
+ if (id.includes('[')) return null;
105
+ return `/${id.split('/').filter(affects_path).join('/')}`;
106
+ }
107
+
99
108
  /**
100
109
  * @param {RegExpMatchArray} match
101
110
  * @param {string[]} names
package/src/utils/url.js CHANGED
@@ -75,23 +75,68 @@ export function decode_params(params) {
75
75
  return params;
76
76
  }
77
77
 
78
- export class LoadURL extends URL {
79
- /** @returns {string} */
80
- get hash() {
81
- throw new Error(
82
- 'url.hash is inaccessible from load. Consider accessing hash from the page store within the script tag of your component.'
83
- );
78
+ /**
79
+ * URL properties that could change during the lifetime of the page,
80
+ * which excludes things like `origin`
81
+ * @type {Array<keyof URL>}
82
+ */
83
+ const tracked_url_properties = ['href', 'pathname', 'search', 'searchParams', 'toString', 'toJSON'];
84
+
85
+ /**
86
+ * @param {URL} url
87
+ * @param {() => void} callback
88
+ */
89
+ export function make_trackable(url, callback) {
90
+ const tracked = new URL(url);
91
+
92
+ for (const property of tracked_url_properties) {
93
+ let value = tracked[property];
94
+
95
+ Object.defineProperty(tracked, property, {
96
+ get() {
97
+ callback();
98
+ return value;
99
+ },
100
+
101
+ enumerable: true,
102
+ configurable: true
103
+ });
84
104
  }
105
+
106
+ // @ts-ignore
107
+ tracked[Symbol.for('nodejs.util.inspect.custom')] = (depth, opts, inspect) => {
108
+ return inspect(url, opts);
109
+ };
110
+
111
+ disable_hash(tracked);
112
+
113
+ return tracked;
85
114
  }
86
115
 
87
- export class PrerenderingURL extends URL {
88
- /** @returns {string} */
89
- get search() {
90
- throw new Error('Cannot access url.search on a page with prerendering enabled');
91
- }
116
+ /**
117
+ * Disallow access to `url.hash` on the server and in `load`
118
+ * @param {URL} url
119
+ */
120
+ export function disable_hash(url) {
121
+ Object.defineProperty(url, 'hash', {
122
+ get() {
123
+ throw new Error(
124
+ 'Cannot access event.url.hash. Consider using `$page.url.hash` inside a component instead'
125
+ );
126
+ }
127
+ });
128
+ }
92
129
 
93
- /** @returns {URLSearchParams} */
94
- get searchParams() {
95
- throw new Error('Cannot access url.searchParams on a page with prerendering enabled');
130
+ /**
131
+ * Disallow access to `url.search` and `url.searchParams` during prerendering
132
+ * @param {URL} url
133
+ */
134
+ export function disable_search(url) {
135
+ for (const property of ['search', 'searchParams']) {
136
+ Object.defineProperty(url, property, {
137
+ get() {
138
+ throw new Error(`Cannot access url.${property} on a page with prerendering enabled`);
139
+ }
140
+ });
96
141
  }
97
142
  }
package/src/vite/index.js CHANGED
@@ -58,9 +58,7 @@ const enforced_config = {
58
58
  root: true
59
59
  };
60
60
 
61
- /**
62
- * @return {import('vite').Plugin[]}
63
- */
61
+ /** @return {import('vite').Plugin[]} */
64
62
  export function sveltekit() {
65
63
  return [...svelte(), kit()];
66
64
  }
@@ -151,7 +151,7 @@ declare module '$app/navigation' {
151
151
  opts?: { replaceState?: boolean; noscroll?: boolean; keepfocus?: boolean; state?: any }
152
152
  ): Promise<void>;
153
153
  /**
154
- * Causes any `load` functions belonging to the currently active page to re-run if they `fetch` the resource in question, or re-fetches data from a page endpoint if the invalidated resource is the page itself. If no argument is given, all resources will be invalidated. Returns a `Promise` that resolves when the page is subsequently updated.
154
+ * Causes any `load` functions belonging to the currently active page to re-run if they `fetch` the resource in question. If no argument is given, all resources will be invalidated. Returns a `Promise` that resolves when the page is subsequently updated.
155
155
  * @param dependency The invalidated resource
156
156
  */
157
157
  export function invalidate(dependency?: string | ((href: string) => boolean)): Promise<void>;
package/types/index.d.ts CHANGED
@@ -238,7 +238,7 @@ export interface ParamMatcher {
238
238
  export interface RequestEvent<
239
239
  Params extends Partial<Record<string, string>> = Partial<Record<string, string>>
240
240
  > {
241
- clientAddress: string;
241
+ getClientAddress: () => string;
242
242
  locals: App.Locals;
243
243
  params: Params;
244
244
  platform: Readonly<App.Platform>;