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

Commit

Permalink
Slot-based routing (#573)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris authored Feb 21, 2019
1 parent c637687 commit e0de230
Show file tree
Hide file tree
Showing 22 changed files with 141 additions and 99 deletions.
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

0 comments on commit e0de230

Please sign in to comment.