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

add server- and client-side store management (#178) #202

Merged
merged 1 commit into from
Mar 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ export type Route = {
export type Template = {
render: (data: Record<string, string>) => string;
stream: (req, res, data: Record<string, string | Promise<string>>) => void;
};

export type Store = {
get: () => any;
};
35 changes: 25 additions & 10 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type RouteObject = {
pattern: RegExp;
params: (match: RegExpMatchArray) => Record<string, string>;
module: {
render: (data: any) => {
render: (data: any, opts: { store: Store }) => {
head: string;
css: { code: string, map: any };
html: string
Expand All @@ -31,15 +31,22 @@ type RouteObject = {

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

type Store = {
get: () => any
};

interface Req extends ClientRequest {
url: string;
baseUrl: string;
originalUrl: string;
method: string;
pathname: string;
path: string;
params: Record<string, string>;
}

export default function middleware({ routes }: {
routes: RouteObject[]
export default function middleware({ routes, store }: {
routes: RouteObject[],
store: (req: Req) => Store
}) {
const output = locations.dest();

Expand Down Expand Up @@ -75,7 +82,7 @@ export default function middleware({ routes }: {
cache_control: 'max-age=31536000'
}),

get_route_handler(client_info.assetsByChunkName, routes)
get_route_handler(client_info.assetsByChunkName, routes, store)
].filter(Boolean));

return middleware;
Expand Down Expand Up @@ -120,7 +127,7 @@ function serve({ prefix, pathname, cache_control }: {

const resolved = Promise.resolve();

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

res.setHeader('Link', link);

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

let redirect: { statusCode: number, location: string };
Expand All @@ -154,7 +162,8 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
},
error: (statusCode: number, message: Error | string) => {
error = { statusCode, message };
}
},
store
}, req) : {}
).catch(err => {
error = { statusCode: 500, message: err };
Expand All @@ -172,10 +181,15 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
return;
}

const serialized = try_serialize(preloaded); // TODO bail on non-POJOs
const serialized = {
preloaded: mod.preload && try_serialize(preloaded),
store: store && try_serialize(store.get())
};
Object.assign(data, preloaded);

const { html, head, css } = mod.render(data);
const { html, head, css } = mod.render(data, {
store
});

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

let inline_script = `__SAPPER__={${[
`baseUrl: "${req.baseUrl}"`,
mod.preload && serialized && `preloaded: ${serialized}`,
serialized.preloaded && `preloaded: ${serialized.preloaded}`,
serialized.store && `store: ${serialized.store}`
].filter(Boolean).join(',')}}`

const has_service_worker = fs.existsSync(path.join(locations.dest(), 'service-worker.js'));
Expand Down
10 changes: 8 additions & 2 deletions src/runtime/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { detach, findAnchor, scroll_state, which } from './utils';
import { Component, ComponentConstructor, Params, Query, Route, RouteData, ScrollPosition, Target } from './interfaces';
import { Component, ComponentConstructor, Params, Query, Route, RouteData, ScrollPosition, Store, Target } from './interfaces';

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

export let component: Component;
let target: Node;
let store: Store;
let routes: Route[];
let errors: { '4xx': Route, '5xx': Route };

Expand Down Expand Up @@ -69,6 +70,7 @@ function render(Component: ComponentConstructor, data: any, scroll: ScrollPositi
component = new Component({
target,
data,
store,
hydrate: !component
});

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

let inited: boolean;

export function init(_target: Node, _routes: Route[]) {
export function init(_target: Node, _routes: Route[], opts?: { store?: (data: any) => Store }) {
target = _target;
routes = _routes.filter(r => !r.error);
errors = {
'4xx': _routes.find(r => r.error === '4xx'),
'5xx': _routes.find(r => r.error === '5xx')
};

if (opts && opts.store) {
store = opts.store(manifest.store);
}

if (!inited) { // this check makes HMR possible
window.addEventListener('click', handle_click);
window.addEventListener('popstate', handle_popstate);
Expand Down
5 changes: 4 additions & 1 deletion src/runtime/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Store } from '../interfaces';

export { Store };
export type Params = Record<string, string>;
export type Query = Record<string, string | true>;
export type RouteData = { params: Params, query: Query };

export interface ComponentConstructor {
new (options: { target: Node, data: any, hydrate: boolean }): Component;
new (options: { target: Node, data: any, store: Store, hydrate: boolean }): Component;
preload: (data: { params: Params, query: Query }) => Promise<any>;
};

Expand Down
5 changes: 4 additions & 1 deletion test/app/app/client.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { init, prefetchRoutes } from '../../../runtime.js';
import { Store } from 'svelte/store.js';
import { routes } from './manifest/client.js';

window.init = () => {
return init(document.querySelector('#sapper'), routes);
return init(document.querySelector('#sapper'), routes, {
store: data => new Store(data)
});
};

window.prefetchRoutes = prefetchRoutes;
10 changes: 9 additions & 1 deletion test/app/app/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { resolve } from 'url';
import express from 'express';
import serve from 'serve-static';
import sapper from '../../../dist/middleware.ts.js';
import { Store } from 'svelte/store.js';
import { routes } from './manifest/server.js';

let pending;
Expand Down Expand Up @@ -77,7 +78,14 @@ const middlewares = [
next();
},

sapper({ routes })
sapper({
routes,
store: () => {
return new Store({
title: 'Stored title'
});
}
})
];

if (BASEPATH) {
Expand Down
1 change: 1 addition & 0 deletions test/app/routes/store.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>{{$title}}</h1>
12 changes: 12 additions & 0 deletions test/common/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,18 @@ function run({ mode, basepath = '' }) {
assert.equal(title, '42');
});
});

it('renders store props', () => {
return nightmare.goto(`${base}/store`)
.page.title()
.then(title => {
assert.equal(title, 'Stored title');
return nightmare.init().page.title();
})
.then(title => {
assert.equal(title, 'Stored title');
});
});
});

describe('headers', () => {
Expand Down