diff --git a/runtime/internal/error.svelte b/runtime/internal/error.svelte
index 8c0dcbb0a..ef7581b50 100644
--- a/runtime/internal/error.svelte
+++ b/runtime/internal/error.svelte
@@ -1 +1,7 @@
-
\ No newline at end of file
+
{status}
+
+{error.message}
+
+{#if process.env.NODE_ENV === 'development'}
+ {error.stack}
+{/if}
\ No newline at end of file
diff --git a/runtime/internal/layout.svelte b/runtime/internal/layout.svelte
index 8c0dcbb0a..49aeb95a1 100644
--- a/runtime/internal/layout.svelte
+++ b/runtime/internal/layout.svelte
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/runtime/src/app/app.ts b/runtime/src/app/app.ts
index 1d7decbe7..2de70527b 100644
--- a/runtime/src/app/app.ts
+++ b/runtime/src/app/app.ts
@@ -1,5 +1,5 @@
import { writable } from 'svelte/store.mjs';
-import Sapper from '@sapper/internal/Sapper.svelte';
+import App from '@sapper/internal/App.svelte';
import { stores } from '@sapper/internal/shared';
import { Root, root_preload, ErrorComponent, ignore, components, routes } from '@sapper/internal/manifest-client';
import {
@@ -180,8 +180,13 @@ async function render(redirect: Redirect, branch: any[], props: any, page: Page)
stores.preloading.set(false);
if (root_component) {
- root_component.props = props;
+ root_component.$set(props);
} else {
+ props.session = session;
+ props.level0 = {
+ props: await root_preloaded
+ };
+
// first load — remove SSR'd contents
const start = document.querySelector('#sapper-head-start');
const end = document.querySelector('#sapper-head-end');
@@ -192,13 +197,9 @@ async function render(redirect: Redirect, branch: any[], props: any, page: Page)
detach(end);
}
- root_component = new Sapper({
+ root_component = new App({
target,
- props: {
- Root,
- props,
- session
- },
+ props,
hydrate: true
});
}
@@ -211,13 +212,14 @@ async function render(redirect: Redirect, branch: any[], props: any, page: Page)
export async function hydrate_target(target: Target): Promise<{
redirect?: Redirect;
props?: any;
- branch?: Array<{ Component: ComponentConstructor, preload: (page) => Promise, segment: string }>
+ branch?: Array<{ Component: ComponentConstructor, preload: (page) => Promise, segment: string }>;
}> {
const { route, page } = target;
const segments = page.path.split('/').filter(Boolean);
let redirect: Redirect = null;
- let error: { statusCode: number, message: Error | string } = null;
+
+ const props = { error: null, status: 200, segments: [segments[0]] };
const preload_context = {
fetch: (url: string, opts?: any) => fetch(url, opts),
@@ -227,8 +229,9 @@ export async function hydrate_target(target: Target): Promise<{
}
redirect = { statusCode, location };
},
- error: (statusCode: number, message: Error | string) => {
- error = { statusCode, message };
+ error: (status: number, error: Error | string) => {
+ props.error = typeof error === 'string' ? new Error(error) : error;
+ props.status = status;
}
};
@@ -241,15 +244,19 @@ export async function hydrate_target(target: Target): Promise<{
}
let branch;
+ let l = 1;
try {
branch = await Promise.all(route.parts.map(async (part, i) => {
+ props.segments[l] = segments[i + 1]; // TODO make this less confusing
if (!part) return null;
+ const j = l++;
+
const segment = segments[i];
if (!session_dirty && current_branch[i] && current_branch[i].segment === segment) return current_branch[i];
- const { default: Component, preload } = await load_component(components[part.i]);
+ const { default: component, preload } = await load_component(components[part.i]);
let preloaded;
if (ready || !initial_data.preloaded[i + 1]) {
@@ -264,49 +271,15 @@ export async function hydrate_target(target: Target): Promise<{
preloaded = initial_data.preloaded[i + 1];
}
- return { Component, preloaded, segment };
+ return (props[`level${j}`] = { component, props: preloaded, segment });
}));
- } catch (e) {
- error = { statusCode: 500, message: e };
+ } catch (error) {
+ props.error = error;
+ props.status = 500;
branch = [];
}
- if (redirect) return { redirect };
-
- if (error) {
- // TODO be nice if this was less of a special case
- return {
- props: {
- child: {
- component: ErrorComponent,
- props: {
- error: typeof error.message === 'string' ? new Error(error.message) : error.message,
- status: error.statusCode
- }
- }
- },
- branch
- };
- }
-
- const props = Object.assign({}, await root_preloaded, {
- child: { segment: segments[0] }
- });
-
- let level = props.child;
-
- branch.forEach((node, i) => {
- if (!node) return;
-
- level.component = node.Component;
- level.props = Object.assign({}, node.preloaded, {
- child: { segment: segments[i + 1] }
- });
-
- level = level.props.child;
- });
-
- return { props, branch };
+ return { redirect, props, branch };
}
function load_css(chunk: string) {
diff --git a/runtime/src/server/middleware/get_page_handler.ts b/runtime/src/server/middleware/get_page_handler.ts
index bb79970ca..c0a39cc0c 100644
--- a/runtime/src/server/middleware/get_page_handler.ts
+++ b/runtime/src/server/middleware/get_page_handler.ts
@@ -9,7 +9,7 @@ import { IGNORE } from '../constants';
import { Manifest, Page, Props, Req, Res } from './types';
import { build_dir, dev, src_dir } from '@sapper/internal/manifest-server';
import { stores } from '@sapper/internal/shared';
-import Sapper from '@sapper/internal/Sapper.svelte';
+import App from '@sapper/internal/App.svelte';
export function get_page_handler(
manifest: Manifest,
@@ -38,7 +38,7 @@ export function get_page_handler(
}
async function handle_page(page: Page, req: Req, res: Res, status = 200, error: Error | string = null) {
- const isSWIndexHtml = req.path === '/service-worker-index.html';
+ const is_service_worker_index = req.path === '/service-worker-index.html';
const build_info: {
bundler: 'rollup' | 'webpack',
shimport: string | null,
@@ -52,7 +52,7 @@ export function get_page_handler(
// preload main.js and current route
// TODO detect other stuff we can preload? images, CSS, fonts?
let preloaded_chunks = Array.isArray(build_info.assets.main) ? build_info.assets.main : [build_info.assets.main];
- if (!error && !isSWIndexHtml) {
+ if (!error && !is_service_worker_index) {
page.parts.forEach(part => {
if (!part) return;
@@ -152,7 +152,7 @@ export function get_page_handler(
let toPreload = [root_preloaded];
- if (!isSWIndexHtml) {
+ if (!is_service_worker_index) {
toPreload = toPreload.concat(page.parts.map(part => {
if (!part) return null;
@@ -193,48 +193,51 @@ export function get_page_handler(
const segments = req.path.split('/').filter(Boolean);
- const props = Object.assign({}, preloaded[0], {
- child: {
+ // TODO make this less confusing
+ const layout_segments = [segments[0]];
+ let l = 1;
+
+ page.parts.forEach((part, i) => {
+ layout_segments[l] = segments[i + 1];
+ if (!part) return null;
+ l++;
+ });
+
+ const props = {
+ segments: layout_segments,
+ status: error ? status : 200,
+ error: error ? error instanceof Error ? error : { message: error } : null,
+ session: writable(session),
+ level0: {
+ props: preloaded[0]
+ },
+ level1: {
segment: segments[0],
props: {}
}
- });
+ };
- let level = props.child;
- if (!isSWIndexHtml) {
+ if (!is_service_worker_index) {
+ let l = 1;
for (let i = 0; i < page.parts.length; i += 1) {
const part = page.parts[i];
if (!part) continue;
- Object.assign(level, {
+ props[`level${l++}`] = {
component: part.component,
- props: Object.assign({}, preloaded[i + 1])
- });
-
- level.props.child = {
- segment: segments[i + 1],
- props: {}
+ props: preloaded[i + 1],
+ segment: segments[i]
};
- 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: params
});
- const { html, head, css } = Sapper.render({
- Root: manifest.root,
- props: props,
- session: writable(session)
- });
+ const { html, head, css } = App.render(props);
const serialized = {
preloaded: `[${preloaded.map(data => try_serialize(data)).join(',')}]`,
diff --git a/src/api/build.ts b/src/api/build.ts
index 9177cead9..fbfdf5a4b 100644
--- a/src/api/build.ts
+++ b/src/api/build.ts
@@ -1,7 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
import minify_html from './utils/minify_html';
-import { create_compilers, create_main_manifests, create_manifest_data, create_serviceworker_manifest } from '../core';
+import { create_compilers, create_app, create_manifest_data, create_serviceworker_manifest } from '../core';
import { copy_shimport } from './utils/copy_shimport';
import read_template from '../core/read_template';
import { CompileResult } from '../core/create_compilers/interfaces';
@@ -71,7 +71,7 @@ export async function build({
const manifest_data = create_manifest_data(routes);
// create src/node_modules/@sapper/app.mjs and server.mjs
- create_main_manifests({
+ create_app({
bundler,
manifest_data,
cwd,
diff --git a/src/api/dev.ts b/src/api/dev.ts
index c5e89cd25..fb356c828 100644
--- a/src/api/dev.ts
+++ b/src/api/dev.ts
@@ -4,7 +4,7 @@ import * as http from 'http';
import * as child_process from 'child_process';
import * as ports from 'port-authority';
import { EventEmitter } from 'events';
-import { create_manifest_data, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core';
+import { create_manifest_data, create_app, create_compilers, create_serviceworker_manifest } from '../core';
import { Compiler, Compilers } from '../core/create_compilers';
import { CompileResult } from '../core/create_compilers/interfaces';
import Deferred from './utils/Deferred';
@@ -162,7 +162,7 @@ class Watcher extends EventEmitter {
try {
manifest_data = create_manifest_data(routes);
- create_main_manifests({
+ create_app({
bundler: this.bundler,
manifest_data,
dev: true,
@@ -190,7 +190,7 @@ class Watcher extends EventEmitter {
() => {
try {
const new_manifest_data = create_manifest_data(routes);
- create_main_manifests({
+ create_app({
bundler: this.bundler,
manifest_data, // TODO is this right? not new_manifest_data?
dev: true,
diff --git a/src/api/utils/copy_runtime.ts b/src/api/utils/copy_runtime.ts
index 36f68e2b3..6f50815f8 100644
--- a/src/api/utils/copy_runtime.ts
+++ b/src/api/utils/copy_runtime.ts
@@ -6,7 +6,6 @@ const runtime = [
'app.mjs',
'server.mjs',
'internal/shared.mjs',
- 'internal/Sapper.svelte',
'internal/layout.svelte',
'internal/error.svelte'
].map(file => ({
diff --git a/src/core.ts b/src/core.ts
index 7d813dc58..80338a4ff 100644
--- a/src/core.ts
+++ b/src/core.ts
@@ -1,3 +1,3 @@
-export * from './core/create_manifests';
+export * from './core/create_app';
export { default as create_compilers } from './core/create_compilers/index';
export { default as create_manifest_data } from './core/create_manifest_data';
\ No newline at end of file
diff --git a/src/core/create_manifests.ts b/src/core/create_app.ts
similarity index 82%
rename from src/core/create_manifests.ts
rename to src/core/create_app.ts
index 04d2b6aa8..983b03b2d 100644
--- a/src/core/create_manifests.ts
+++ b/src/core/create_app.ts
@@ -3,7 +3,7 @@ import * as path from 'path';
import { posixify, stringify, walk, write_if_changed } from '../utils';
import { Page, PageComponent, ManifestData } from '../interfaces';
-export function create_main_manifests({
+export function create_app({
bundler,
manifest_data,
dev_port,
@@ -31,8 +31,11 @@ export function create_main_manifests({
const client_manifest = generate_client_manifest(manifest_data, path_to_routes, bundler, dev, dev_port);
const server_manifest = generate_server_manifest(manifest_data, path_to_routes, cwd, src, dest, dev);
+ const app = generate_app(manifest_data, path_to_routes);
+
write_if_changed(`${output}/internal/manifest-client.mjs`, client_manifest);
write_if_changed(`${output}/internal/manifest-server.mjs`, server_manifest);
+ write_if_changed(`${output}/internal/App.svelte`, app);
}
export function create_serviceworker_manifest({ manifest_data, output, client_files, static_files }: {
@@ -230,6 +233,58 @@ function generate_server_manifest(
`.replace(/^\t{2}/gm, '').trim();
}
+function generate_app(manifest_data: ManifestData, path_to_routes: string) {
+ // TODO remove default layout altogether
+
+ const max_depth = Math.max(...manifest_data.pages.map(page => page.parts.filter(Boolean).length));
+
+ const levels = [];
+ for (let i = 0; i < max_depth; i += 1) {
+ levels.push(i + 1);
+ }
+
+ let l = max_depth;
+
+ let pyramid = ``;
+
+ while (l-- > 1) {
+ pyramid = `
+
+ {#if level${l + 1}}
+ ${pyramid.replace(/\n/g, '\n\t\t\t\t\t')}
+ {/if}
+
+ `.replace(/^\t\t\t/gm, '').trim();
+ }
+
+ return `
+
+
+
+
+ {#if error}
+
+ {:else}
+ ${pyramid.replace(/\n/g, '\n\t\t\t\t')}
+ {/if}
+
+ `.replace(/^\t\t/gm, '').trim();
+}
+
function get_file(path_to_routes: string, component: PageComponent) {
if (component.default) return `./${component.type}.svelte`;
return posixify(`${path_to_routes}/${component.file}`);
diff --git a/test/apps/layout/src/routes/[x]/[y]/[z].html b/test/apps/layout/src/routes/[x]/[y]/[z].svelte
similarity index 100%
rename from test/apps/layout/src/routes/[x]/[y]/[z].html
rename to test/apps/layout/src/routes/[x]/[y]/[z].svelte
diff --git a/test/apps/layout/src/routes/[x]/[y]/_layout.html b/test/apps/layout/src/routes/[x]/[y]/_layout.svelte
similarity index 68%
rename from test/apps/layout/src/routes/[x]/[y]/_layout.html
rename to test/apps/layout/src/routes/[x]/[y]/_layout.svelte
index 401f654a7..38fb80313 100644
--- a/test/apps/layout/src/routes/[x]/[y]/_layout.html
+++ b/test/apps/layout/src/routes/[x]/[y]/_layout.svelte
@@ -12,10 +12,10 @@
import { page } from '@sapper/app';
export let count;
- export let child;
+ export let segment;
y: {$page.params.y} {count}
-
+
-child segment: {child.segment}
\ No newline at end of file
+child segment: {segment}
\ No newline at end of file
diff --git a/test/apps/layout/test.ts b/test/apps/layout/test.ts
index c08843285..86a6d3aad 100644
--- a/test/apps/layout/test.ts
+++ b/test/apps/layout/test.ts
@@ -28,7 +28,7 @@ describe('layout', function() {
await page.goto(`${base}/foo/bar/baz`);
const text1 = String(await page.evaluate(() => document.querySelector('#sapper').textContent));
- assert.deepEqual(text1.split('\n').filter(Boolean).map(str => str.trim()), [
+ assert.deepEqual(text1.split('\n').map(str => str.trim()).filter(Boolean), [
'y: bar 1',
'z: baz 1',
'click me',
@@ -37,7 +37,7 @@ describe('layout', function() {
await start();
const text2 = String(await page.evaluate(() => document.querySelector('#sapper').textContent));
- assert.deepEqual(text2.split('\n').filter(Boolean).map(str => str.trim()), [
+ assert.deepEqual(text2.split('\n').map(str => str.trim()).filter(Boolean), [
'y: bar 1',
'z: baz 1',
'click me',
@@ -48,7 +48,7 @@ describe('layout', function() {
await wait(50);
const text3 = String(await page.evaluate(() => document.querySelector('#sapper').textContent));
- assert.deepEqual(text3.split('\n').filter(Boolean).map(str => str.trim()), [
+ assert.deepEqual(text3.split('\n').map(str => str.trim()).filter(Boolean), [
'y: bar 1',
'z: qux 2',
'click me',
diff --git a/test/apps/preloading/src/routes/_layout.svelte b/test/apps/preloading/src/routes/_layout.svelte
index 09606e205..32be4d87d 100644
--- a/test/apps/preloading/src/routes/_layout.svelte
+++ b/test/apps/preloading/src/routes/_layout.svelte
@@ -8,13 +8,16 @@
{#if $preloading}