@sveltejs/kit 1.0.0-next.303 → 1.0.0-next.306

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.
@@ -269,7 +269,7 @@ function parse_route_id(id) {
269
269
  ? /^\/$/
270
270
  : new RegExp(
271
271
  `^${decodeURIComponent(id)
272
- .split('/')
272
+ .split(/(?:@(?:~|[a-zA-Z0-9_-]*))?(?:\/|$)/)
273
273
  .map((segment, i, segments) => {
274
274
  // special case — /[...rest]/ could contain zero segments
275
275
  const match = /^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(segment);
@@ -282,40 +282,41 @@ function parse_route_id(id) {
282
282
  const is_last = i === segments.length - 1;
283
283
 
284
284
  return (
285
+ segment &&
285
286
  '/' +
286
- segment
287
- .split(/\[(.+?)\]/)
288
- .map((content, i) => {
289
- if (i % 2) {
290
- const [, rest, name, type] = /** @type {RegExpMatchArray} */ (
291
- param_pattern.exec(content)
292
- );
293
- names.push(name);
294
- types.push(type);
295
- return rest ? '(.*?)' : '([^/]+?)';
296
- }
297
-
298
- if (is_last && content.includes('.')) add_trailing_slash = false;
299
-
300
- return (
301
- content // allow users to specify characters on the file system in an encoded manner
302
- .normalize()
303
- // We use [ and ] to denote parameters, so users must encode these on the file
304
- // system to match against them. We don't decode all characters since others
305
- // can already be epressed and so that '%' can be easily used directly in filenames
306
- .replace(/%5[Bb]/g, '[')
307
- .replace(/%5[Dd]/g, ']')
308
- // '#', '/', and '?' can only appear in URL path segments in an encoded manner.
309
- // They will not be touched by decodeURI so need to be encoded here, so
310
- // that we can match against them.
311
- // We skip '/' since you can't create a file with it on any OS
312
- .replace(/#/g, '%23')
313
- .replace(/\?/g, '%3F')
314
- // escape characters that have special meaning in regex
315
- .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
316
- ); // TODO handle encoding
317
- })
318
- .join('')
287
+ segment
288
+ .split(/\[(.+?)\]/)
289
+ .map((content, i) => {
290
+ if (i % 2) {
291
+ const [, rest, name, type] = /** @type {RegExpMatchArray} */ (
292
+ param_pattern.exec(content)
293
+ );
294
+ names.push(name);
295
+ types.push(type);
296
+ return rest ? '(.*?)' : '([^/]+?)';
297
+ }
298
+
299
+ if (is_last && content.includes('.')) add_trailing_slash = false;
300
+
301
+ return (
302
+ content // allow users to specify characters on the file system in an encoded manner
303
+ .normalize()
304
+ // We use [ and ] to denote parameters, so users must encode these on the file
305
+ // system to match against them. We don't decode all characters since others
306
+ // can already be epressed and so that '%' can be easily used directly in filenames
307
+ .replace(/%5[Bb]/g, '[')
308
+ .replace(/%5[Dd]/g, ']')
309
+ // '#', '/', and '?' can only appear in URL path segments in an encoded manner.
310
+ // They will not be touched by decodeURI so need to be encoded here, so
311
+ // that we can match against them.
312
+ // We skip '/' since you can't create a file with it on any OS
313
+ .replace(/#/g, '%23')
314
+ .replace(/\?/g, '%3F')
315
+ // escape characters that have special meaning in regex
316
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
317
+ ); // TODO handle encoding
318
+ })
319
+ .join('')
319
320
  );
320
321
  })
321
322
  .join('')}${add_trailing_slash ? '/?' : ''}$`
@@ -2181,7 +2181,8 @@ async function respond$1(opts) {
2181
2181
 
2182
2182
  try {
2183
2183
  nodes = await Promise.all(
2184
- route.a.map((n) => options.manifest._.nodes[n] && options.manifest._.nodes[n]())
2184
+ // we use == here rather than === because [undefined] serializes as "[null]"
2185
+ route.a.map((n) => (n == undefined ? n : options.manifest._.nodes[n]()))
2185
2186
  );
2186
2187
  } catch (err) {
2187
2188
  const error = coalesce_to_error(err);
@@ -2280,7 +2281,8 @@ async function respond$1(opts) {
2280
2281
  if (error) {
2281
2282
  while (i--) {
2282
2283
  if (route.b[i]) {
2283
- const error_node = await options.manifest._.nodes[route.b[i]]();
2284
+ const index = /** @type {number} */ (route.b[i]);
2285
+ const error_node = await options.manifest._.nodes[index]();
2284
2286
 
2285
2287
  /** @type {Loaded} */
2286
2288
  let node_loaded;
@@ -12,6 +12,7 @@ import { getRequest, setResponse } from '../node.js';
12
12
  import { sequence } from '../hooks.js';
13
13
  import { p as posixify } from './filesystem.js';
14
14
  import { p as parse_route_id } from './misc.js';
15
+ import { n as normalize_path } from './url.js';
15
16
  import 'sade';
16
17
  import 'child_process';
17
18
  import 'net';
@@ -73,7 +74,7 @@ async function create_plugin(config, cwd) {
73
74
  const url = id.startsWith('..') ? `/@fs${path__default.posix.resolve(id)}` : `/${id}`;
74
75
 
75
76
  const module = /** @type {import('types').SSRComponent} */ (
76
- await vite.ssrLoadModule(url)
77
+ await vite.ssrLoadModule(url, { fixStacktrace: false })
77
78
  );
78
79
  const node = await vite.moduleGraph.getModuleByUrl(url);
79
80
 
@@ -95,7 +96,7 @@ async function create_plugin(config, cwd) {
95
96
  (query.has('svelte') && query.get('type') === 'style')
96
97
  ) {
97
98
  try {
98
- const mod = await vite.ssrLoadModule(dep.url);
99
+ const mod = await vite.ssrLoadModule(dep.url, { fixStacktrace: false });
99
100
  styles[dep.url] = mod.default;
100
101
  } catch {
101
102
  // this can happen with dynamically imported modules, I think
@@ -128,11 +129,11 @@ async function create_plugin(config, cwd) {
128
129
  shadow: route.shadow
129
130
  ? async () => {
130
131
  const url = path__default.resolve(cwd, /** @type {string} */ (route.shadow));
131
- return await vite.ssrLoadModule(url);
132
+ return await vite.ssrLoadModule(url, { fixStacktrace: false });
132
133
  }
133
134
  : null,
134
- a: route.a.map((id) => manifest_data.components.indexOf(id)),
135
- b: route.b.map((id) => manifest_data.components.indexOf(id))
135
+ a: route.a.map((id) => (id ? manifest_data.components.indexOf(id) : undefined)),
136
+ b: route.b.map((id) => (id ? manifest_data.components.indexOf(id) : undefined))
136
137
  };
137
138
  }
138
139
 
@@ -144,7 +145,7 @@ async function create_plugin(config, cwd) {
144
145
  types,
145
146
  load: async () => {
146
147
  const url = path__default.resolve(cwd, route.file);
147
- return await vite.ssrLoadModule(url);
148
+ return await vite.ssrLoadModule(url, { fixStacktrace: false });
148
149
  }
149
150
  };
150
151
  }),
@@ -155,7 +156,7 @@ async function create_plugin(config, cwd) {
155
156
  for (const key in manifest_data.matchers) {
156
157
  const file = manifest_data.matchers[key];
157
158
  const url = path__default.resolve(cwd, file);
158
- const module = await vite.ssrLoadModule(url);
159
+ const module = await vite.ssrLoadModule(url, { fixStacktrace: false });
159
160
 
160
161
  if (module.match) {
161
162
  matchers[key] = module.match;
@@ -172,20 +173,7 @@ async function create_plugin(config, cwd) {
172
173
 
173
174
  /** @param {Error} error */
174
175
  function fix_stack_trace(error) {
175
- // TODO https://github.com/vitejs/vite/issues/7045
176
-
177
- // ideally vite would expose ssrRewriteStacktrace, but
178
- // in lieu of that, we can implement it ourselves. we
179
- // don't want to mutate the error object, because
180
- // the stack trace could be 'fixed' multiple times,
181
- // and Vite will fix stack traces before we even
182
- // see them if they occur during ssrLoadModule
183
- const original = error.stack;
184
- vite.ssrFixStacktrace(error);
185
- const fixed = error.stack;
186
- error.stack = original;
187
-
188
- return fixed;
176
+ return error.stack ? vite.ssrRewriteStacktrace(error.stack) : error.stack;
189
177
  }
190
178
 
191
179
  update_manifest();
@@ -227,11 +215,17 @@ async function create_plugin(config, cwd) {
227
215
 
228
216
  if (req.url === '/favicon.ico') return not_found(res);
229
217
 
230
- if (!decoded.startsWith(config.kit.paths.base)) return not_found(res);
218
+ if (!decoded.startsWith(config.kit.paths.base)) {
219
+ const suggestion = normalize_path(
220
+ config.kit.paths.base + req.url,
221
+ config.kit.trailingSlash
222
+ );
223
+ return not_found(res, `Not found (did you mean ${suggestion}?)`);
224
+ }
231
225
 
232
226
  /** @type {Partial<import('types').Hooks>} */
233
227
  const user_hooks = resolve_entry(config.kit.files.hooks)
234
- ? await vite.ssrLoadModule(`/${config.kit.files.hooks}`)
228
+ ? await vite.ssrLoadModule(`/${config.kit.files.hooks}`, { fixStacktrace: false })
235
229
  : {};
236
230
 
237
231
  const handle = user_hooks.handle || (({ event, resolve }) => resolve(event));
@@ -271,13 +265,15 @@ async function create_plugin(config, cwd) {
271
265
  // can get loaded twice via different URLs, which causes failures. Might
272
266
  // require changes to Vite to fix
273
267
  const { default: root } = await vite.ssrLoadModule(
274
- `/${posixify(path__default.relative(cwd, `${config.kit.outDir}/generated/root.svelte`))}`
268
+ `/${posixify(path__default.relative(cwd, `${config.kit.outDir}/generated/root.svelte`))}`,
269
+ { fixStacktrace: false }
275
270
  );
276
271
 
277
272
  const paths = await vite.ssrLoadModule(
278
273
  true
279
274
  ? `/${posixify(path__default.relative(cwd, `${config.kit.outDir}/runtime/paths.js`))}`
280
- : `/@fs${runtime}/paths.js`
275
+ : `/@fs${runtime}/paths.js`,
276
+ { fixStacktrace: false }
281
277
  );
282
278
 
283
279
  paths.set_paths({
@@ -381,9 +377,9 @@ async function create_plugin(config, cwd) {
381
377
  }
382
378
 
383
379
  /** @param {import('http').ServerResponse} res */
384
- function not_found(res) {
380
+ function not_found(res, message = 'Not found') {
385
381
  res.statusCode = 404;
386
- res.end('Not found');
382
+ res.end(message);
387
383
  }
388
384
 
389
385
  /**
@@ -71,6 +71,7 @@ async function build_service_worker(
71
71
 
72
72
  export const files = [
73
73
  ${manifest_data.assets
74
+ .filter((asset) => config.kit.serviceWorker.files(asset.file))
74
75
  .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`)
75
76
  .join(',\n\t\t\t\t')}
76
77
  ];
@@ -1003,10 +1004,6 @@ async function prerender({ config, entries, files, log }) {
1003
1004
  paths: []
1004
1005
  };
1005
1006
 
1006
- if (!config.kit.prerender.enabled) {
1007
- return prerendered;
1008
- }
1009
-
1010
1007
  installFetch();
1011
1008
 
1012
1009
  const server_root = join(config.kit.outDir, 'output');
@@ -1023,6 +1020,23 @@ async function prerender({ config, entries, files, log }) {
1023
1020
 
1024
1021
  const server = new Server(manifest);
1025
1022
 
1023
+ const rendered = await server.respond(new Request('http://sveltekit-prerender/[fallback]'), {
1024
+ getClientAddress,
1025
+ prerender: {
1026
+ fallback: true,
1027
+ default: false,
1028
+ dependencies: new Map()
1029
+ }
1030
+ });
1031
+
1032
+ const file = `${config.kit.outDir}/output/prerendered/fallback.html`;
1033
+ mkdirp(dirname(file));
1034
+ writeFileSync(file, await rendered.text());
1035
+
1036
+ if (!config.kit.prerender.enabled) {
1037
+ return prerendered;
1038
+ }
1039
+
1026
1040
  const error = normalise_error_handler(log, config.kit.prerender.onError);
1027
1041
 
1028
1042
  const q = queue(config.kit.prerender.concurrency);
@@ -1214,19 +1228,6 @@ async function prerender({ config, entries, files, log }) {
1214
1228
  await q.done();
1215
1229
  }
1216
1230
 
1217
- const rendered = await server.respond(new Request('http://sveltekit-prerender/[fallback]'), {
1218
- getClientAddress,
1219
- prerender: {
1220
- fallback: true,
1221
- default: false,
1222
- dependencies: new Map()
1223
- }
1224
- });
1225
-
1226
- const file = `${config.kit.outDir}/output/prerendered/fallback.html`;
1227
- mkdirp(dirname(file));
1228
- writeFileSync(file, await rendered.text());
1229
-
1230
1231
  return prerendered;
1231
1232
  }
1232
1233
 
@@ -54,7 +54,7 @@ function generate_manifest({ build_data, relative_path, routes, format = 'esm' }
54
54
  assets.push(build_data.service_worker);
55
55
  }
56
56
 
57
- /** @param {string} id */
57
+ /** @param {string | undefined} id */
58
58
  const get_index = (id) => id && /** @type {LookupEntry} */ (bundled_nodes.get(id)).index;
59
59
 
60
60
  const matchers = new Set();
@@ -17,7 +17,7 @@ function parse_route_id(id) {
17
17
  ? /^\/$/
18
18
  : new RegExp(
19
19
  `^${decodeURIComponent(id)
20
- .split('/')
20
+ .split(/(?:@(?:~|[a-zA-Z0-9_-]*))?(?:\/|$)/)
21
21
  .map((segment, i, segments) => {
22
22
  // special case — /[...rest]/ could contain zero segments
23
23
  const match = /^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(segment);
@@ -30,40 +30,41 @@ function parse_route_id(id) {
30
30
  const is_last = i === segments.length - 1;
31
31
 
32
32
  return (
33
+ segment &&
33
34
  '/' +
34
- segment
35
- .split(/\[(.+?)\]/)
36
- .map((content, i) => {
37
- if (i % 2) {
38
- const [, rest, name, type] = /** @type {RegExpMatchArray} */ (
39
- param_pattern.exec(content)
40
- );
41
- names.push(name);
42
- types.push(type);
43
- return rest ? '(.*?)' : '([^/]+?)';
44
- }
35
+ segment
36
+ .split(/\[(.+?)\]/)
37
+ .map((content, i) => {
38
+ if (i % 2) {
39
+ const [, rest, name, type] = /** @type {RegExpMatchArray} */ (
40
+ param_pattern.exec(content)
41
+ );
42
+ names.push(name);
43
+ types.push(type);
44
+ return rest ? '(.*?)' : '([^/]+?)';
45
+ }
45
46
 
46
- if (is_last && content.includes('.')) add_trailing_slash = false;
47
+ if (is_last && content.includes('.')) add_trailing_slash = false;
47
48
 
48
- return (
49
- content // allow users to specify characters on the file system in an encoded manner
50
- .normalize()
51
- // We use [ and ] to denote parameters, so users must encode these on the file
52
- // system to match against them. We don't decode all characters since others
53
- // can already be epressed and so that '%' can be easily used directly in filenames
54
- .replace(/%5[Bb]/g, '[')
55
- .replace(/%5[Dd]/g, ']')
56
- // '#', '/', and '?' can only appear in URL path segments in an encoded manner.
57
- // They will not be touched by decodeURI so need to be encoded here, so
58
- // that we can match against them.
59
- // We skip '/' since you can't create a file with it on any OS
60
- .replace(/#/g, '%23')
61
- .replace(/\?/g, '%3F')
62
- // escape characters that have special meaning in regex
63
- .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
64
- ); // TODO handle encoding
65
- })
66
- .join('')
49
+ return (
50
+ content // allow users to specify characters on the file system in an encoded manner
51
+ .normalize()
52
+ // We use [ and ] to denote parameters, so users must encode these on the file
53
+ // system to match against them. We don't decode all characters since others
54
+ // can already be epressed and so that '%' can be easily used directly in filenames
55
+ .replace(/%5[Bb]/g, '[')
56
+ .replace(/%5[Dd]/g, ']')
57
+ // '#', '/', and '?' can only appear in URL path segments in an encoded manner.
58
+ // They will not be touched by decodeURI so need to be encoded here, so
59
+ // that we can match against them.
60
+ // We skip '/' since you can't create a file with it on any OS
61
+ .replace(/#/g, '%23')
62
+ .replace(/\?/g, '%3F')
63
+ // escape characters that have special meaning in regex
64
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
65
+ ); // TODO handle encoding
66
+ })
67
+ .join('')
67
68
  );
68
69
  })
69
70
  .join('')}${add_trailing_slash ? '/?' : ''}$`
@@ -121,18 +121,39 @@ var mime = new Mime(standard, other);
121
121
  * rest: boolean;
122
122
  * type: string | null;
123
123
  * }} Part
124
+ */
125
+
126
+ /**
127
+ * A route, consisting of an endpoint module and/or an array of components
128
+ * (n layouts and one leaf) for successful navigations and an array of
129
+ * n error components to render if navigation fails
124
130
  * @typedef {{
125
- * name: string;
126
- * parts: Part[],
127
- * file: string;
128
- * is_dir: boolean;
129
- * is_index: boolean;
130
- * is_page: boolean;
131
- * route_suffix: string
132
- * }} Item
131
+ * id: string;
132
+ * pattern: RegExp;
133
+ * segments: Part[][];
134
+ * page?: {
135
+ * a: Array<string | undefined>;
136
+ * b: Array<string | undefined>;
137
+ * };
138
+ * endpoint?: string;
139
+ * }} Unit
133
140
  */
134
141
 
135
- const specials = new Set(['__layout', '__layout.reset', '__error']);
142
+ /**
143
+ * @typedef {{
144
+ * error: string | undefined;
145
+ * layouts: Record<string, { file: string, name: string }>
146
+ * }} Node
147
+ */
148
+
149
+ /**
150
+ * @typedef {Map<string, Node>} Tree
151
+ */
152
+
153
+ const layout_pattern = /^__layout(?:-([a-zA-Z0-9_-]+))?(?:@([a-zA-Z0-9_-]+))?$/;
154
+ const dunder_pattern = /(^|\/)__(?!tests?__)/; // forbid __-prefixed files/directories except __error, __layout[-...], __test__, __tests__
155
+
156
+ const DEFAULT = 'default';
136
157
 
137
158
  /**
138
159
  * @param {{
@@ -147,172 +168,199 @@ function create_manifest_data({
147
168
  fallback = `${get_runtime_path(config)}/components`,
148
169
  cwd = process.cwd()
149
170
  }) {
150
- /**
151
- * @param {string} file_name
152
- * @param {string} dir
153
- */
154
- function find_layout(file_name, dir) {
155
- const files = config.extensions.map((ext) => posixify(path__default.join(dir, `${file_name}${ext}`)));
156
- return files.find((file) => fs__default.existsSync(path__default.resolve(cwd, file)));
157
- }
158
-
159
- /** @type {string[]} */
160
- const components = [];
161
-
162
171
  /** @type {import('types').RouteData[]} */
163
172
  const routes = [];
164
173
 
165
- const default_layout = posixify(path__default.relative(cwd, `${fallback}/layout.svelte`));
166
- const default_error = posixify(path__default.relative(cwd, `${fallback}/error.svelte`));
167
-
168
- /**
169
- * @param {string} dir
170
- * @param {string[]} parent_id
171
- * @param {Array<string|undefined>} layout_stack // accumulated __layout.svelte components
172
- * @param {Array<string|undefined>} error_stack // accumulated __error.svelte components
173
- */
174
- function walk(dir, parent_id, layout_stack, error_stack) {
175
- /** @type {Item[]} */
176
- const items = [];
174
+ /** @type {Map<string, Unit>} */
175
+ const units = new Map();
177
176
 
178
- fs__default.readdirSync(dir).forEach((basename) => {
179
- const resolved = path__default.join(dir, basename);
180
- const file = posixify(path__default.relative(cwd, resolved));
181
- const is_dir = fs__default.statSync(resolved).isDirectory();
177
+ /** @type {Tree} */
178
+ const tree = new Map();
182
179
 
183
- const ext = is_dir
184
- ? ''
185
- : config.extensions.find((ext) => basename.endsWith(ext)) ||
186
- config.kit.endpointExtensions.find((ext) => basename.endsWith(ext));
180
+ const default_layout = {
181
+ file: posixify(path__default.relative(cwd, `${fallback}/layout.svelte`)),
182
+ name: DEFAULT
183
+ };
187
184
 
188
- if (ext === undefined) return;
185
+ // set default root layout/error
186
+ tree.set('', {
187
+ error: posixify(path__default.relative(cwd, `${fallback}/error.svelte`)),
188
+ layouts: { [DEFAULT]: default_layout }
189
+ });
189
190
 
190
- const name = basename.slice(0, basename.length - ext.length);
191
+ const routes_base = posixify(path__default.relative(cwd, config.kit.files.routes));
192
+ const valid_extensions = [...config.extensions, ...config.kit.endpointExtensions];
191
193
 
192
- if (name.startsWith('__') && !specials.has(name)) {
193
- throw new Error(`Files and directories prefixed with __ are reserved (saw ${file})`);
194
- }
194
+ list_files(config.kit.files.routes).forEach((file) => {
195
+ const extension = valid_extensions.find((ext) => file.endsWith(ext));
196
+ if (!extension) return;
195
197
 
196
- if (!config.kit.routes(file)) return;
198
+ const id = file.slice(0, -extension.length).replace(/\/?index(\.[a-z]+)?$/, '$1');
199
+ const project_relative = `${routes_base}/${file}`;
197
200
 
198
- items.push({
199
- file,
200
- name,
201
- parts: get_parts(name, file),
202
- route_suffix: basename.slice(basename.indexOf('.'), -ext.length),
203
- is_dir,
204
- is_index: !is_dir && basename.startsWith('index.'),
205
- is_page: config.extensions.includes(ext)
206
- });
207
- });
201
+ const segments = id.split('/');
202
+ const name = /** @type {string} */ (segments.pop());
208
203
 
209
- items.sort(comparator);
204
+ if (name === '__layout.reset') {
205
+ throw new Error(
206
+ '__layout.reset has been removed in favour of named layouts: https://kit.svelte.dev/docs/layouts#named-layouts'
207
+ );
208
+ }
210
209
 
211
- items.forEach((item) => {
212
- const id_parts = parent_id.slice();
210
+ if (name === '__error' || layout_pattern.test(name)) {
211
+ const dir = segments.join('/');
213
212
 
214
- if (item.is_index) {
215
- if (item.route_suffix && id_parts.length > 0) {
216
- id_parts[id_parts.length - 1] += item.route_suffix;
217
- }
218
- } else {
219
- id_parts.push(item.name);
213
+ if (!tree.has(dir)) {
214
+ tree.set(dir, {
215
+ error: undefined,
216
+ layouts: {}
217
+ });
220
218
  }
221
219
 
222
- if (item.is_dir) {
223
- const layout_reset = find_layout('__layout.reset', item.file);
224
- const layout = find_layout('__layout', item.file);
225
- const error = find_layout('__error', item.file);
220
+ const group = /** @type {Node} */ (tree.get(dir));
226
221
 
227
- if (layout_reset && layout) {
228
- throw new Error(`Cannot have __layout next to __layout.reset: ${layout_reset}`);
222
+ if (name === '__error') {
223
+ group.error = project_relative;
224
+ } else {
225
+ const match = /** @type {RegExpMatchArray} */ (layout_pattern.exec(name));
226
+
227
+ if (match[1] === DEFAULT) {
228
+ throw new Error(`${project_relative} cannot use reserved "${DEFAULT}" name`);
229
229
  }
230
230
 
231
- if (layout_reset) components.push(layout_reset);
232
- if (layout) components.push(layout);
233
- if (error) components.push(error);
231
+ const layout_id = match[1] || DEFAULT;
234
232
 
235
- walk(
236
- path__default.join(dir, item.name),
237
- id_parts,
238
- layout_reset ? [layout_reset] : layout_stack.concat(layout),
239
- layout_reset ? [error] : error_stack.concat(error)
240
- );
241
- } else {
242
- const id = id_parts.join('/');
243
- const { pattern } = parse_route_id(id);
233
+ const defined = group.layouts[layout_id];
234
+ if (defined && defined !== default_layout) {
235
+ throw new Error(
236
+ `Duplicate layout ${project_relative} already defined at ${defined.file}`
237
+ );
238
+ }
244
239
 
245
- if (item.is_page) {
246
- components.push(item.file);
240
+ group.layouts[layout_id] = {
241
+ file: project_relative,
242
+ name
243
+ };
244
+ }
247
245
 
248
- const concatenated = layout_stack.concat(item.file);
249
- const errors = error_stack.slice();
246
+ return;
247
+ } else if (dunder_pattern.test(file)) {
248
+ throw new Error(
249
+ `Files and directories prefixed with __ are reserved (saw ${project_relative})`
250
+ );
251
+ }
250
252
 
251
- let i = concatenated.length;
252
- while (i--) {
253
- if (!errors[i] && !concatenated[i]) {
254
- errors.splice(i, 1);
255
- concatenated.splice(i, 1);
256
- }
257
- }
253
+ if (!config.kit.routes(file)) return;
258
254
 
259
- i = errors.length;
260
- while (i--) {
261
- if (errors[i]) break;
262
- }
255
+ if (/\]\[/.test(id)) {
256
+ throw new Error(`Invalid route ${project_relative} — parameters must be separated`);
257
+ }
263
258
 
264
- errors.splice(i + 1);
259
+ if (count_occurrences('[', id) !== count_occurrences(']', id)) {
260
+ throw new Error(`Invalid route ${project_relative} — brackets are unbalanced`);
261
+ }
265
262
 
266
- const path = id.includes('[') ? '' : `/${id}`;
263
+ if (!units.has(id)) {
264
+ units.set(id, {
265
+ id,
266
+ pattern: parse_route_id(id).pattern,
267
+ segments: id
268
+ .split('/')
269
+ .filter(Boolean)
270
+ .map((segment) => {
271
+ /** @type {Part[]} */
272
+ const parts = [];
273
+ segment.split(/\[(.+?)\]/).map((content, i) => {
274
+ const dynamic = !!(i % 2);
275
+
276
+ if (!content) return;
277
+
278
+ parts.push({
279
+ content,
280
+ dynamic,
281
+ rest: dynamic && content.startsWith('...'),
282
+ type: (dynamic && content.split('=')[1]) || null
283
+ });
284
+ });
285
+ return parts;
286
+ }),
287
+ page: undefined,
288
+ endpoint: undefined
289
+ });
290
+ }
267
291
 
268
- routes.push({
269
- type: 'page',
270
- id,
271
- pattern,
272
- path,
273
- shadow: null,
274
- a: /** @type {string[]} */ (concatenated),
275
- b: /** @type {string[]} */ (errors)
276
- });
277
- } else {
278
- routes.push({
279
- type: 'endpoint',
280
- id,
281
- pattern,
282
- file: item.file
283
- });
284
- }
285
- }
286
- });
287
- }
292
+ const unit = /** @type {Unit} */ (units.get(id));
288
293
 
289
- const routes_base = path__default.relative(cwd, config.kit.files.routes);
294
+ if (config.extensions.find((ext) => file.endsWith(ext))) {
295
+ const { layouts, errors } = trace(project_relative, file, tree, config.extensions);
296
+ unit.page = {
297
+ a: layouts.concat(project_relative),
298
+ b: errors
299
+ };
300
+ } else {
301
+ unit.endpoint = project_relative;
302
+ }
303
+ });
290
304
 
291
- const layout = find_layout('__layout', routes_base) || default_layout;
292
- const error = find_layout('__error', routes_base) || default_error;
305
+ /** @type {string[]} */
306
+ const components = [];
293
307
 
294
- components.push(layout, error);
308
+ tree.forEach(({ layouts, error }) => {
309
+ // we do [default, error, ...other_layouts] so that components[0] and [1]
310
+ // are the root layout/error. kinda janky, there's probably a nicer way
311
+ if (layouts[DEFAULT]) {
312
+ components.push(layouts[DEFAULT].file);
313
+ }
295
314
 
296
- walk(config.kit.files.routes, [], [layout], [error]);
315
+ if (error) {
316
+ components.push(error);
317
+ }
297
318
 
298
- const lookup = new Map();
299
- for (const route of routes) {
300
- if (route.type === 'page') {
301
- lookup.set(route.id, route);
319
+ for (const id in layouts) {
320
+ if (id !== DEFAULT) components.push(layouts[id].file);
302
321
  }
303
- }
322
+ });
304
323
 
305
- let i = routes.length;
306
- while (i--) {
307
- const route = routes[i];
308
- if (route.type === 'endpoint' && lookup.has(route.id)) {
309
- lookup.get(route.id).shadow = route.file;
310
- routes.splice(i, 1);
324
+ units.forEach((unit) => {
325
+ if (unit.page) {
326
+ const leaf = /** @type {string} */ (unit.page.a[unit.page.a.length - 1]);
327
+ components.push(leaf);
311
328
  }
312
- }
329
+ });
330
+
331
+ Array.from(units.values())
332
+ .sort(compare)
333
+ .forEach((unit) => {
334
+ // TODO when we introduce layout endpoints and scoped middlewares, we
335
+ // will probably want to have a single unified route type here
336
+ // (created in the list_files(...).forEach(...) callback)
337
+ if (unit.page) {
338
+ routes.push({
339
+ type: 'page',
340
+ id: unit.id,
341
+ pattern: unit.pattern,
342
+ path: unit.id.includes('[') ? '' : `/${unit.id.replace(/@(?:[a-zA-Z0-9_-]+)/g, '')}`,
343
+ shadow: unit.endpoint || null,
344
+ a: unit.page.a,
345
+ b: unit.page.b
346
+ });
347
+ } else if (unit.endpoint) {
348
+ routes.push({
349
+ type: 'endpoint',
350
+ id: unit.id,
351
+ pattern: unit.pattern,
352
+ file: unit.endpoint
353
+ });
354
+ }
355
+ });
313
356
 
357
+ /** @type {import('types').Asset[]} */
314
358
  const assets = fs__default.existsSync(config.kit.files.assets)
315
- ? list_files({ config, dir: config.kit.files.assets, path: '' })
359
+ ? list_files(config.kit.files.assets).map((file) => ({
360
+ file,
361
+ size: fs__default.statSync(`${config.kit.files.assets}/${file}`).size,
362
+ type: mime.getType(file)
363
+ }))
316
364
  : [];
317
365
 
318
366
  const params_base = path__default.relative(cwd, config.kit.files.params);
@@ -336,8 +384,6 @@ function create_manifest_data({
336
384
 
337
385
  return {
338
386
  assets,
339
- layout,
340
- error,
341
387
  components,
342
388
  routes,
343
389
  matchers
@@ -345,142 +391,162 @@ function create_manifest_data({
345
391
  }
346
392
 
347
393
  /**
348
- * @param {string} needle
349
- * @param {string} haystack
394
+ * @param {string} file
395
+ * @param {string} path
396
+ * @param {Tree} tree
397
+ * @param {string[]} extensions
350
398
  */
351
- function count_occurrences(needle, haystack) {
352
- let count = 0;
353
- for (let i = 0; i < haystack.length; i += 1) {
354
- if (haystack[i] === needle) count += 1;
355
- }
356
- return count;
357
- }
358
-
359
- const spread_pattern = /\[\.{3}/;
399
+ function trace(file, path, tree, extensions) {
400
+ /** @type {Array<string | undefined>} */
401
+ const layouts = [];
402
+
403
+ /** @type {Array<string | undefined>} */
404
+ const errors = [];
405
+
406
+ const parts = path.split('/');
407
+ const filename = /** @type {string} */ (parts.pop());
408
+ const extension = /** @type {string} */ (extensions.find((ext) => path.endsWith(ext)));
409
+ const base = filename.slice(0, -extension.length);
410
+
411
+ let layout_id = base.includes('@') ? base.split('@')[1] : DEFAULT;
412
+
413
+ // walk up the tree, find which __layout and __error components
414
+ // apply to this page
415
+ // eslint-disable-next-line
416
+ while (true) {
417
+ const node = tree.get(parts.join('/'));
418
+ const layout = node?.layouts[layout_id];
419
+
420
+ // any segment that has neither a __layout nor an __error can be discarded.
421
+ // in other words these...
422
+ // layouts: [a, , b, c]
423
+ // errors: [d, , e, ]
424
+ //
425
+ // ...can be compacted to these:
426
+ // layouts: [a, b, c]
427
+ // errors: [d, e, ]
428
+ if (node?.error || layout?.file) {
429
+ errors.unshift(node?.error);
430
+ layouts.unshift(layout?.file);
431
+ }
360
432
 
361
- /**
362
- * @param {Item} a
363
- * @param {Item} b
364
- */
365
- function comparator(a, b) {
366
- if (a.is_index !== b.is_index) {
367
- if (a.is_index) return spread_pattern.test(a.file) ? 1 : -1;
368
- return spread_pattern.test(b.file) ? -1 : 1;
433
+ if (layout?.name.includes('@')) {
434
+ layout_id = layout.name.split('@')[1];
435
+ } else {
436
+ if (layout) layout_id = DEFAULT;
437
+ if (parts.length === 0) break;
438
+ parts.pop();
439
+ }
369
440
  }
370
441
 
371
- const max = Math.max(a.parts.length, b.parts.length);
442
+ if (layout_id !== DEFAULT) {
443
+ throw new Error(`${file} references missing layout "${layout_id}"`);
444
+ }
372
445
 
373
- for (let i = 0; i < max; i += 1) {
374
- const a_sub_part = a.parts[i];
375
- const b_sub_part = b.parts[i];
446
+ // trim empty space off the end of the errors array
447
+ let i = errors.length;
448
+ while (i--) if (errors[i]) break;
449
+ errors.length = i + 1;
376
450
 
377
- if (!a_sub_part) return 1; // b is more specific, so goes first
378
- if (!b_sub_part) return -1;
451
+ return { layouts, errors };
452
+ }
379
453
 
380
- if (a_sub_part.rest && b_sub_part.rest) {
381
- if (a.is_page !== b.is_page) {
382
- return a.is_page ? 1 : -1;
454
+ /**
455
+ * @param {Unit} a
456
+ * @param {Unit} b
457
+ */
458
+ function compare(a, b) {
459
+ const max_segments = Math.max(a.segments.length, b.segments.length);
460
+ for (let i = 0; i < max_segments; i += 1) {
461
+ const sa = a.segments[i];
462
+ const sb = b.segments[i];
463
+
464
+ // /x < /x/y, but /[...x]/y < /[...x]
465
+ if (!sa) return a.id.includes('[...') ? +1 : -1;
466
+ if (!sb) return b.id.includes('[...') ? -1 : +1;
467
+
468
+ const max_parts = Math.max(sa.length, sb.length);
469
+ for (let i = 0; i < max_parts; i += 1) {
470
+ const pa = sa[i];
471
+ const pb = sb[i];
472
+
473
+ // xy < x[y], but [x].json < [x]
474
+ if (pa === undefined) return pb.dynamic ? -1 : +1;
475
+ if (pb === undefined) return pa.dynamic ? +1 : -1;
476
+
477
+ // x < [x]
478
+ if (pa.dynamic !== pb.dynamic) {
479
+ return pa.dynamic ? +1 : -1;
383
480
  }
384
- // sort alphabetically
385
- return a_sub_part.content < b_sub_part.content ? -1 : 1;
386
- }
387
-
388
- // If one is ...rest order it later
389
- if (a_sub_part.rest !== b_sub_part.rest) return a_sub_part.rest ? 1 : -1;
390
-
391
- if (a_sub_part.dynamic !== b_sub_part.dynamic) {
392
- return a_sub_part.dynamic ? 1 : -1;
393
- }
394
481
 
395
- if (a_sub_part.dynamic && !!a_sub_part.type !== !!b_sub_part.type) {
396
- return a_sub_part.type ? -1 : 1;
397
- }
482
+ if (pa.dynamic) {
483
+ // [x] < [...x]
484
+ if (pa.rest !== pb.rest) {
485
+ return pa.rest ? +1 : -1;
486
+ }
398
487
 
399
- if (!a_sub_part.dynamic && a_sub_part.content !== b_sub_part.content) {
400
- return (
401
- b_sub_part.content.length - a_sub_part.content.length ||
402
- (a_sub_part.content < b_sub_part.content ? -1 : 1)
403
- );
488
+ // [x=type] < [x]
489
+ if (!!pa.type !== !!pb.type) {
490
+ return pa.type ? -1 : +1;
491
+ }
492
+ }
404
493
  }
405
494
  }
406
495
 
407
- if (a.is_page !== b.is_page) {
408
- return a.is_page ? 1 : -1;
496
+ const a_is_endpoint = !a.page && a.endpoint;
497
+ const b_is_endpoint = !b.page && b.endpoint;
498
+
499
+ if (a_is_endpoint !== b_is_endpoint) {
500
+ return a_is_endpoint ? -1 : +1;
409
501
  }
410
502
 
411
- // otherwise sort alphabetically
412
- return a.file < b.file ? -1 : 1;
503
+ return a < b ? -1 : 1;
413
504
  }
414
505
 
415
506
  /**
416
- * @param {string} part
417
- * @param {string} file
507
+ * @param {string} needle
508
+ * @param {string} haystack
418
509
  */
419
- function get_parts(part, file) {
420
- if (/\]\[/.test(part)) {
421
- throw new Error(`Invalid route ${file} parameters must be separated`);
422
- }
423
-
424
- if (count_occurrences('[', part) !== count_occurrences(']', part)) {
425
- throw new Error(`Invalid route ${file} — brackets are unbalanced`);
510
+ function count_occurrences(needle, haystack) {
511
+ let count = 0;
512
+ for (let i = 0; i < haystack.length; i += 1) {
513
+ if (haystack[i] === needle) count += 1;
426
514
  }
427
-
428
- /** @type {Part[]} */
429
- const result = [];
430
- part.split(/\[(.+?\(.+?\)|.+?)\]/).map((str, i) => {
431
- if (!str) return;
432
- const dynamic = i % 2 === 1;
433
-
434
- const [, content, type] = dynamic
435
- ? /^((?:\.\.\.)?[a-zA-Z_][a-zA-Z0-9_]*)(?:=([a-zA-Z_][a-zA-Z0-9_]*))?$/.exec(str) || [
436
- null,
437
- null,
438
- null
439
- ]
440
- : [null, str, null];
441
-
442
- if (!content) {
443
- throw new Error(
444
- `Invalid route ${file} — parameter name and type must match /^[a-zA-Z_][a-zA-Z0-9_]*$/`
445
- );
446
- }
447
-
448
- result.push({
449
- content,
450
- dynamic,
451
- rest: dynamic && /^\.{3}.+$/.test(content),
452
- type
453
- });
454
- });
455
-
456
- return result;
515
+ return count;
457
516
  }
458
517
 
459
518
  /**
460
- * @param {{
461
- * config: import('types').ValidatedConfig;
462
- * dir: string;
463
- * path: string;
464
- * files?: import('types').Asset[]
465
- * }} args
519
+ * @param {string} dir
520
+ * @param {string} [path]
521
+ * @param {string[]} [files]
466
522
  */
467
- function list_files({ config, dir, path, files = [] }) {
468
- fs__default.readdirSync(dir).forEach((file) => {
469
- const full = `${dir}/${file}`;
470
-
471
- const stats = fs__default.statSync(full);
472
- const joined = path ? `${path}/${file}` : file;
473
-
474
- if (stats.isDirectory()) {
475
- list_files({ config, dir: full, path: joined, files });
476
- } else if (config.kit.serviceWorker.files(joined)) {
477
- files.push({
478
- file: joined,
479
- size: stats.size,
480
- type: mime.getType(joined)
481
- });
482
- }
483
- });
523
+ function list_files(dir, path = '', files = []) {
524
+ fs__default.readdirSync(dir, { withFileTypes: true })
525
+ .sort(({ name: a }, { name: b }) => {
526
+ // sort each directory in (__layout, __error, everything else) order
527
+ // so that we can trace layouts/errors immediately
528
+
529
+ if (a.startsWith('__layout')) {
530
+ if (!b.startsWith('__layout')) return -1;
531
+ } else if (b.startsWith('__layout')) {
532
+ return 1;
533
+ } else if (a.startsWith('__')) {
534
+ if (!b.startsWith('__')) return -1;
535
+ } else if (b.startsWith('__')) {
536
+ return 1;
537
+ }
538
+
539
+ return a < b ? -1 : 1;
540
+ })
541
+ .forEach((file) => {
542
+ const joined = path ? `${path}/${file.name}` : file.name;
543
+
544
+ if (file.isDirectory()) {
545
+ list_files(`${dir}/${file.name}`, joined, files);
546
+ } else {
547
+ files.push(joined);
548
+ }
549
+ });
484
550
 
485
551
  return files;
486
552
  }
@@ -549,7 +615,7 @@ function write_manifest(manifest_data, base, output) {
549
615
  .join(',\n\t\t\t\t\t')}
550
616
  ]`.replace(/^\t/gm, '');
551
617
 
552
- /** @param {string[]} parts */
618
+ /** @param {Array<string | undefined>} parts */
553
619
  const get_indices = (parts) =>
554
620
  `[${parts.map((part) => (part ? component_indexes[part] : '')).join(', ')}]`;
555
621
 
@@ -833,21 +899,6 @@ function write_types(config, manifest_data) {
833
899
  /** @type {Map<string, { params: string[], type: 'page' | 'endpoint' | 'both' }>} */
834
900
  const shadow_types = new Map();
835
901
 
836
- /** @param {string} key */
837
- function extract_params(key) {
838
- /** @type {string[]} */
839
- const params = [];
840
-
841
- const pattern = /\[(?:\.{3})?([^\]]+)\]/g;
842
- let match;
843
-
844
- while ((match = pattern.exec(key))) {
845
- params.push(match[1]);
846
- }
847
-
848
- return params;
849
- }
850
-
851
902
  manifest_data.routes.forEach((route) => {
852
903
  const file = route.type === 'endpoint' ? route.file : route.shadow;
853
904
 
@@ -857,7 +908,7 @@ function write_types(config, manifest_data) {
857
908
  );
858
909
  const key = file.slice(0, -ext.length);
859
910
  shadow_types.set(key, {
860
- params: extract_params(key),
911
+ params: parse_route_id(key).names,
861
912
  type: route.type === 'endpoint' ? 'endpoint' : 'both'
862
913
  });
863
914
  }
@@ -870,7 +921,7 @@ function write_types(config, manifest_data) {
870
921
  const key = component.slice(0, -ext.length);
871
922
 
872
923
  if (!shadow_types.has(key)) {
873
- shadow_types.set(key, { params: extract_params(key), type: 'page' });
924
+ shadow_types.set(key, { params: parse_route_id(key).names, type: 'page' });
874
925
  }
875
926
  });
876
927
 
package/dist/cli.js CHANGED
@@ -853,8 +853,9 @@ function handle_error(e) {
853
853
  /**
854
854
  * @param {number} port
855
855
  * @param {boolean} https
856
+ * @param {string} base
856
857
  */
857
- async function launch(port, https) {
858
+ async function launch(port, https, base) {
858
859
  const { exec } = await import('child_process');
859
860
  let cmd = 'open';
860
861
  if (process.platform == 'win32') {
@@ -866,10 +867,10 @@ async function launch(port, https) {
866
867
  cmd = 'xdg-open';
867
868
  }
868
869
  }
869
- exec(`${cmd} ${https ? 'https' : 'http'}://localhost:${port}`);
870
+ exec(`${cmd} ${https ? 'https' : 'http'}://localhost:${port}${base}`);
870
871
  }
871
872
 
872
- const prog = sade('svelte-kit').version('1.0.0-next.303');
873
+ const prog = sade('svelte-kit').version('1.0.0-next.306');
873
874
 
874
875
  prog
875
876
  .command('dev')
@@ -903,6 +904,7 @@ prog
903
904
  host: address_info.address,
904
905
  https: !!(https || server_config.https),
905
906
  open: open || !!server_config.open,
907
+ base: config.kit.paths.base,
906
908
  loose: server_config.fs.strict === false,
907
909
  allow: server_config.fs.allow,
908
910
  cwd
@@ -970,7 +972,7 @@ prog
970
972
 
971
973
  await preview({ port, host, config, https });
972
974
 
973
- welcome({ port, host, https, open });
975
+ welcome({ port, host, https, open, base: config.kit.paths.base });
974
976
  } catch (error) {
975
977
  handle_error(error);
976
978
  }
@@ -1039,15 +1041,16 @@ async function check_port(port) {
1039
1041
  * host: string;
1040
1042
  * https: boolean;
1041
1043
  * port: number;
1044
+ * base: string;
1042
1045
  * loose?: boolean;
1043
1046
  * allow?: string[];
1044
1047
  * cwd?: string;
1045
1048
  * }} param0
1046
1049
  */
1047
- function welcome({ port, host, https, open, loose, allow, cwd }) {
1048
- if (open) launch(port, https);
1050
+ function welcome({ port, host, https, open, base, loose, allow, cwd }) {
1051
+ if (open) launch(port, https, base);
1049
1052
 
1050
- console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.303'}\n`));
1053
+ console.log($.bold().cyan(`\n SvelteKit v${'1.0.0-next.306'}\n`));
1051
1054
 
1052
1055
  const protocol = https ? 'https:' : 'http:';
1053
1056
  const exposed = typeof host !== 'undefined' && host !== 'localhost' && host !== '127.0.0.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.303",
3
+ "version": "1.0.0-next.306",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -12,10 +12,10 @@
12
12
  "dependencies": {
13
13
  "@sveltejs/vite-plugin-svelte": "^1.0.0-next.32",
14
14
  "sade": "^1.7.4",
15
- "vite": "^2.8.0"
15
+ "vite": "^2.9.0"
16
16
  },
17
17
  "devDependencies": {
18
- "@playwright/test": "^1.19.1",
18
+ "@playwright/test": "^1.20.2",
19
19
  "@rollup/plugin-replace": "^4.0.0",
20
20
  "@types/amphtml-validator": "^1.0.1",
21
21
  "@types/cookie": "^0.4.1",
package/types/index.d.ts CHANGED
@@ -184,7 +184,7 @@ export interface HandleError {
184
184
  }
185
185
 
186
186
  /**
187
- * The type of a `load` function exported from `<script context="module">` in a page or layout.
187
+ * The `(input: LoadInput) => LoadOutput` `load` function exported from `<script context="module">` in a page or layout.
188
188
  *
189
189
  * Note that you can use [generated types](/docs/types#generated-types) instead of manually specifying the Params generic argument.
190
190
  */
@@ -215,13 +215,9 @@ export interface ParamMatcher {
215
215
  }
216
216
 
217
217
  /**
218
- * A function exported from an endpoint that corresponds to an
219
- * HTTP verb (`get`, `put`, `patch`, etc) and handles requests with
220
- * that method. Note that since 'delete' is a reserved word in
221
- * JavaScript, delete handles are called `del` instead.
218
+ * A `(event: RequestEvent) => RequestHandlerOutput` function exported from an endpoint that corresponds to an HTTP verb (`get`, `put`, `patch`, etc) and handles requests with that method. Note that since 'delete' is a reserved word in JavaScript, delete handles are called `del` instead.
222
219
  *
223
- * Note that you can use [generated types](/docs/types#generated-types)
224
- * instead of manually specifying the `Params` generic argument.
220
+ * Note that you can use [generated types](/docs/types#generated-types) instead of manually specifying the `Params` generic argument.
225
221
  */
226
222
  export interface RequestHandler<
227
223
  Params extends Record<string, string> = Record<string, string>,
@@ -18,7 +18,6 @@ import {
18
18
  RequestOptions,
19
19
  ResolveOptions,
20
20
  ResponseHeaders,
21
- RouteSegment,
22
21
  TrailingSlash
23
22
  } from './private';
24
23
 
@@ -102,8 +101,6 @@ export class InternalServer extends Server {
102
101
 
103
102
  export interface ManifestData {
104
103
  assets: Asset[];
105
- layout: string;
106
- error: string;
107
104
  components: string[];
108
105
  routes: RouteData[];
109
106
  matchers: Record<string, string>;
@@ -129,8 +126,8 @@ export interface PageData {
129
126
  shadow: string | null;
130
127
  pattern: RegExp;
131
128
  path: string;
132
- a: string[];
133
- b: string[];
129
+ a: Array<string | undefined>;
130
+ b: Array<string | undefined>;
134
131
  }
135
132
 
136
133
  export type PayloadScriptAttributes =
@@ -286,12 +283,12 @@ export interface SSRPage {
286
283
  /**
287
284
  * plan a is to render 1 or more layout components followed by a leaf component.
288
285
  */
289
- a: number[];
286
+ a: Array<number | undefined>;
290
287
  /**
291
288
  * plan b — if one of them components fails in `load` we backtrack until we find
292
289
  * the nearest error component.
293
290
  */
294
- b: number[];
291
+ b: Array<number | undefined>;
295
292
  }
296
293
 
297
294
  export interface SSRPagePart {
@@ -2,8 +2,6 @@
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 { ValidatedConfig } from './internal';
6
-
7
5
  export interface AdapterEntry {
8
6
  /**
9
7
  * A string that uniquely identifies an HTTP service (e.g. serverless function) and is used for deduplication.
@@ -191,8 +189,6 @@ export interface Logger {
191
189
 
192
190
  export type MaybePromise<T> = T | Promise<T>;
193
191
 
194
- export type Only<T, U> = { [P in keyof T]: T[P] } & { [P in Exclude<keyof U, keyof T>]?: never };
195
-
196
192
  export interface Prerendered {
197
193
  pages: Map<
198
194
  string,