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

Slot-based routing #574

Merged
merged 9 commits into from
Feb 21, 2019
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
8 changes: 7 additions & 1 deletion runtime/internal/error.svelte
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
<svelte:component this={child.component} {...child.props}/>
<h1>{status}</h1>

<p>{error.message}</p>

{#if process.env.NODE_ENV === 'development'}
<pre>{error.stack}</pre>
{/if}
2 changes: 1 addition & 1 deletion runtime/internal/layout.svelte
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<svelte:component this={child.component} {...child.props}/>
<slot></slot>
77 changes: 25 additions & 52 deletions runtime/src/app/app.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 <head> contents
const start = document.querySelector('#sapper-head-start');
const end = document.querySelector('#sapper-head-end');
Expand All @@ -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
});
}
Expand All @@ -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<any>, segment: string }>
branch?: Array<{ Component: ComponentConstructor, preload: (page) => Promise<any>, 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),
Expand All @@ -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;
}
};

Expand All @@ -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]) {
Expand All @@ -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) {
Expand Down
57 changes: 30 additions & 27 deletions runtime/src/server/middleware/get_page_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 = <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(',')}]`,
Expand Down
4 changes: 2 additions & 2 deletions src/api/build.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions src/api/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion src/api/utils/copy_runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const runtime = [
'app.mjs',
'server.mjs',
'internal/shared.mjs',
'internal/Sapper.svelte',
'internal/layout.svelte',
'internal/error.svelte'
].map(file => ({
Expand Down
2 changes: 1 addition & 1 deletion src/core.ts
Original file line number Diff line number Diff line change
@@ -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';
Loading