Fix spread routes on server side
MODIFY
packages/app-utils/src/renderer/render/page.ts
packages/app-utils/src/renderer/render/page.ts
-import { URLSearchParams } from 'url';
-import { existsSync, createReadStream } from 'fs';
-import { readable, writable } from 'svelte/store';
-import { parse, resolve } from 'url';
import devalue from 'devalue';
-import fetch, { Response } from 'node-fetch';
+import { createReadStream, existsSync } from 'fs';
import * as mime from 'mime';
+import fetch, { Response } from 'node-fetch';
+import { readable, writable } from 'svelte/store';
+import { parse, resolve, URLSearchParams } from 'url';
+import {
+ EndpointResponse,
+ Headers,
+ IncomingRequest,
+ PageManifest,
+ PageManifestPart,
+ PageResponse,
+ RenderOptions,
+ RouteParams
+} from '../../types';
import { render } from './index';
-import { IncomingRequest, RenderOptions, PageManifest, EndpointResponse, PageResponse, Headers } from '../../types';
const noop = () => {};
@@ -136,11 +144,8 @@ export default async function render_page(
const match = page.pattern.exec(request.path)!;
- // TODO this logic is duplicated in several places
- const params: Record<string, string> = {};
- page.parts[page.parts.length - 1].params.forEach((name, i) => {
- params[name] = match[i + 1];
- });
+ // the last part has all parameters from any segment in the URL
+ const params = parts_to_params(match, page.parts[page.parts.length - 1] as PageManifestPart)
const preloaded: any[] = [];
let can_prerender = true;
@@ -155,10 +160,8 @@ export default async function render_page(
return;
}
- const params: Record<string, string> = {};
- part.params.forEach((name, i) => {
- params[name] = match[i + 1];
- });
+ // these are only the parameters up to the current URL segment
+ const params = parts_to_params(match, part)
const props = mod.preload
? await mod.preload.call(preload_context, {
@@ -242,7 +245,7 @@ export default async function render_page(
const js_deps = new Set(options.client.deps.__entry__ ? [...options.client.deps.__entry__.js] : []);
const css_deps = new Set(options.client.deps.__entry__ ? [...options.client.deps.__entry__.css] : []);
- page.parts.filter(Boolean).forEach(part => {
+ (page.parts.filter(Boolean) as PageManifestPart[]).forEach(part => {
const deps = options.client.deps[part.component.name];
if (!deps) return; // we don't have this info during dev
@@ -304,6 +307,22 @@ export default async function render_page(
}
}
+function parts_to_params(match: RegExpMatchArray, part: PageManifestPart): RouteParams {
+ const params: RouteParams = {};
+
+ part.params.forEach((name, i) => {
+ const is_spread = /^\.{3}.+$/.test(name)
+
+ if (is_spread) {
+ params[name.slice(3)] = match[i + 1].split('/');
+ } else {
+ params[name] = match[i + 1];
+ }
+ });
+
+ return params;
+}
+
function try_serialize(data: any, fail?: (err: Error) => void) {
try {
return devalue(data);

MODIFY
packages/app-utils/src/types.d.ts
packages/app-utils/src/types.d.ts
@@ -53,6 +53,8 @@ export interface RenderOptions {
dev: boolean; // TODO this is awkward
}
+export type RouteParams = Record<string, string | string[]>;
+
export interface PageComponentManifest {
default?: boolean;
type?: string;
@@ -61,13 +63,20 @@ export interface PageComponentManifest {
file: string;
}
+export interface PageManifestPart {
+ component: PageComponentManifest;
+ params: string[];
+}
+
export interface PageManifest {
pattern: RegExp;
path: string;
- parts: Array<{
- component: PageComponentManifest;
- params: string[];
- }>;
+ /**
+ * Each part contains the parameters for the page (last part) or layout (earlier parts)
+ * corresponding to a URL segment any part except the last may be null
+ * if there is no layout for that segment.
+ */
+ parts: Array<PageManifestPart | null>;
}
export interface EndpointManifest {

MODIFY
packages/kit/src/ambient.d.ts
packages/kit/src/ambient.d.ts
declare module "MANIFEST" {
- export const components: any[];
- export const routes: any[];
- export const layout: any;
- export const ErrorComponent: any;
+ import { SvelteComponent } from "svelte";
+
+ export type Route = {
+ pattern: RegExp;
+ parts: {
+ params: (match: RegExpExecArray) => Record<string, string>,
+ i: number
+ }[];
+ };
+
+ export const components: (() => SvelteComponent)[];
+ export const routes: Route[];
+ export const layout: SvelteComponent;
+ export const ErrorComponent: SvelteComponent;
}
declare module "ROOT" {
- const root: any;
+ import { SvelteComponent } from "svelte";
+
+ type Constructor<T> = {
+ new (...args: any[]): T;
+ }
+
+ const root: Constructor<SvelteComponent>;
export default root;
}

MODIFY
packages/kit/src/core/create_manifest_data.ts
packages/kit/src/core/create_manifest_data.ts
@@ -296,7 +296,7 @@ function get_parts(part: string): Part[] {
return {
content,
dynamic,
- spread: /^\.{3}.+$/.test(content),
+ spread: dynamic && /^\.{3}.+$/.test(content),
qualifier
};
})

MODIFY
packages/kit/src/runtime/navigation/types.ts
packages/kit/src/runtime/navigation/types.ts
@@ -25,9 +25,19 @@ export interface ScrollPosition {
y: number;
}
+export type RouteParams = Record<string, string | string[]>;
+
+export interface Route {
+ pattern: RegExp;
+ parts: {
+ params: (match: RegExpExecArray) => RouteParams,
+ i: number
+ }[];
+}
+
export interface Target {
href: string;
- route: any; // TODO Route;
+ route: Route;
match: RegExpExecArray;
page: Page;
}
@@ -40,7 +50,7 @@ export interface Redirect {
export interface Page {
host: string;
path: string;
- params: Record<string, string>;
+ params: RouteParams;
query: Record<string, string | string[]>;
}

Fix spread routes on server side (#115)