@sveltejs/kit 1.0.0-next.296 → 1.0.0-next.299

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.
@@ -11,6 +11,7 @@ import { update, init } from './sync.js';
11
11
  import { getRequest, setResponse } from '../node.js';
12
12
  import { sequence } from '../hooks.js';
13
13
  import { p as posixify } from './filesystem.js';
14
+ import { p as parse_route_id } from './routing.js';
14
15
  import 'sade';
15
16
  import 'child_process';
16
17
  import 'net';
@@ -116,12 +117,15 @@ async function create_plugin(config, cwd) {
116
117
  };
117
118
  }),
118
119
  routes: manifest_data.routes.map((route) => {
120
+ const { pattern, names, types } = parse_route_id(route.id);
121
+
119
122
  if (route.type === 'page') {
120
123
  return {
121
124
  type: 'page',
122
- key: route.key,
123
- pattern: route.pattern,
124
- params: get_params(route.params),
125
+ id: route.id,
126
+ pattern,
127
+ names,
128
+ types,
125
129
  shadow: route.shadow
126
130
  ? async () => {
127
131
  const url = path__default.resolve(cwd, /** @type {string} */ (route.shadow));
@@ -135,14 +139,34 @@ async function create_plugin(config, cwd) {
135
139
 
136
140
  return {
137
141
  type: 'endpoint',
138
- pattern: route.pattern,
139
- params: get_params(route.params),
142
+ id: route.id,
143
+ pattern,
144
+ names,
145
+ types,
140
146
  load: async () => {
141
147
  const url = path__default.resolve(cwd, route.file);
142
148
  return await vite.ssrLoadModule(url);
143
149
  }
144
150
  };
145
- })
151
+ }),
152
+ validators: async () => {
153
+ /** @type {Record<string, import('types').ParamValidator>} */
154
+ const validators = {};
155
+
156
+ for (const key in manifest_data.validators) {
157
+ const file = manifest_data.validators[key];
158
+ const url = path__default.resolve(cwd, file);
159
+ const module = await vite.ssrLoadModule(url);
160
+
161
+ if (module.validate) {
162
+ validators[key] = module.validate;
163
+ } else {
164
+ throw new Error(`${file} does not export a \`validate\` function`);
165
+ }
166
+ }
167
+
168
+ return validators;
169
+ }
146
170
  }
147
171
  };
148
172
  }
