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} {/if} - \ No newline at end of file + \ No newline at end of file diff --git a/test/apps/preloading/src/routes/prefetch/[slug]/index.html b/test/apps/preloading/src/routes/prefetch/[slug]/index.svelte similarity index 100% rename from test/apps/preloading/src/routes/prefetch/[slug]/index.html rename to test/apps/preloading/src/routes/prefetch/[slug]/index.svelte diff --git a/test/apps/preloading/src/routes/prefetch/_layout.html b/test/apps/preloading/src/routes/prefetch/_layout.html deleted file mode 100644 index f11eb2afa..000000000 --- a/test/apps/preloading/src/routes/prefetch/_layout.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/apps/preloading/src/routes/prefetch/index.html b/test/apps/preloading/src/routes/prefetch/index.svelte similarity index 100% rename from test/apps/preloading/src/routes/prefetch/index.html rename to test/apps/preloading/src/routes/prefetch/index.svelte diff --git a/test/apps/preloading/src/routes/preload-root/index.html b/test/apps/preloading/src/routes/preload-root/index.html deleted file mode 100644 index 41f384a3c..000000000 --- a/test/apps/preloading/src/routes/preload-root/index.html +++ /dev/null @@ -1 +0,0 @@ -

root preload function ran: {rootPreloadFunctionRan}

\ No newline at end of file diff --git a/test/apps/preloading/src/routes/preload-root/index.svelte b/test/apps/preloading/src/routes/preload-root/index.svelte new file mode 100644 index 000000000..b56ec231d --- /dev/null +++ b/test/apps/preloading/src/routes/preload-root/index.svelte @@ -0,0 +1,6 @@ + + +

root preload function ran: {rootPreloadFunctionRan}

\ No newline at end of file diff --git a/test/apps/preloading/src/routes/preload-values/custom-class.html b/test/apps/preloading/src/routes/preload-values/custom-class.svelte similarity index 100% rename from test/apps/preloading/src/routes/preload-values/custom-class.html rename to test/apps/preloading/src/routes/preload-values/custom-class.svelte diff --git a/test/apps/preloading/src/routes/preload-values/index.html b/test/apps/preloading/src/routes/preload-values/index.html deleted file mode 100644 index 8c0dcbb0a..000000000 --- a/test/apps/preloading/src/routes/preload-values/index.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/apps/preloading/src/routes/preload-values/set.html b/test/apps/preloading/src/routes/preload-values/set.svelte similarity index 100% rename from test/apps/preloading/src/routes/preload-values/set.html rename to test/apps/preloading/src/routes/preload-values/set.svelte diff --git a/test/apps/layout/src/routes/[slug].svelte b/webpack/[slug].svelte similarity index 100% rename from test/apps/layout/src/routes/[slug].svelte rename to webpack/[slug].svelte