Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.

Commit 8ee5346

Browse files
committed
switch to single App component model (#157)
1 parent 9e4b79c commit 8ee5346

File tree

6 files changed

+74
-53
lines changed

6 files changed

+74
-53
lines changed

src/middleware.ts

+17-14
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,7 @@ type RouteObject = {
2020
type: 'page' | 'route';
2121
pattern: RegExp;
2222
params: (match: RegExpMatchArray) => Record<string, string>;
23-
module: {
24-
render: (data: any, opts: { store: Store }) => {
25-
head: string;
26-
css: { code: string, map: any };
27-
html: string
28-
},
29-
preload: (data: any) => any | Promise<any>
30-
};
23+
module: Component;
3124
error?: string;
3225
}
3326

@@ -47,7 +40,17 @@ interface Req extends ClientRequest {
4740
headers: Record<string, string>;
4841
}
4942

50-
export default function middleware({ routes, store }: {
43+
interface Component {
44+
render: (data: any, opts: { store: Store }) => {
45+
head: string;
46+
css: { code: string, map: any };
47+
html: string
48+
},
49+
preload: (data: any) => any | Promise<any>
50+
}
51+
52+
export default function middleware({ App, routes, store }: {
53+
App: Component,
5154
routes: RouteObject[],
5255
store: (req: Req) => Store
5356
}) {
@@ -90,7 +93,7 @@ export default function middleware({ routes, store }: {
9093
cache_control: 'max-age=31536000'
9194
}),
9295

93-
get_route_handler(client_info.assets, routes, store)
96+
get_route_handler(client_info.assets, App, routes, store)
9497
].filter(Boolean));
9598

9699
return middleware;
@@ -135,7 +138,7 @@ function serve({ prefix, pathname, cache_control }: {
135138

136139
const resolved = Promise.resolve();
137140

138-
function get_route_handler(chunks: Record<string, string>, routes: RouteObject[], store_getter: (req: Req) => Store) {
141+
function get_route_handler(chunks: Record<string, string>, App: Component, routes: RouteObject[], store_getter: (req: Req) => Store) {
139142
const template = dev()
140143
? () => fs.readFileSync(`${locations.app()}/template.html`, 'utf-8')
141144
: (str => () => str)(fs.readFileSync(`${locations.dest()}/template.html`, 'utf-8'));
@@ -170,7 +173,7 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
170173
res.setHeader('Link', link);
171174

172175
const store = store_getter ? store_getter(req) : null;
173-
const data = { params: req.params, query: req.query };
176+
const props = { params: req.params, query: req.query, path: req.path };
174177

175178
let redirect: { statusCode: number, location: string };
176179
let error: { statusCode: number, message: Error | string };
@@ -240,9 +243,9 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
240243
preloaded: mod.preload && try_serialize(preloaded),
241244
store: store && try_serialize(store.get())
242245
};
243-
Object.assign(data, preloaded);
246+
Object.assign(props, preloaded);
244247

245-
const { html, head, css } = mod.render(data, {
248+
const { html, head, css } = App.render({ Page: mod, props }, {
246249
store
247250
});
248251

src/runtime/index.ts

+46-35
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Component, ComponentConstructor, Params, Query, Route, RouteData, Scrol
33

44
const manifest = typeof window !== 'undefined' && window.__SAPPER__;
55

6+
export let App: ComponentConstructor;
67
export let component: Component;
78
let target: Node;
89
let store: Store;
@@ -27,10 +28,10 @@ function select_route(url: URL): Target {
2728
if (url.origin !== window.location.origin) return null;
2829
if (!url.pathname.startsWith(manifest.baseUrl)) return null;
2930

30-
const pathname = url.pathname.slice(manifest.baseUrl.length);
31+
const path = url.pathname.slice(manifest.baseUrl.length);
3132

3233
for (const route of routes) {
33-
const match = route.pattern.exec(pathname);
34+
const match = route.pattern.exec(path);
3435
if (match) {
3536
if (route.ignore) return null;
3637

@@ -43,19 +44,18 @@ function select_route(url: URL): Target {
4344
query[key] = value || true;
4445
})
4546
}
46-
return { url, route, data: { params, query } };
47+
return { url, route, props: { params, query, path } };
4748
}
4849
}
4950
}
5051

52+
let first_load = true;
5153
let current_token: {};
5254

53-
function render(Component: ComponentConstructor, data: any, scroll: ScrollPosition, token: {}) {
55+
function render(Page: ComponentConstructor, props: any, scroll: ScrollPosition, token: {}) {
5456
if (current_token !== token) return;
5557

56-
if (component) {
57-
component.destroy();
58-
} else {
58+
if (first_load) {
5959
// first load — remove SSR'd <head> contents
6060
const start = document.querySelector('#sapper-head-start');
6161
const end = document.querySelector('#sapper-head-end');
@@ -65,33 +65,43 @@ function render(Component: ComponentConstructor, data: any, scroll: ScrollPositi
6565
detach(start);
6666
detach(end);
6767
}
68-
}
6968

70-
component = new Component({
71-
target,
72-
data,
73-
store,
74-
hydrate: !component
75-
});
69+
component = new App({
70+
target,
71+
data: {
72+
Page,
73+
props
74+
},
75+
store,
76+
hydrate: true
77+
});
78+
79+
first_load = false;
80+
} else {
81+
component.set({
82+
Page,
83+
props
84+
});
85+
}
7686

7787
if (scroll) {
7888
window.scrollTo(scroll.x, scroll.y);
7989
}
8090
}
8191

82-
function prepare_route(Component: ComponentConstructor, data: RouteData) {
92+
function prepare_route(Page: ComponentConstructor, props: RouteData) {
8393
let redirect: { statusCode: number, location: string } = null;
8494
let error: { statusCode: number, message: Error | string } = null;
8595

86-
if (!Component.preload) {
87-
return { Component, data, redirect, error };
96+
if (!Page.preload) {
97+
return { Page, props, redirect, error };
8898
}
8999

90100
if (!component && manifest.preloaded) {
91-
return { Component, data: Object.assign(data, manifest.preloaded), redirect, error };
101+
return { Page, props: Object.assign(props, manifest.preloaded), redirect, error };
92102
}
93103

94-
return Promise.resolve(Component.preload.call({
104+
return Promise.resolve(Page.preload.call({
95105
store,
96106
fetch: (url: string, opts?: any) => window.fetch(url, opts),
97107
redirect: (statusCode: number, location: string) => {
@@ -100,23 +110,23 @@ function prepare_route(Component: ComponentConstructor, data: RouteData) {
100110
error: (statusCode: number, message: Error | string) => {
101111
error = { statusCode, message };
102112
}
103-
}, data)).catch(err => {
113+
}, props)).catch(err => {
104114
error = { statusCode: 500, message: err };
105115
}).then(preloaded => {
106116
if (error) {
107117
const route = error.statusCode >= 400 && error.statusCode < 500
108118
? errors['4xx']
109119
: errors['5xx'];
110120

111-
return route.load().then(({ default: Component }: { default: ComponentConstructor }) => {
121+
return route.load().then(({ default: Page }: { default: ComponentConstructor }) => {
112122
const err = error.message instanceof Error ? error.message : new Error(error.message);
113-
Object.assign(data, { status: error.statusCode, error: err });
114-
return { Component, data, redirect: null };
123+
Object.assign(props, { status: error.statusCode, error: err });
124+
return { Page, props, redirect: null };
115125
});
116126
}
117127

118-
Object.assign(data, preloaded)
119-
return { Component, data, redirect };
128+
Object.assign(props, preloaded)
129+
return { Page, props, redirect };
120130
});
121131
}
122132

@@ -136,18 +146,18 @@ function navigate(target: Target, id: number) {
136146

137147
const loaded = prefetching && prefetching.href === target.url.href ?
138148
prefetching.promise :
139-
target.route.load().then(mod => prepare_route(mod.default, target.data));
149+
target.route.load().then(mod => prepare_route(mod.default, target.props));
140150

141151
prefetching = null;
142152

143153
const token = current_token = {};
144154

145-
return loaded.then(({ Component, data, redirect }) => {
155+
return loaded.then(({ Page, props, redirect }) => {
146156
if (redirect) {
147157
return goto(redirect.location, { replaceState: true });
148158
}
149159

150-
render(Component, data, scroll_history[id], token);
160+
render(Page, props, scroll_history[id], token);
151161
});
152162
}
153163

@@ -208,7 +218,7 @@ function handle_popstate(event: PopStateEvent) {
208218

209219
let prefetching: {
210220
href: string;
211-
promise: Promise<{ Component: ComponentConstructor, data: any }>;
221+
promise: Promise<{ Page: ComponentConstructor, props: any }>;
212222
} = null;
213223

214224
export function prefetch(href: string) {
@@ -217,7 +227,7 @@ export function prefetch(href: string) {
217227
if (selected && (!prefetching || href !== prefetching.href)) {
218228
prefetching = {
219229
href,
220-
promise: selected.route.load().then(mod => prepare_route(mod.default, selected.data))
230+
promise: selected.route.load().then(mod => prepare_route(mod.default, selected.props))
221231
};
222232
}
223233
}
@@ -240,12 +250,13 @@ function trigger_prefetch(event: MouseEvent | TouchEvent) {
240250

241251
let inited: boolean;
242252

243-
export function init(_target: Node, _routes: Route[], opts?: { store?: (data: any) => Store }) {
244-
target = _target;
245-
routes = _routes.filter(r => !r.error);
253+
export function init(opts: { App: ComponentConstructor, target: Node, routes: Route[], store?: (data: any) => Store }) {
254+
App = opts.App;
255+
target = opts.target;
256+
routes = opts.routes.filter(r => !r.error);
246257
errors = {
247-
'4xx': _routes.find(r => r.error === '4xx'),
248-
'5xx': _routes.find(r => r.error === '5xx')
258+
'4xx': opts.routes.find(r => r.error === '4xx'),
259+
'5xx': opts.routes.find(r => r.error === '5xx')
249260
};
250261

251262
if (opts && opts.store) {

src/runtime/interfaces.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import { Store } from '../interfaces';
33
export { Store };
44
export type Params = Record<string, string>;
55
export type Query = Record<string, string | true>;
6-
export type RouteData = { params: Params, query: Query };
6+
export type RouteData = { params: Params, query: Query, path: string };
77

88
export interface ComponentConstructor {
99
new (options: { target: Node, data: any, store: Store, hydrate: boolean }): Component;
10-
preload: (data: { params: Params, query: Query }) => Promise<any>;
10+
preload: (props: { params: Params, query: Query }) => Promise<any>;
1111
};
1212

1313
export interface Component {
@@ -30,5 +30,5 @@ export type ScrollPosition = {
3030
export type Target = {
3131
url: URL;
3232
route: Route;
33-
data: RouteData;
33+
props: RouteData;
3434
};

test/app/app/App.html

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<svelte:component this={Page} {...props}/>

test/app/app/client.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { init, prefetchRoutes } from '../../../runtime.js';
22
import { Store } from 'svelte/store.js';
33
import { routes } from './manifest/client.js';
4+
import App from './App.html';
45

56
window.init = () => {
6-
return init(document.querySelector('#sapper'), routes, {
7+
return init({
8+
target: document.querySelector('#sapper'),
9+
App,
10+
routes,
711
store: data => new Store(data)
812
});
913
};

test/app/app/server.js

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import serve from 'serve-static';
55
import sapper from '../../../dist/middleware.ts.js';
66
import { Store } from 'svelte/store.js';
77
import { routes } from './manifest/server.js';
8+
import App from './App.html'
89

910
let pending;
1011
let ended;
@@ -86,6 +87,7 @@ const middlewares = [
8687
},
8788

8889
sapper({
90+
App,
8991
routes,
9092
store: () => {
9193
return new Store({

0 commit comments

Comments
 (0)