@@ -168,7 +192,7 @@ async function create_plugin(config, cwd) {
168
192
  update_manifest();
169
193
 
170
194
  vite.watcher.on('add', update_manifest);
171
- vite.watcher.on('remove', update_manifest);
195
+ vite.watcher.on('unlink', update_manifest);
172
196
 
173
197
  const assets = config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base;
174
198
  const asset_server = sirv(config.kit.files.assets, {
@@ -271,62 +295,72 @@ async function create_plugin(config, cwd) {
271
295
 
272
296
  const template = load_template(cwd, config);
273
297
 
274
- const rendered = await respond(request, {
275
- amp: config.kit.amp,
276
- csp: config.kit.csp,
277
- dev: true,
278
- floc: config.kit.floc,
279
- get_stack: (error) => {
280
- return fix_stack_trace(error);
281
- },
282
- handle_error: (error, event) => {
283
- hooks.handleError({
284
- error: new Proxy(error, {
285
- get: (target, property) => {
286
- if (property === 'stack') {
287
- return fix_stack_trace(error);
298
+ const rendered = await respond(
299
+ request,
300
+ {
301
+ amp: config.kit.amp,
302
+ csp: config.kit.csp,
303
+ dev: true,
304
+ floc: config.kit.floc,
305
+ get_stack: (error) => {
306
+ return fix_stack_trace(error);
307
+ },
308
+ handle_error: (error, event) => {
309
+ hooks.handleError({
310
+ error: new Proxy(error, {
311
+ get: (target, property) => {
312
+ if (property === 'stack') {
313
+ return fix_stack_trace(error);
314
+ }
315
+
316
+ return Reflect.get(target, property, target);
288
317
  }
289
-
290
- return Reflect.get(target, property, target);
318
+ }),
319
+ event,
320
+
321
+ // TODO remove for 1.0
322
+ // @ts-expect-error
323
+ get request() {
324
+ throw new Error(
325
+ 'request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details'
326
+ );
291
327
  }
292
- }),
293
- event,
294
-
295
- // TODO remove for 1.0
296
- // @ts-expect-error
297
- get request() {
298
- throw new Error(
299
- 'request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details'
300
- );
301
- }
302
- });
328
+ });
329
+ },
330
+ hooks,
331
+ hydrate: config.kit.browser.hydrate,
332
+ manifest,
333
+ method_override: config.kit.methodOverride,
334
+ paths: {
335
+ base: config.kit.paths.base,
336
+ assets
337
+ },
338
+ prefix: '',
339
+ prerender: config.kit.prerender.enabled,
340
+ read: (file) => fs__default.readFileSync(path__default.join(config.kit.files.assets, file)),
341
+ root,
342
+ router: config.kit.browser.router,
343
+ template: ({ head, body, assets, nonce }) => {
344
+ return (
345
+ template
346
+ .replace(/%svelte\.assets%/g, assets)
347
+ .replace(/%svelte\.nonce%/g, nonce)
348
+ // head and body must be replaced last, in case someone tries to sneak in %svelte.assets% etc
349
+ .replace('%svelte.head%', () => head)
350
+ .replace('%svelte.body%', () => body)
351
+ );
352
+ },
353
+ template_contains_nonce: template.includes('%svelte.nonce%'),
354
+ trailing_slash: config.kit.trailingSlash
303
355
  },
304
- hooks,
305
- hydrate: config.kit.browser.hydrate,
306
- manifest,
307
- method_override: config.kit.methodOverride,
308
- paths: {
309
- base: config.kit.paths.base,
310
- assets
311
- },
312
- prefix: '',
313
- prerender: config.kit.prerender.enabled,
314
- read: (file) => fs__default.readFileSync(path__default.join(config.kit.files.assets, file)),
315
- root,
316
- router: config.kit.browser.router,
317
- template: ({ head, body, assets, nonce }) => {
318
- return (
319
- template
320
- .replace(/%svelte\.assets%/g, assets)
321
- .replace(/%svelte\.nonce%/g, nonce)
322
- // head and body must be replaced last, in case someone tries to sneak in %svelte.assets% etc
323
- .replace('%svelte.head%', () => head)
324
- .replace('%svelte.body%', () => body)
325
- );
326
- },
327
- template_contains_nonce: template.includes('%svelte.nonce%'),
328
- trailing_slash: config.kit.trailingSlash
329
- });
356
+ {
357
+ getClientAddress: () => {
358
+ const { remoteAddress } = req.socket;
359
+ if (remoteAddress) return remoteAddress;
360
+ throw new Error('Could not determine clientAddress');
361
+ }
362
+ }
363
+ );
330
364
 
331
365
  if (rendered) {
332
366
  setResponse(res, rendered);
@@ -345,29 +379,6 @@ async function create_plugin(config, cwd) {
345
379
  };
346
380
  }
347
381
 
348
- /** @param {string[]} array */
349
- function get_params(array) {
350
- // given an array of params like `['x', 'y', 'z']` for
351
- // src/routes/[x]/[y]/[z]/svelte, create a function
352
- // that turns a RegExpExecArray into ({ x, y, z })
353
-
354
- /** @param {RegExpExecArray} match */
355
- const fn = (match) => {
356
- /** @type {Record<string, string>} */
357
- const params = {};
358
- array.forEach((key, i) => {
359
- if (key.startsWith('...')) {
360
- params[key.slice(3)] = match[i + 1] || '';
361
- } else {
362
- params[key] = match[i + 1];
363
- }
364
- });
365
- return params;
366
- };
367
-
368
- return fn;
369
- }
370
-
371
382
  /** @param {import('http').ServerResponse} res */
372
383
  function not_found(res) {
373
384
  res.statusCode = 404;
@@ -15,6 +15,7 @@ import 'sade';
15
15
  import 'child_process';
16
16
  import 'net';
17
17
  import 'os';
18
+ import './routing.js';
18
19
  import 'node:http';
19
20
  import 'node:https';
20
21
  import 'node:zlib';
@@ -145,7 +146,6 @@ async function create_build(config) {
145
146
  * @param {import('vite').Manifest} manifest
146
147
  * @param {Set<string>} css
147
148
  * @param {Set<string>} js
148
- * @returns
149
149
  */
150
150
  function find_deps(file, manifest, js, css) {
151
151
  const chunk = manifest[file];
@@ -287,7 +287,6 @@ async function build_client({
287
287
  * runtime: string;
288
288
  * template: string;
289
289
  * }} opts
290
- * @returns
291
290
  */
292
291
  const server_template = ({ config, hooks, has_service_worker, runtime, template }) => `
293
292
  import root from '__GENERATED__/root.svelte';
@@ -424,7 +423,7 @@ async function build_server(
424
423
  }
425
424
  });
426
425
 
427
- // ...and every component used by pages
426
+ // ...and every component used by pages...
428
427
  manifest_data.components.forEach((file) => {
429
428
  const resolved = path__default.resolve(cwd, file);
430
429
  const relative = path__default.relative(config.kit.files.routes, resolved);
@@ -435,6 +434,12 @@ async function build_server(
435
434
  input[name] = resolved;
436
435
  });
437
436
 
437
+ // ...and every validator
438
+ Object.entries(manifest_data.validators).forEach(([key, file]) => {
439
+ const name = posixify(path__default.join('entries/validators', key));
440
+ input[name] = path__default.resolve(cwd, file);
441
+ });
442
+
438
443
  /** @type {(file: string) => string} */
439
444
  const app_relative = (file) => {
440
445
  const relative_file = path__default.relative(build_dir, path__default.resolve(cwd, file));
@@ -500,6 +505,8 @@ async function build_server(
500
505
 
501
506
  print_config_conflicts(conflicts, 'kit.vite.', 'build_server');
502
507
 
508
+ process.env.VITE_SVELTEKIT_ADAPTER_NAME = config.kit.adapter?.name;
509
+
503
510
  const { chunks } = await create_build(merged_config);
504
511
 
505
512
  /** @type {import('vite').Manifest} */
@@ -575,7 +582,6 @@ const method_names = {
575
582
  };
576
583
 
577
584
  /**
578
- *
579
585
  * @param {string} cwd
580
586
  * @param {import('rollup').OutputChunk[]} output
581
587
  * @param {import('types').ManifestData} manifest_data
@@ -1073,6 +1079,7 @@ async function prerender({ config, entries, files, log }) {
1073
1079
  const dependencies = new Map();
1074
1080
 
1075
1081
  const response = await server.respond(new Request(`http://sveltekit-prerender${encoded}`), {
1082
+ getClientAddress,
1076
1083
  prerender: {
1077
1084
  default: config.kit.prerender.default,
1078
1085
  dependencies
@@ -1209,6 +1216,7 @@ async function prerender({ config, entries, files, log }) {
1209
1216
  }
1210
1217
 
1211
1218
  const rendered = await server.respond(new Request('http://sveltekit-prerender/[fallback]'), {
1219
+ getClientAddress,
1212
1220
  prerender: {
1213
1221
  fallback: true,
1214
1222
  default: false,
@@ -1223,6 +1231,11 @@ async function prerender({ config, entries, files, log }) {
1223
1231
  return prerendered;
1224
1232
  }
1225
1233
 
1234
+ /** @return {string} */
1235
+ function getClientAddress() {
1236
+ throw new Error('Cannot read clientAddress during prerendering');
1237
+ }
1238
+
1226
1239
  /**
1227
1240
  * @param {import('types').ValidatedConfig} config
1228
1241
  * @param {{ log: import('types').Logger }} opts
@@ -1,4 +1,5 @@
1
1
  import { s } from './misc.js';
2
+ import { p as parse_route_id } from './routing.js';
2
3
  import { a as get_mime_lookup } from '../cli.js';
3
4
 
4
5
  /**
@@ -41,10 +42,13 @@ function generate_manifest({ build_data, relative_path, routes, format = 'esm' }
41
42
  });
42
43
 
43
44
  /** @type {(path: string) => string} */
44
- const importer =
45
+ const load =
45
46
  format === 'esm'
46
- ? (path) => `() => import('${path}')`
47
- : (path) => `() => Promise.resolve().then(() => require('${path}'))`;
47
+ ? (path) => `import('${path}')`
48
+ : (path) => `Promise.resolve().then(() => require('${path}'))`;
49
+
50
+ /** @type {(path: string) => string} */
51
+ const loader = (path) => `() => ${load(path)}`;
48
52
 
49
53
  const assets = build_data.manifest_data.assets.map((asset) => asset.file);
50
54
  if (build_data.service_worker) {
@@ -54,6 +58,8 @@ function generate_manifest({ build_data, relative_path, routes, format = 'esm' }
54
58
  /** @param {string} id */
55
59
  const get_index = (id) => id && /** @type {LookupEntry} */ (bundled_nodes.get(id)).index;
56
60
 
61
+ const validators = new Set();
62
+
57
63
  // prettier-ignore
58
64
  return `{
59
65
  appDir: ${s(build_data.app_dir)},
@@ -62,18 +68,25 @@ function generate_manifest({ build_data, relative_path, routes, format = 'esm' }
62
68
  _: {
63
69
  entry: ${s(build_data.client.entry)},
64
70
  nodes: [
65
- ${Array.from(bundled_nodes.values()).map(node => importer(node.path)).join(',\n\t\t\t\t')}
71
+ ${Array.from(bundled_nodes.values()).map(node => loader(node.path)).join(',\n\t\t\t\t')}
66
72
  ],
67
73
  routes: [
68
74
  ${routes.map(route => {
75
+ const { pattern, names, types } = parse_route_id(route.id);
76
+
77
+ types.forEach(type => {
78
+ if (type) validators.add(type);
79
+ });
80
+
69
81
  if (route.type === 'page') {
70
82
  return `{
71
83
  type: 'page',
72
- key: ${s(route.key)},
73
- pattern: ${route.pattern},
74
- params: ${get_params(route.params)},
84
+ id: ${s(route.id)},
85
+ pattern: ${pattern},
86
+ names: ${s(names)},
87
+ types: ${s(types)},
75
88
  path: ${route.path ? s(route.path) : null},
76
- shadow: ${route.shadow ? importer(`${relative_path}/${build_data.server.vite_manifest[route.shadow].file}`) : null},
89
+ shadow: ${route.shadow ? loader(`${relative_path}/${build_data.server.vite_manifest[route.shadow].file}`) : null},
77
90
  a: ${s(route.a.map(get_index))},
78
91
  b: ${s(route.b.map(get_index))}
79
92
  }`.replace(/^\t\t/gm, '');
@@ -86,33 +99,21 @@ function generate_manifest({ build_data, relative_path, routes, format = 'esm' }
86
99
 
87
100
  return `{
88
101
  type: 'endpoint',
89
- pattern: ${route.pattern},
90
- params: ${get_params(route.params)},
91
- load: ${importer(`${relative_path}/${build_data.server.vite_manifest[route.file].file}`)}
102
+ id: ${s(route.id)},
103
+ pattern: ${pattern},
104
+ names: ${s(names)},
105
+ types: ${s(types)},
106
+ load: ${loader(`${relative_path}/${build_data.server.vite_manifest[route.file].file}`)}
92
107
  }`.replace(/^\t\t/gm, '');
93
108
  }
94
109
  }).filter(Boolean).join(',\n\t\t\t\t')}
95
- ]
110
+ ],
111
+ validators: async () => {
112
+ ${Array.from(validators).map(type => `const { validate: ${type} } = await ${load(`${relative_path}/entries/validators/${type}.js`)}`).join('\n\t\t\t\t')}
113
+ return { ${Array.from(validators).join(', ')} };
114
+ }
96
115
  }
97
116
  }`.replace(/^\t/gm, '');
98
117
  }
99
118
 
100
- /** @param {string[]} array */
101
- function get_params(array) {
102
- // given an array of params like `['x', 'y', 'z']` for
103
- // src/routes/[x]/[y]/[z]/svelte, create a function
104
- // that turns a RexExpMatchArray into ({ x, y, z })
105
- return array.length
106
- ? '(m) => ({ ' +
107
- array
108
- .map((param, i) => {
109
- return param.startsWith('...')
110
- ? `${param.slice(3)}: m[${i + 1}] || ''`
111
- : `${param}: m[${i + 1}]`;
112
- })
113
- .join(', ') +
114
- '})'
115
- : 'null';
116
- }
117
-
118
119
  export { generate_manifest as g };
@@ -9,6 +9,7 @@ import 'fs';
9
9
  import 'url';
10
10
  import 'os';
11
11
  import './misc.js';
12
+ import './routing.js';
12
13
 
13
14
  /**
14
15
  * @param {{
@@ -145,7 +145,16 @@ async function preview({ port, host, config, https: use_https = false }) {
145
145
  return res.end(err.reason || 'Invalid request body');
146
146
  }
147
147
 
148
- setResponse(res, await server.respond(request));
148
+ setResponse(
149
+ res,
150
+ await server.respond(request, {
151
+ getClientAddress: () => {
152
+ const { remoteAddress } = req.socket;
153
+ if (remoteAddress) return remoteAddress;
154
+ throw new Error('Could not determine clientAddress');
155
+ }
156
+ })
157
+ );
149
158
  }
150
159
  ]);
151
160
 
@@ -0,0 +1,39 @@
1
+ /** @param {string} key */
2
+ function parse_route_id(key) {
3
+ /** @type {string[]} */
4
+ const names = [];
5
+
6
+ /** @type {string[]} */
7
+ const types = [];
8
+
9
+ const pattern =
10
+ key === ''
11
+ ? /^\/$/
12
+ : new RegExp(
13
+ `^${decodeURIComponent(key)
14
+ .split('/')
15
+ .map((segment) => {
16
+ // special case — /[...rest]/ could contain zero segments
17
+ const match = /^\[\.\.\.(\w+)(?:=\w+)?\]$/.exec(segment);
18
+ if (match) {
19
+ names.push(match[1]);
20
+ types.push(match[2]);
21
+ return '(?:/(.*))?';
22
+ }
23
+
24
+ return (
25
+ '/' +
26
+ segment.replace(/\[(\.\.\.)?(\w+)(?:=(\w+))?\]/g, (m, rest, name, type) => {
27
+ names.push(name);
28
+ types.push(type);
29
+ return rest ? '(.*?)' : '([^/]+?)';
30
+ })
31
+ );
32
+ })
33
+ .join('')}/?$`
34
+ );
35
+
36
+ return { pattern, names, types };
37
+ }
38
+
39
+ export { parse_route_id as p };