mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-21 23:05:02 +00:00
first crack at context-driven store
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { tick } from 'svelte';
|
||||
import RootComponent, * as RootComponentStatic from '__ROOT__';
|
||||
import App from '@sapper/App.html';
|
||||
import { preloading, page } from '../shared/stores';
|
||||
import Root, * as RootStatic from '__ROOT__';
|
||||
import ErrorComponent from '__ERROR__';
|
||||
import {
|
||||
Target,
|
||||
@@ -128,7 +129,9 @@ export function navigate(target: Target, id: number, noscroll?: boolean, hash?:
|
||||
cid = id;
|
||||
|
||||
if (root_component) {
|
||||
root_component.$set({ preloading: true });
|
||||
preloading.set({
|
||||
// TODO path, params, query
|
||||
});
|
||||
}
|
||||
const loaded = prefetching && prefetching.href === target.url.href ?
|
||||
prefetching.promise :
|
||||
@@ -150,6 +153,8 @@ export function navigate(target: Target, id: number, noscroll?: boolean, hash?:
|
||||
async function render(props: any, nullable_depth: number, scroll: ScrollPosition, noscroll: boolean, hash: string, token: {}) {
|
||||
if (current_token !== token) return;
|
||||
|
||||
preloading.set(null);
|
||||
|
||||
if (root_component) {
|
||||
// first, clear out highest-level root component
|
||||
let level = props.child;
|
||||
@@ -160,18 +165,12 @@ async function render(props: any, nullable_depth: number, scroll: ScrollPosition
|
||||
|
||||
const { component } = level;
|
||||
level.component = null;
|
||||
root_component.$set({ child: props.child });
|
||||
|
||||
await tick();
|
||||
root_component.props = props;
|
||||
|
||||
// then render new stuff
|
||||
// TODO do we need to call `flush` before doing this?
|
||||
level.component = component;
|
||||
root_component.$set(props);
|
||||
|
||||
// if we need to scroll to a deep link, we need to
|
||||
// wait for the current update to happen first
|
||||
if (!noscroll && hash) await tick();
|
||||
root_component.props = props;
|
||||
} else {
|
||||
// first load — remove SSR'd <head> contents
|
||||
const start = document.querySelector('#sapper-head-start');
|
||||
@@ -185,10 +184,13 @@ async function render(props: any, nullable_depth: number, scroll: ScrollPosition
|
||||
|
||||
Object.assign(props, root_data);
|
||||
|
||||
root_component = new RootComponent({
|
||||
root_component = new App({
|
||||
target,
|
||||
props,
|
||||
store,
|
||||
props: {
|
||||
Root,
|
||||
props,
|
||||
session: __SAPPER__.session
|
||||
},
|
||||
hydrate: true
|
||||
});
|
||||
}
|
||||
@@ -247,7 +249,7 @@ export function prepare_page(target: Target): Promise<{
|
||||
};
|
||||
|
||||
if (!root_preload) {
|
||||
const preload_fn = RootComponentStatic['pre' + 'load']; // Rollup makes us jump through these hoops :(
|
||||
const preload_fn = RootStatic['pre' + 'load']; // Rollup makes us jump through these hoops :(
|
||||
root_preload = preload_fn
|
||||
? initial_data.preloaded[0] || preload_fn.call(preload_context, {
|
||||
path,
|
||||
@@ -315,7 +317,6 @@ export function prepare_page(target: Target): Promise<{
|
||||
return {
|
||||
nullable_depth: 0,
|
||||
data: Object.assign({}, props, {
|
||||
preloading: false,
|
||||
child: {
|
||||
component: ErrorComponent,
|
||||
props
|
||||
@@ -327,7 +328,6 @@ export function prepare_page(target: Target): Promise<{
|
||||
const props = { path, query, error: null, status: null };
|
||||
const data = {
|
||||
path,
|
||||
preloading: false,
|
||||
child: Object.assign({}, root_props.child, {
|
||||
segment: segments[0]
|
||||
})
|
||||
13
templates/src/app/index.ts
Normal file
13
templates/src/app/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { getContext } from 'svelte';
|
||||
import { CONTEXT_KEY } from '@sapper/internal';
|
||||
import * as stores from '../shared/stores';
|
||||
|
||||
export const preloading = { subscribe: stores.preloading.subscribe };
|
||||
export const page = { subscribe: stores.page.subscribe };
|
||||
|
||||
export const getSession = () => getContext(CONTEXT_KEY);
|
||||
|
||||
export { default as start } from './start/index';
|
||||
export { default as goto } from './goto/index';
|
||||
export { default as prefetch } from './prefetch/index';
|
||||
export { default as prefetchRoutes } from './prefetchRoutes/index';
|
||||
@@ -6,25 +6,21 @@ import {
|
||||
scroll_history,
|
||||
scroll_state,
|
||||
select_route,
|
||||
set_store,
|
||||
set_target,
|
||||
uid,
|
||||
set_uid,
|
||||
set_cid
|
||||
} from '../app';
|
||||
import prefetch from '../prefetch/index';
|
||||
import { Store, ScrollPosition } from '../types';
|
||||
|
||||
export default function start(opts: {
|
||||
target: Node,
|
||||
store?: (data: any) => Store
|
||||
target: Node
|
||||
}) {
|
||||
if ('scrollRestoration' in history) {
|
||||
history.scrollRestoration = 'manual';
|
||||
}
|
||||
|
||||
set_target(opts.target);
|
||||
if (opts.store) set_store(opts.store);
|
||||
|
||||
addEventListener('click', handle_click);
|
||||
addEventListener('popstate', handle_popstate);
|
||||
@@ -1,4 +0,0 @@
|
||||
export { default as start } from './start/index';
|
||||
export { default as goto } from './goto/index';
|
||||
export { default as prefetch } from './prefetch/index';
|
||||
export { default as prefetchRoutes } from './prefetchRoutes/index';
|
||||
@@ -4,12 +4,14 @@ import cookie from 'cookie';
|
||||
import devalue from 'devalue';
|
||||
import fetch from 'node-fetch';
|
||||
import { URL, resolve } from 'url';
|
||||
import * as stores from '../../shared/stores';
|
||||
import { build_dir, dev, src_dir, IGNORE } from '../placeholders';
|
||||
import { Manifest, Page, Props, Req, Res, Store } from './types';
|
||||
import { Manifest, Page, Props, Req, Res } from './types';
|
||||
import App from '@sapper/App.html';
|
||||
|
||||
export function get_page_handler(
|
||||
manifest: Manifest,
|
||||
store_getter: (req: Req, res: Res) => Store
|
||||
session_getter: (req: Req, res: Res) => any
|
||||
) {
|
||||
const get_build_info = dev
|
||||
? () => JSON.parse(fs.readFileSync(path.join(build_dir, 'build.json'), 'utf-8'))
|
||||
@@ -76,7 +78,7 @@ export function get_page_handler(
|
||||
res.setHeader('Link', link);
|
||||
}
|
||||
|
||||
const store = store_getter ? store_getter(req, res) : null;
|
||||
const session = session_getter(req, res);
|
||||
|
||||
let redirect: { statusCode: number, location: string };
|
||||
let preload_error: { statusCode: number, message: Error | string };
|
||||
@@ -127,8 +129,7 @@ export function get_page_handler(
|
||||
}
|
||||
|
||||
return fetch(parsed.href, opts);
|
||||
},
|
||||
store
|
||||
}
|
||||
};
|
||||
|
||||
let preloaded;
|
||||
@@ -177,11 +178,6 @@ export function get_page_handler(
|
||||
return;
|
||||
}
|
||||
|
||||
const serialized = {
|
||||
preloaded: `[${preloaded.map(data => try_serialize(data)).join(',')}]`,
|
||||
store: store && try_serialize(store.get())
|
||||
};
|
||||
|
||||
const segments = req.path.split('/').filter(Boolean);
|
||||
|
||||
const props: Props = {
|
||||
@@ -223,15 +219,30 @@ export function get_page_handler(
|
||||
level = level.props.child;
|
||||
}
|
||||
|
||||
const { html, head, css } = manifest.root.render(data, {
|
||||
store
|
||||
stores.page.set({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
params: req.params
|
||||
});
|
||||
|
||||
const { html, head, css } = App.render({
|
||||
Root: manifest.root,
|
||||
props: data,
|
||||
session
|
||||
});
|
||||
|
||||
const serialized = {
|
||||
preloaded: `[${preloaded.map(data => try_serialize(data)).join(',')}]`,
|
||||
session: session && try_serialize(session, err => {
|
||||
throw new Error(`Failed to serialize session data: ${err.message}`);
|
||||
})
|
||||
};
|
||||
|
||||
let script = `__SAPPER__={${[
|
||||
error && `error:1`,
|
||||
`baseUrl:"${req.baseUrl}"`,
|
||||
serialized.preloaded && `preloaded:${serialized.preloaded}`,
|
||||
serialized.store && `store:${serialized.store}`
|
||||
serialized.session && `session:${serialized.session}`
|
||||
].filter(Boolean).join(',')}};`;
|
||||
|
||||
if (has_service_worker) {
|
||||
@@ -320,10 +331,11 @@ function read_template(dir = build_dir) {
|
||||
return fs.readFileSync(`${dir}/template.html`, 'utf-8');
|
||||
}
|
||||
|
||||
function try_serialize(data: any) {
|
||||
function try_serialize(data: any, fail?: (err) => void) {
|
||||
try {
|
||||
return devalue(data);
|
||||
} catch (err) {
|
||||
if (fail) fail(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import { get_page_handler } from './get_page_handler';
|
||||
import { lookup } from './mime';
|
||||
|
||||
export default function middleware(opts: {
|
||||
store?: (req: Req, res: Res) => Store,
|
||||
session?: (req: Req, res: Res) => any,
|
||||
ignore?: any
|
||||
} = {}) {
|
||||
const { store, ignore } = opts;
|
||||
const { session, ignore } = opts;
|
||||
|
||||
let emitted_basepath = false;
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function middleware(opts: {
|
||||
|
||||
get_server_route_handler(manifest.server_routes),
|
||||
|
||||
get_page_handler(manifest, store)
|
||||
get_page_handler(manifest, session || noop)
|
||||
].filter(Boolean));
|
||||
}
|
||||
|
||||
@@ -140,4 +140,6 @@ export function serve({ prefix, pathname, cache_control }: {
|
||||
next();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function noop(){}
|
||||
4
templates/src/shared/stores.ts
Normal file
4
templates/src/shared/stores.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const preloading = writable(null);
|
||||
export const page = writable(null);
|
||||
Reference in New Issue
Block a user