diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts
index 79e37ea..da6f41d 100644
--- a/src/core/create_manifests.ts
+++ b/src/core/create_manifests.ts
@@ -5,7 +5,7 @@ import { Page, PageComponent, ManifestData } from '../interfaces';
const app = fs.readFileSync(path.resolve(__dirname, '../templates/App.html'), 'utf-8');
const internal = fs.readFileSync(path.resolve(__dirname, '../templates/internal.mjs'), 'utf-8');
-const layout = ``;
+const layout = fs.readFileSync(path.resolve(__dirname, '../templates/layout.html'), 'utf-8');
export function create_main_manifests({
bundler,
diff --git a/templates/internal.mjs b/templates/internal.mjs
index 454d657..0eba064 100644
--- a/templates/internal.mjs
+++ b/templates/internal.mjs
@@ -1 +1,8 @@
+import { writable } from 'svelte/store';
+
+export const stores = {
+ preloading: writable(null),
+ page: writable(null)
+};
+
export const CONTEXT_KEY = {};
\ No newline at end of file
diff --git a/templates/layout.html b/templates/layout.html
new file mode 100644
index 0000000..8c0dcbb
--- /dev/null
+++ b/templates/layout.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/templates/src/app/app.ts b/templates/src/app/app.ts
index d6c8e0a..832b46b 100644
--- a/templates/src/app/app.ts
+++ b/templates/src/app/app.ts
@@ -1,5 +1,5 @@
import App from '@sapper/App.html';
-import { preloading, page } from '../shared/stores';
+import { stores } from '@sapper/internal';
import Root, * as RootStatic from '__ROOT__';
import ErrorComponent from '__ERROR__';
import {
@@ -10,7 +10,8 @@ import {
ComponentLoader,
ComponentConstructor,
RootProps,
- Page
+ Page,
+ PageData
} from './types';
import goto from './goto';
@@ -25,20 +26,9 @@ let current_token: {};
let root_preload: Promise;
let root_data: any;
-const root_props: RootProps = {
- path: null,
- params: null,
- query: null,
- child: {
- segment: null,
- component: null,
- props: {}
- }
-};
-
export let prefetching: {
href: string;
- promise: Promise<{ redirect?: Redirect, data?: any, nullable_depth?: number, new_segments?: any }>;
+ promise: Promise<{ redirect?: Redirect, data?: any, new_segments?: any }>;
} = null;
export function set_prefetching(href, promise) {
prefetching = { href, promise };
@@ -111,7 +101,7 @@ export function scroll_state() {
};
}
-export function navigate(target: Target, id: number, noscroll?: boolean, hash?: string): Promise {
+export async function navigate(target: Target, id: number, noscroll?: boolean, hash?: string): Promise {
let scroll: ScrollPosition;
if (id) {
// popstate or initial navigation
@@ -129,7 +119,7 @@ export function navigate(target: Target, id: number, noscroll?: boolean, hash?:
cid = id;
if (root_component) {
- preloading.set({
+ stores.preloading.set({
// TODO path, params, query
});
}
@@ -141,38 +131,22 @@ export function navigate(target: Target, id: number, noscroll?: boolean, hash?:
const token = current_token = {};
- return loaded.then(({ redirect, data, nullable_depth, new_segments }) => {
- if (redirect) {
- return goto(redirect.location, { replaceState: true });
- }
- if (new_segments) {
- segments = new_segments;
- }
- render(data, nullable_depth, scroll_history[id], noscroll, hash, token);
- if (document.activeElement) document.activeElement.blur();
- });
+ const { redirect, page, data, new_segments, results } = await loaded;
+
+ if (redirect) return goto(redirect.location, { replaceState: true });
+ if (new_segments) segments = new_segments;
+
+ await render(results, data, page, scroll_history[id], noscroll, hash, token);
+ if (document.activeElement) document.activeElement.blur();
}
-async function render(props: any, nullable_depth: number, scroll: ScrollPosition, noscroll: boolean, hash: string, token: {}) {
+async function render(results: any[], props: any, page: PageData, scroll: ScrollPosition, noscroll: boolean, hash: string, token: {}) {
if (current_token !== token) return;
- preloading.set(null);
+ stores.page.set(page);
+ stores.preloading.set(null);
if (root_component) {
- // first, clear out highest-level root component
- let level = props.child;
- for (let i = 0; i < nullable_depth; i += 1) {
- if (i === nullable_depth) break;
- level = level.props.child;
- }
-
- const { component } = level;
- level.component = null;
- root_component.props = props;
-
- // then render new stuff
- // TODO do we need to call `flush` before doing this?
- level.component = component;
root_component.props = props;
} else {
// first load — remove SSR'd contents
@@ -185,7 +159,7 @@ async function render(props: any, nullable_depth: number, scroll: ScrollPosition
detach(end);
}
- Object.assign(props, root_data);
+ Object.assign(props, root_data); // TODO what is root_data, do we still need it?
root_component = new App({
target,
@@ -215,14 +189,16 @@ async function render(props: any, nullable_depth: number, scroll: ScrollPosition
if (scroll) scrollTo(scroll.x, scroll.y);
}
- Object.assign(root_props, props);
+ previous_thingummy = results;
ready = true;
}
+let previous_thingummy = [];
+
export function prepare_page(target: Target): Promise<{
redirect?: Redirect;
data?: any;
- nullable_depth?: number;
+ page: PageData
}> {
const { page, path, query } = target;
const new_segments = path.split('/').filter(Boolean);
@@ -240,9 +216,9 @@ export function prepare_page(target: Target): Promise<{
let redirect: Redirect = null;
let error: { statusCode: number, message: Error | string } = null;
+ let page_data: PageData;
const preload_context = {
- store,
fetch: (url: string, opts?: any) => fetch(url, opts),
redirect: (statusCode: number, location: string) => {
if (redirect && (redirect.statusCode !== statusCode || redirect.location !== location)) {
@@ -267,11 +243,13 @@ export function prepare_page(target: Target): Promise<{
}
return Promise.all(page.parts.map((part, i) => {
- if (i < changed_from) return null;
+ const segment = new_segments[i];
+
+ if (i < changed_from || !part) return previous_thingummy[i];
if (!part) return null;
return load_component(components[part.i]).then(({ default: Component, preload }) => {
- const req = {
+ page_data = {
path,
query,
params: part.params ? part.params(target.match) : {}
@@ -280,7 +258,7 @@ export function prepare_page(target: Target): Promise<{
let preloaded;
if (ready || !initial_data.preloaded[i + 1]) {
preloaded = preload
- ? preload.call(preload_context, req)
+ ? preload.call(preload_context, page_data)
: {};
} else {
preloaded = initial_data.preloaded[i + 1];
@@ -304,72 +282,55 @@ export function prepare_page(target: Target): Promise<{
}
}).then(results => {
if (redirect) {
- return { redirect, new_segments };
+ return { redirect, new_segments, page: null };
}
- const get_params = page.parts[page.parts.length - 1].params || (() => ({}));
- const params = get_params(target.match);
+ const deepest = page.parts[page.parts.length - 1];
+
+ const page_data = {
+ path,
+ query,
+ params: deepest.params ? deepest.params(target.match) : {}
+ };
if (error) {
- const props = {
- path,
- query,
- params,
- error: typeof error.message === 'string' ? new Error(error.message) : error.message,
- status: error.statusCode
- };
-
return {
- nullable_depth: 0,
new_segments,
- data: Object.assign({}, props, {
+ page: page_data,
+ data: {
child: {
component: ErrorComponent,
- props
+ props: {
+ error: typeof error.message === 'string' ? new Error(error.message) : error.message,
+ status: error.statusCode
+ }
}
- })
+ }
};
}
- const props = { path, query, error: null, status: null };
- const data = {
- path,
- child: Object.assign({}, root_props.child, {
+ const props = {
+ child: {
segment: new_segments[0]
- })
+ }
};
- if (changed(query, root_props.query)) data.query = query;
- if (changed(params, root_props.params)) data.params = params;
- let level = data.child;
- let nullable_depth = 0;
+ let level = props.child;
for (let i = 0; i < page.parts.length; i += 1) {
const part = page.parts[i];
if (!part) continue;
- const get_params = part.params || (() => ({}));
-
- if (i < changed_from) {
- level.props.path = path;
- level.props.query = query;
- level.props.child = Object.assign({}, level.props.child);
-
- nullable_depth += 1;
- } else {
- level.component = results[i].Component;
- level.props = Object.assign({}, level.props, props, {
- params: get_params(target.match),
- }, results[i].preloaded);
-
- level.props.child = {};
- }
+ level.component = results[i].Component;
+ level.props = Object.assign({}, results[i].preloaded, {
+ child: {}
+ });
level = level.props.child;
level.segment = new_segments[i + 1];
}
- return { data, nullable_depth, new_segments };
+ return { data: props, new_segments, page: page_data, results };
});
}
@@ -402,8 +363,4 @@ export function load_component(component: ComponentLoader): Promise<{
function detach(node: Node) {
node.parentNode.removeChild(node);
-}
-
-function changed(a: Record, b: Record) {
- return JSON.stringify(a) !== JSON.stringify(b);
-}
+}
\ No newline at end of file
diff --git a/templates/src/app/index.ts b/templates/src/app/index.ts
index 5fbc232..46d3610 100644
--- a/templates/src/app/index.ts
+++ b/templates/src/app/index.ts
@@ -1,6 +1,5 @@
import { getContext } from 'svelte';
-import { CONTEXT_KEY } from '@sapper/internal';
-import * as stores from '../shared/stores';
+import { CONTEXT_KEY, stores } from '@sapper/internal';
export const preloading = { subscribe: stores.preloading.subscribe };
export const page = { subscribe: stores.page.subscribe };
diff --git a/templates/src/app/types.ts b/templates/src/app/types.ts
index d3388c3..da3f7ad 100644
--- a/templates/src/app/types.ts
+++ b/templates/src/app/types.ts
@@ -65,4 +65,10 @@ export type Redirect = {
export type Store = {
get: () => any;
-}
\ No newline at end of file
+}
+
+export type PageData = {
+ path: string;
+ params: Record;
+ query: Record;
+};
\ No newline at end of file
diff --git a/templates/src/server/middleware/get_page_handler.ts b/templates/src/server/middleware/get_page_handler.ts
index a2ccc19..c695180 100644
--- a/templates/src/server/middleware/get_page_handler.ts
+++ b/templates/src/server/middleware/get_page_handler.ts
@@ -4,9 +4,9 @@ import cookie from 'cookie';
import devalue from 'devalue';
import fetch from 'node-fetch';
import URL from 'url';
-import * as stores from '../../shared/stores';
import { build_dir, dev, src_dir, IGNORE } from '../placeholders';
import { Manifest, Page, Props, Req, Res } from './types';
+import { stores } from '@sapper/internal';
import App from '@sapper/App.html';
export function get_page_handler(
@@ -135,6 +135,7 @@ export function get_page_handler(
let preloaded;
let match;
+ let params;
try {
const root_preloaded = manifest.root_preload
@@ -147,16 +148,20 @@ export function get_page_handler(
match = error ? null : page.pattern.exec(req.path);
+
let toPreload = [root_preloaded];
if (!isSWIndexHtml) {
toPreload = toPreload.concat(page.parts.map(part => {
if (!part) return null;
+ // the deepest level is used below, to initialise the store
+ params = part.params ? part.params(match) : {};
+
return part.preload
? part.preload.call(preload_context, {
path: req.path,
query: req.query,
- params: part.params ? part.params(match) : {}
+ params
})
: {};
}))
@@ -186,60 +191,46 @@ export function get_page_handler(
const segments = req.path.split('/').filter(Boolean);
- const props: Props = {
- path: req.path,
- query: req.query,
- params: {},
- child: null
- };
-
- if (error) {
- props.error = error instanceof Error ? error : { message: error };
- props.status = status;
- }
-
- const data = Object.assign({}, props, preloaded[0], {
- params: {},
+ const props = Object.assign({}, preloaded[0], {
child: {
- segment: segments[0]
+ segment: segments[0],
+ props: {}
}
});
- let level = data.child;
- if (isSWIndexHtml) {
- level.props = Object.assign({}, props, {
- params: {}
- })
- } else {
+ let level = props.child;
+ if (!isSWIndexHtml) {
for (let i = 0; i < page.parts.length; i += 1) {
const part = page.parts[i];
if (!part) continue;
- const get_params = part.params || (() => ({}));
-
Object.assign(level, {
component: part.component,
- props: Object.assign({}, props, {
- params: get_params(match)
- }, preloaded[i + 1])
+ props: Object.assign({}, preloaded[i + 1])
});
level.props.child = {
- segment: segments[i + 1]
+ segment: segments[i + 1],
+ props: {}
};
level = level.props.child;
}
}
+ if (error) {
+ props.child.props.error = error instanceof Error ? error : { message: error };
+ props.child.props.status = status;
+ }
+
stores.page.set({
path: req.path,
query: req.query,
- params: req.params
+ params: params
});
const { html, head, css } = App.render({
Root: manifest.root,
- props: data,
+ props: props,
session
});
@@ -313,6 +304,7 @@ export function get_page_handler(
res.statusCode = status;
res.end(body);
} catch(err) {
+ console.log(err);
if (error) {
// we encountered an error while rendering the error page — oops
res.statusCode = 500;
diff --git a/templates/src/server/middleware/types.ts b/templates/src/server/middleware/types.ts
index 6d7597f..b76b75c 100644
--- a/templates/src/server/middleware/types.ts
+++ b/templates/src/server/middleware/types.ts
@@ -12,6 +12,7 @@ export type Page = {
name: string;
component: Component;
params?: (match: RegExpMatchArray) => Record;
+ preload?: (data: any) => any | Promise;
}>
};
@@ -19,6 +20,7 @@ export type Manifest = {
server_routes: ServerRoute[];
pages: Page[];
root: Component;
+ root_preload?: (data: any) => any | Promise;
error: Component;
}
@@ -29,9 +31,6 @@ export type Store = {
};
export type Props = {
- path: string;
- query: Record;
- params: Record;
error?: { message: string };
status?: number;
child: {
@@ -64,6 +63,5 @@ interface Component {
head: string;
css: { code: string, map: any };
html: string
- },
- preload: (data: any) => any | Promise
+ }
}
\ No newline at end of file
diff --git a/templates/src/shared/stores.ts b/templates/src/shared/stores.ts
deleted file mode 100644
index f66a43b..0000000
--- a/templates/src/shared/stores.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { writable } from 'svelte/store';
-
-export const preloading = writable(null);
-export const page = writable(null);
\ No newline at end of file
diff --git a/test/apps/basics/src/routes/[slug].html b/test/apps/basics/src/routes/[slug].html
index ade66de..e31f6d9 100644
--- a/test/apps/basics/src/routes/[slug].html
+++ b/test/apps/basics/src/routes/[slug].html
@@ -1 +1,5 @@
-{params.slug.toUpperCase()}
\ No newline at end of file
+
+
+{$page.params.slug.toUpperCase()}
\ No newline at end of file
diff --git a/test/apps/basics/src/routes/echo-query/index.html b/test/apps/basics/src/routes/echo-query/index.html
index aa09b35..614bb4d 100644
--- a/test/apps/basics/src/routes/echo-query/index.html
+++ b/test/apps/basics/src/routes/echo-query/index.html
@@ -1 +1,5 @@
-{JSON.stringify(query)}
\ No newline at end of file
+
+
+{JSON.stringify($page.query)}
\ No newline at end of file
diff --git a/test/apps/encoding/src/routes/echo/page/[slug].html b/test/apps/encoding/src/routes/echo/page/[slug].html
index deead40..e2c9491 100644
--- a/test/apps/encoding/src/routes/echo/page/[slug].html
+++ b/test/apps/encoding/src/routes/echo/page/[slug].html
@@ -7,8 +7,8 @@
-{slug} {JSON.stringify(query)}
\ No newline at end of file
+{slug} {JSON.stringify($page.query)}
\ No newline at end of file
diff --git a/test/apps/layout/src/client.js b/test/apps/layout/src/client.js
index 6cce7e6..c492517 100644
--- a/test/apps/layout/src/client.js
+++ b/test/apps/layout/src/client.js
@@ -2,6 +2,11 @@ import * as sapper from '@sapper/app';
window.start = () => sapper.start({
target: document.querySelector('#sapper')
+}).catch(err => {
+ console.error(`OH NO! ${err.message}`);
+ throw err;
+}).then(() => {
+ console.log(`STARTED`);
});
window.prefetchRoutes = () => sapper.prefetchRoutes();
diff --git a/test/apps/layout/src/routes/[x]/[y]/[z].html b/test/apps/layout/src/routes/[x]/[y]/[z].html
index b51089c..38c5575 100644
--- a/test/apps/layout/src/routes/[x]/[y]/[z].html
+++ b/test/apps/layout/src/routes/[x]/[y]/[z].html
@@ -9,10 +9,10 @@
-z: {segment} {count}
+z: {$page.params.z} {count}
click me
\ No newline at end of file
diff --git a/test/apps/layout/src/routes/[x]/[y]/_layout.html b/test/apps/layout/src/routes/[x]/[y]/_layout.html
index f60dd63..401f654 100644
--- a/test/apps/layout/src/routes/[x]/[y]/_layout.html
+++ b/test/apps/layout/src/routes/[x]/[y]/_layout.html
@@ -9,13 +9,13 @@
-y: {segment} {count}
+y: {$page.params.y} {count}
child segment: {child.segment}
\ No newline at end of file
diff --git a/test/apps/layout/test.ts b/test/apps/layout/test.ts
index 36eda4e..c088432 100644
--- a/test/apps/layout/test.ts
+++ b/test/apps/layout/test.ts
@@ -26,10 +26,18 @@ describe('layout', function() {
it('only recreates components when necessary', async () => {
await page.goto(`${base}/foo/bar/baz`);
- await start();
const text1 = String(await page.evaluate(() => document.querySelector('#sapper').textContent));
- assert.deepEqual(text1.split('\n').filter(Boolean), [
+ assert.deepEqual(text1.split('\n').filter(Boolean).map(str => str.trim()), [
+ 'y: bar 1',
+ 'z: baz 1',
+ 'click me',
+ 'child segment: baz'
+ ]);
+
+ await start();
+ const text2 = String(await page.evaluate(() => document.querySelector('#sapper').textContent));
+ assert.deepEqual(text2.split('\n').filter(Boolean).map(str => str.trim()), [
'y: bar 1',
'z: baz 1',
'click me',
@@ -39,8 +47,8 @@ describe('layout', function() {
await page.click('[href="foo/bar/qux"]');
await wait(50);
- const text2 = String(await page.evaluate(() => document.querySelector('#sapper').textContent));
- assert.deepEqual(text2.split('\n').filter(Boolean), [
+ const text3 = String(await page.evaluate(() => document.querySelector('#sapper').textContent));
+ assert.deepEqual(text3.split('\n').filter(Boolean).map(str => str.trim()), [
'y: bar 1',
'z: qux 2',
'click me',
diff --git a/test/apps/preloading/src/routes/prefetch/[slug]/index.html b/test/apps/preloading/src/routes/prefetch/[slug]/index.html
index d13188c..6eebc4c 100644
--- a/test/apps/preloading/src/routes/prefetch/[slug]/index.html
+++ b/test/apps/preloading/src/routes/prefetch/[slug]/index.html
@@ -1 +1,5 @@
-{params.slug}
+
+
+{$page.params.slug}