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

Commit cf6621b

Browse files
authored
Merge pull request #202 from sveltejs/gh-178-store
add server- and client-side store management (#178)
2 parents 67a81a3 + 9812cbd commit cf6621b

File tree

8 files changed

+67
-15
lines changed

8 files changed

+67
-15
lines changed

src/interfaces.ts

+4
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ export type Route = {
1212
export type Template = {
1313
render: (data: Record<string, string>) => string;
1414
stream: (req, res, data: Record<string, string | Promise<string>>) => void;
15+
};
16+
17+
export type Store = {
18+
get: () => any;
1519
};

src/middleware.ts

+25-10
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type RouteObject = {
1919
pattern: RegExp;
2020
params: (match: RegExpMatchArray) => Record<string, string>;
2121
module: {
22-
render: (data: any) => {
22+
render: (data: any, opts: { store: Store }) => {
2323
head: string;
2424
css: { code: string, map: any };
2525
html: string
@@ -31,15 +31,22 @@ type RouteObject = {
3131

3232
type Handler = (req: Req, res: ServerResponse, next: () => void) => void;
3333

34+
type Store = {
35+
get: () => any
36+
};
37+
3438
interface Req extends ClientRequest {
3539
url: string;
40+
baseUrl: string;
41+
originalUrl: string;
3642
method: string;
37-
pathname: string;
43+
path: string;
3844
params: Record<string, string>;
3945
}
4046

41-
export default function middleware({ routes }: {
42-
routes: RouteObject[]
47+
export default function middleware({ routes, store }: {
48+
routes: RouteObject[],
49+
store: (req: Req) => Store
4350
}) {
4451
const output = locations.dest();
4552

@@ -75,7 +82,7 @@ export default function middleware({ routes }: {
7582
cache_control: 'max-age=31536000'
7683
}),
7784

78-
get_route_handler(client_info.assetsByChunkName, routes)
85+
get_route_handler(client_info.assetsByChunkName, routes, store)
7986
].filter(Boolean));
8087

8188
return middleware;
@@ -120,7 +127,7 @@ function serve({ prefix, pathname, cache_control }: {
120127

121128
const resolved = Promise.resolve();
122129

123-
function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]) {
130+
function get_route_handler(chunks: Record<string, string>, routes: RouteObject[], store_getter: (req: Req) => Store) {
124131
const template = dev()
125132
? () => fs.readFileSync(`${locations.app()}/template.html`, 'utf-8')
126133
: (str => () => str)(fs.readFileSync(`${locations.dest()}/template.html`, 'utf-8'));
@@ -142,6 +149,7 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
142149

143150
res.setHeader('Link', link);
144151

152+
const store = store_getter ? store_getter(req) : null;
145153
const data = { params: req.params, query: req.query };
146154

147155
let redirect: { statusCode: number, location: string };
@@ -154,7 +162,8 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
154162
},
155163
error: (statusCode: number, message: Error | string) => {
156164
error = { statusCode, message };
157-
}
165+
},
166+
store
158167
}, req) : {}
159168
).catch(err => {
160169
error = { statusCode: 500, message: err };
@@ -172,10 +181,15 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
172181
return;
173182
}
174183

175-
const serialized = try_serialize(preloaded); // TODO bail on non-POJOs
184+
const serialized = {
185+
preloaded: mod.preload && try_serialize(preloaded),
186+
store: store && try_serialize(store.get())
187+
};
176188
Object.assign(data, preloaded);
177189

178-
const { html, head, css } = mod.render(data);
190+
const { html, head, css } = mod.render(data, {
191+
store
192+
});
179193

180194
let scripts = []
181195
.concat(chunks.main) // chunks main might be an array. it might not! thanks, webpack
@@ -184,7 +198,8 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
184198

185199
let inline_script = `__SAPPER__={${[
186200
`baseUrl: "${req.baseUrl}"`,
187-
mod.preload && serialized && `preloaded: ${serialized}`,
201+
serialized.preloaded && `preloaded: ${serialized.preloaded}`,
202+
serialized.store && `store: ${serialized.store}`
188203
].filter(Boolean).join(',')}}`
189204

190205
const has_service_worker = fs.existsSync(path.join(locations.dest(), 'service-worker.js'));

src/runtime/index.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { detach, findAnchor, scroll_state, which } from './utils';
2-
import { Component, ComponentConstructor, Params, Query, Route, RouteData, ScrollPosition, Target } from './interfaces';
2+
import { Component, ComponentConstructor, Params, Query, Route, RouteData, ScrollPosition, Store, Target } from './interfaces';
33

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

66
export let component: Component;
77
let target: Node;
8+
let store: Store;
89
let routes: Route[];
910
let errors: { '4xx': Route, '5xx': Route };
1011

@@ -69,6 +70,7 @@ function render(Component: ComponentConstructor, data: any, scroll: ScrollPositi
6970
component = new Component({
7071
target,
7172
data,
73+
store,
7274
hydrate: !component
7375
});
7476

@@ -227,14 +229,18 @@ function handle_touchstart_mouseover(event: MouseEvent | TouchEvent) {
227229

228230
let inited: boolean;
229231

230-
export function init(_target: Node, _routes: Route[]) {
232+
export function init(_target: Node, _routes: Route[], opts?: { store?: (data: any) => Store }) {
231233
target = _target;
232234
routes = _routes.filter(r => !r.error);
233235
errors = {
234236
'4xx': _routes.find(r => r.error === '4xx'),
235237
'5xx': _routes.find(r => r.error === '5xx')
236238
};
237239

240+
if (opts && opts.store) {
241+
store = opts.store(manifest.store);
242+
}
243+
238244
if (!inited) { // this check makes HMR possible
239245
window.addEventListener('click', handle_click);
240246
window.addEventListener('popstate', handle_popstate);

src/runtime/interfaces.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import { Store } from '../interfaces';
2+
3+
export { Store };
14
export type Params = Record<string, string>;
25
export type Query = Record<string, string | true>;
36
export type RouteData = { params: Params, query: Query };
47

58
export interface ComponentConstructor {
6-
new (options: { target: Node, data: any, hydrate: boolean }): Component;
9+
new (options: { target: Node, data: any, store: Store, hydrate: boolean }): Component;
710
preload: (data: { params: Params, query: Query }) => Promise<any>;
811
};
912

test/app/app/client.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { init, prefetchRoutes } from '../../../runtime.js';
2+
import { Store } from 'svelte/store.js';
23
import { routes } from './manifest/client.js';
34

45
window.init = () => {
5-
return init(document.querySelector('#sapper'), routes);
6+
return init(document.querySelector('#sapper'), routes, {
7+
store: data => new Store(data)
8+
});
69
};
710

811
window.prefetchRoutes = prefetchRoutes;

test/app/app/server.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { resolve } from 'url';
33
import express from 'express';
44
import serve from 'serve-static';
55
import sapper from '../../../dist/middleware.ts.js';
6+
import { Store } from 'svelte/store.js';
67
import { routes } from './manifest/server.js';
78

89
let pending;
@@ -77,7 +78,14 @@ const middlewares = [
7778
next();
7879
},
7980

80-
sapper({ routes })
81+
sapper({
82+
routes,
83+
store: () => {
84+
return new Store({
85+
title: 'Stored title'
86+
});
87+
}
88+
})
8189
];
8290

8391
if (BASEPATH) {

test/app/routes/store.html

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>{{$title}}</h1>

test/common/test.js

+12
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,18 @@ function run({ mode, basepath = '' }) {
521521
assert.equal(title, '42');
522522
});
523523
});
524+
525+
it('renders store props', () => {
526+
return nightmare.goto(`${base}/store`)
527+
.page.title()
528+
.then(title => {
529+
assert.equal(title, 'Stored title');
530+
return nightmare.init().page.title();
531+
})
532+
.then(title => {
533+
assert.equal(title, 'Stored title');
534+
});
535+
});
524536
});
525537

526538
describe('headers', () => {

0 commit comments

Comments
 (0)