@sveltejs/kit 1.1.4 → 1.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.1.4",
3
+ "version": "1.2.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.15.0"
25
+ "undici": "5.15.1"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@playwright/test": "^1.29.2",
@@ -86,6 +86,7 @@
86
86
  "format": "pnpm lint --write",
87
87
  "test": "pnpm test:unit && pnpm test:integration",
88
88
  "test:integration": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test",
89
+ "test:cross-browser": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:cross-browser",
89
90
  "test:unit": "uvu src \"(spec\\.js|test[\\\\/]index\\.js)\"",
90
91
  "postinstall": "node postinstall.js"
91
92
  }
@@ -9,24 +9,33 @@ import options from './options.js';
9
9
  * @param {string} cwd
10
10
  * @param {import('types').ValidatedConfig} config
11
11
  */
12
- export function load_template(cwd, config) {
13
- const { appTemplate } = config.kit.files;
14
- const relative = path.relative(cwd, appTemplate);
15
-
16
- if (fs.existsSync(appTemplate)) {
17
- const contents = fs.readFileSync(appTemplate, 'utf8');
18
-
19
- const expected_tags = ['%sveltekit.head%', '%sveltekit.body%'];
20
- expected_tags.forEach((tag) => {
21
- if (contents.indexOf(tag) === -1) {
22
- throw new Error(`${relative} is missing ${tag}`);
23
- }
24
- });
25
- } else {
12
+ export function load_template(cwd, { kit }) {
13
+ const { env, files } = kit;
14
+
15
+ const relative = path.relative(cwd, files.appTemplate);
16
+
17
+ if (!fs.existsSync(files.appTemplate)) {
26
18
  throw new Error(`${relative} does not exist`);
27
19
  }
28
20
 
29
- return fs.readFileSync(appTemplate, 'utf-8');
21
+ const contents = fs.readFileSync(files.appTemplate, 'utf8');
22
+
23
+ const expected_tags = ['%sveltekit.head%', '%sveltekit.body%'];
24
+ expected_tags.forEach((tag) => {
25
+ if (contents.indexOf(tag) === -1) {
26
+ throw new Error(`${relative} is missing ${tag}`);
27
+ }
28
+ });
29
+
30
+ for (const match of contents.matchAll(/%sveltekit\.env\.([^%]+)%/g)) {
31
+ if (!match[1].startsWith(env.publicPrefix)) {
32
+ throw new Error(
33
+ `Environment variables in ${relative} must start with ${env.publicPrefix} (saw %sveltekit.env.${match[1]}%)`
34
+ );
35
+ }
36
+ }
37
+
38
+ return contents;
30
39
  }
31
40
 
32
41
  /**
@@ -38,11 +38,15 @@ export const options = {
38
38
  root,
39
39
  service_worker: ${has_service_worker},
40
40
  templates: {
41
- app: ({ head, body, assets, nonce }) => ${s(template)
41
+ app: ({ head, body, assets, nonce, env }) => ${s(template)
42
42
  .replace('%sveltekit.head%', '" + head + "')
43
43
  .replace('%sveltekit.body%', '" + body + "')
44
44
  .replace(/%sveltekit\.assets%/g, '" + assets + "')
45
- .replace(/%sveltekit\.nonce%/g, '" + nonce + "')},
45
+ .replace(/%sveltekit\.nonce%/g, '" + nonce + "')
46
+ .replace(
47
+ /%sveltekit\.env\.([^%]+)%/g,
48
+ (_match, capture) => `" + (env[${s(capture)}] ?? "") + "`
49
+ )},
46
50
  error: ({ status, message }) => ${s(error_page)
47
51
  .replace(/%sveltekit\.status%/g, '" + status + "')
48
52
  .replace(/%sveltekit\.error\.message%/g, '" + message + "')}
@@ -29,12 +29,36 @@ export function redirect(status, location) {
29
29
  export function json(data, init) {
30
30
  // TODO deprecate this in favour of `Response.json` when it's
31
31
  // more widely supported
32
+ const body = JSON.stringify(data);
33
+
34
+ // we can't just do `text(JSON.stringify(data), init)` because
35
+ // it will set a default `content-type` header. duplicated code
36
+ // means less duplicated work
32
37
  const headers = new Headers(init?.headers);
38
+ if (!headers.has('content-length')) {
39
+ headers.set('content-length', encoder.encode(body).byteLength.toString());
40
+ }
41
+
33
42
  if (!headers.has('content-type')) {
34
43
  headers.set('content-type', 'application/json');
35
44
  }
36
45
 
37
- return new Response(JSON.stringify(data), {
46
+ return new Response(body, {
47
+ ...init,
48
+ headers
49
+ });
50
+ }
51
+
52
+ const encoder = new TextEncoder();
53
+
54
+ /** @type {import('@sveltejs/kit').text} */
55
+ export function text(body, init) {
56
+ const headers = new Headers(init?.headers);
57
+ if (!headers.has('content-length')) {
58
+ headers.set('content-length', encoder.encode(body).byteLength.toString());
59
+ }
60
+
61
+ return new Response(body, {
38
62
  ...init,
39
63
  headers
40
64
  });
@@ -73,8 +73,7 @@ export async function dev(vite, vite_config, svelte_config) {
73
73
  } catch (error) {
74
74
  manifest_error = /** @type {Error} */ (error);
75
75
 
76
- console.error(colors.bold().red('Invalid routes'));
77
- console.error(error);
76
+ console.error(colors.bold().red(manifest_error.message));
78
77
  vite.ws.send({
79
78
  type: 'error',
80
79
  err: {
@@ -444,8 +443,7 @@ export async function dev(vite, vite_config, svelte_config) {
444
443
  }
445
444
 
446
445
  if (manifest_error) {
447
- console.error(colors.bold().red('Invalid routes'));
448
- console.error(manifest_error);
446
+ console.error(colors.bold().red(manifest_error.message));
449
447
 
450
448
  const error_page = load_error_page(svelte_config);
451
449
 
@@ -1,3 +1,4 @@
1
+ /** @type {Record<string, string>} */
1
2
  export let env = {};
2
3
 
3
4
  /** @type {(environment: Record<string, string>) => void} */
@@ -8,6 +8,12 @@ import { normalize_path } from '../../utils/url.js';
8
8
  * @type {Record<string, Set<string>>} */
9
9
  const cookie_paths = {};
10
10
 
11
+ /**
12
+ * Cookies that are larger than this size (including the name and other
13
+ * attributes) are discarded by browsers
14
+ */
15
+ const MAX_COOKIE_SIZE = 4129;
16
+
11
17
  /**
12
18
  * @param {Request} request
13
19
  * @param {URL} url
@@ -111,6 +117,11 @@ export function get_cookies(request, url, trailing_slash) {
111
117
  };
112
118
 
113
119
  if (__SVELTEKIT_DEV__) {
120
+ const serialized = serialize(name, value, new_cookies[name].options);
121
+ if (new TextEncoder().encode(serialized).byteLength > MAX_COOKIE_SIZE) {
122
+ throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
123
+ }
124
+
114
125
  cookie_paths[name] = cookie_paths[name] ?? new Set();
115
126
  if (!value) {
116
127
  if (!cookie_paths[name].has(path) && cookie_paths[name].size > 0) {
@@ -4,6 +4,7 @@ import { once } from '../../../utils/functions.js';
4
4
  import { load_server_data } from '../page/load_data.js';
5
5
  import { clarify_devalue_error, handle_error_and_jsonify, serialize_data_node } from '../utils.js';
6
6
  import { normalize_path } from '../../../utils/url.js';
7
+ import { text } from '../../../exports/index.js';
7
8
 
8
9
  export const INVALIDATED_PARAM = 'x-sveltekit-invalidated';
9
10
 
@@ -139,7 +140,7 @@ export async function render_data(
139
140
  * @param {number} [status]
140
141
  */
141
142
  function json_response(json, status = 200) {
142
- return new Response(json, {
143
+ return text(json, {
143
144
  status,
144
145
  headers: {
145
146
  'content-type': 'application/json',
@@ -1,3 +1,4 @@
1
+ import { text } from '../../../exports/index.js';
1
2
  import { compact } from '../../../utils/array.js';
2
3
  import { normalize_error } from '../../../utils/error.js';
3
4
  import { add_data_suffix } from '../../../utils/url.js';
@@ -32,7 +33,7 @@ import { respond_with_error } from './respond_with_error.js';
32
33
  export async function render_page(event, route, page, options, manifest, state, resolve_opts) {
33
34
  if (state.initiator === route) {
34
35
  // infinite request cycle detected
35
- return new Response(`Not found: ${event.url.pathname}`, {
36
+ return text(`Not found: ${event.url.pathname}`, {
36
37
  status: 404
37
38
  });
38
39
  }
@@ -239,7 +240,7 @@ export async function render_page(event, route, page, options, manifest, state,
239
240
  });
240
241
 
241
242
  state.prerendering.dependencies.set(data_pathname, {
242
- response: new Response(body),
243
+ response: text(body),
243
244
  body
244
245
  });
245
246
  }
@@ -294,7 +295,7 @@ export async function render_page(event, route, page, options, manifest, state,
294
295
  .join(',')}]}`;
295
296
 
296
297
  state.prerendering.dependencies.set(data_pathname, {
297
- response: new Response(body),
298
+ response: text(body),
298
299
  body
299
300
  });
300
301
  }
@@ -9,6 +9,7 @@ import { uneval_action_response } from './actions.js';
9
9
  import { clarify_devalue_error } from '../utils.js';
10
10
  import { assets, base, version } from '../../shared.js';
11
11
  import { env } from '../../env-public.js';
12
+ import { text } from '../../../exports/index.js';
12
13
 
13
14
  // TODO rename this function/module
14
15
 
@@ -366,7 +367,8 @@ export async function render_response({
366
367
  head,
367
368
  body,
368
369
  assets: resolved_assets,
369
- nonce: /** @type {string} */ (csp.nonce)
370
+ nonce: /** @type {string} */ (csp.nonce),
371
+ env
370
372
  });
371
373
 
372
374
  // TODO flush chunks as early as we can
@@ -407,7 +409,7 @@ export async function render_response({
407
409
  }
408
410
  }
409
411
 
410
- return new Response(transformed, {
412
+ return text(transformed, {
411
413
  status,
412
414
  headers
413
415
  });
@@ -23,7 +23,7 @@ import {
23
23
  validate_page_server_exports,
24
24
  validate_server_exports
25
25
  } from '../../utils/exports.js';
26
- import { error, json } from '../../exports/index.js';
26
+ import { error, json, text } from '../../exports/index.js';
27
27
  import * as paths from '../shared.js';
28
28
 
29
29
  /* global __SVELTEKIT_ADAPTER_NAME__ */
@@ -53,7 +53,7 @@ export async function respond(request, options, manifest, state) {
53
53
  if (request.headers.get('accept') === 'application/json') {
54
54
  return json(csrf_error.body, { status: csrf_error.status });
55
55
  }
56
- return new Response(csrf_error.body.message, { status: csrf_error.status });
56
+ return text(csrf_error.body.message, { status: csrf_error.status });
57
57
  }
58
58
  }
59
59
 
@@ -61,7 +61,7 @@ export async function respond(request, options, manifest, state) {
61
61
  try {
62
62
  decoded = decode_pathname(url.pathname);
63
63
  } catch {
64
- return new Response('Malformed URI', { status: 400 });
64
+ return text('Malformed URI', { status: 400 });
65
65
  }
66
66
 
67
67
  /** @type {import('types').SSRRoute | null} */
@@ -72,7 +72,7 @@ export async function respond(request, options, manifest, state) {
72
72
 
73
73
  if (paths.base && !state.prerendering?.fallback) {
74
74
  if (!decoded.startsWith(paths.base)) {
75
- return new Response('Not found', { status: 404 });
75
+ return text('Not found', { status: 404 });
76
76
  }
77
77
  decoded = decoded.slice(paths.base.length) || '/';
78
78
  }
@@ -374,7 +374,7 @@ export async function respond(request, options, manifest, state) {
374
374
  }
375
375
 
376
376
  if (state.initiator === GENERIC_ERROR) {
377
- return new Response('Internal Server Error', {
377
+ return text('Internal Server Error', {
378
378
  status: 500
379
379
  });
380
380
  }
@@ -394,7 +394,7 @@ export async function respond(request, options, manifest, state) {
394
394
  }
395
395
 
396
396
  if (state.prerendering) {
397
- return new Response('not found', { status: 404 });
397
+ return text('not found', { status: 404 });
398
398
  }
399
399
 
400
400
  // we can't load the endpoint from our own manifest,
@@ -1,4 +1,5 @@
1
1
  import * as devalue from 'devalue';
2
+ import { json, text } from '../../exports/index.js';
2
3
  import { coalesce_to_error } from '../../utils/error.js';
3
4
  import { negotiate } from '../../utils/http.js';
4
5
  import { has_data_suffix } from '../../utils/url.js';
@@ -27,7 +28,7 @@ export const GENERIC_ERROR = {
27
28
  * @param {import('types').HttpMethod} method
28
29
  */
29
30
  export function method_not_allowed(mod, method) {
30
- return new Response(`${method} method not allowed`, {
31
+ return text(`${method} method not allowed`, {
31
32
  status: 405,
32
33
  headers: {
33
34
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
@@ -75,7 +76,7 @@ export function get_option(nodes, option) {
75
76
  * @param {string} message
76
77
  */
77
78
  export function static_error_page(options, status, message) {
78
- return new Response(options.templates.error({ status, message }), {
79
+ return text(options.templates.error({ status, message }), {
79
80
  headers: { 'content-type': 'text/html; charset=utf-8' },
80
81
  status
81
82
  });
@@ -98,9 +99,8 @@ export async function handle_fatal_error(event, options, error) {
98
99
  ]);
99
100
 
100
101
  if (has_data_suffix(new URL(event.request.url).pathname) || type === 'application/json') {
101
- return new Response(JSON.stringify(body), {
102
- status,
103
- headers: { 'content-type': 'application/json; charset=utf-8' }
102
+ return json(body, {
103
+ status
104
104
  });
105
105
  }
106
106
 
package/types/index.d.ts CHANGED
@@ -1123,10 +1123,17 @@ export interface Redirect {
1123
1123
  /**
1124
1124
  * Create a JSON `Response` object from the supplied data.
1125
1125
  * @param data The value that will be serialized as JSON.
1126
- * @param init Options such as `status` and `headers` that will be added to the response. A `Content-Type: application/json` header will be added automatically.
1126
+ * @param init Options such as `status` and `headers` that will be added to the response. `Content-Type: application/json` and `Content-Length` headers will be added automatically.
1127
1127
  */
1128
1128
  export function json(data: any, init?: ResponseInit): Response;
1129
1129
 
1130
+ /**
1131
+ * Create a `Response` object from the supplied body.
1132
+ * @param body The value that will be used as-is.
1133
+ * @param init Options such as `status` and `headers` that will be added to the response. A `Content-Length` header will be added automatically.
1134
+ */
1135
+ export function text(body: string, init?: ResponseInit): Response;
1136
+
1130
1137
  /**
1131
1138
  * Create an `ActionFailure` object.
1132
1139
  * @param status The [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses). Must be in the range 400-599.
@@ -306,7 +306,13 @@ export interface SSROptions {
306
306
  root: SSRComponent['default'];
307
307
  service_worker: boolean;
308
308
  templates: {
309
- app(values: { head: string; body: string; assets: string; nonce: string }): string;
309
+ app(values: {
310
+ head: string;
311
+ body: string;
312
+ assets: string;
313
+ nonce: string;
314
+ env: Record<string, string>;
315
+ }): string;
310
316
  error(values: { message: string; status: number }): string;
311
317
  };
312
318
  }