From 85c86b556202748df6004ab80fc65230cbc35f27 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Sat, 2 Feb 2019 12:53:36 -0500 Subject: [PATCH] tidy up --- templates/src/app/app.ts | 115 +++++++++++----------- templates/src/app/goto/index.ts | 4 +- templates/src/app/prefetch/index.ts | 6 +- templates/src/app/prefetchRoutes/index.ts | 12 +-- templates/src/app/start/index.ts | 8 +- templates/src/app/types.ts | 11 ++- 6 files changed, 80 insertions(+), 76 deletions(-) diff --git a/templates/src/app/app.ts b/templates/src/app/app.ts index 098b31e..54250c1 100644 --- a/templates/src/app/app.ts +++ b/templates/src/app/app.ts @@ -9,14 +9,17 @@ import { Redirect, ComponentLoader, ComponentConstructor, - Page, - PageData + Route, + Page } from './types'; import goto from './goto'; +// injected at build time +declare const __IGNORE__, __COMPONENTS__, __PAGES__, __SAPPER__; + const ignore = __IGNORE__; export const components: ComponentLoader[] = __COMPONENTS__; -export const pages: Page[] = __PAGES__; +export const routes: Route[] = __PAGES__; let ready = false; let root_component: Component; @@ -64,19 +67,19 @@ export { _history as history }; export const scroll_history: Record = {}; -export function select_route(url: URL): Target { +export function select_target(url: URL): Target { if (url.origin !== location.origin) return null; if (!url.pathname.startsWith(initial_data.baseUrl)) return null; const path = url.pathname.slice(initial_data.baseUrl.length); - // avoid accidental clashes between server routes and pages + // avoid accidental clashes between server routes and page routes if (ignore.some(pattern => pattern.test(path))) return; - for (let i = 0; i < pages.length; i += 1) { - const page = pages[i]; + for (let i = 0; i < routes.length; i += 1) { + const route = routes[i]; - const match = page.pattern.exec(path); + const match = route.pattern.exec(path); if (match) { const query: Record = Object.create(null); if (url.search.length > 0) { @@ -84,11 +87,22 @@ export function select_route(url: URL): Target { let [, key, value] = /([^=]*)(?:=(.*))?/.exec(decodeURIComponent(searchParam)); value = (value || '').replace(/\+/g, ' '); if (typeof query[key] === 'string') query[key] = [query[key]]; - if (typeof query[key] === 'object') query[key].push(value); + if (typeof query[key] === 'object') (query[key] as string[]).push(value); else query[key] = value; }); } - return { url, path, page, match, query }; + + const part = route.parts[route.parts.length - 1]; + const params = part.params ? part.params(match) : {}; + + return { + href: url.href, + path, + route, + match, + query, + params + }; } } } @@ -122,23 +136,23 @@ export async function navigate(target: Target, id: number, noscroll?: boolean, h // TODO path, params, query }); } - const loaded = prefetching && prefetching.href === target.url.href ? + const loaded = prefetching && prefetching.href === target.href ? prefetching.promise : - prepare_page(target); + hydrate_target(target); prefetching = null; const token = current_token = {}; - const { redirect, page, data, branch } = await loaded; + const { redirect, page, props, branch } = await loaded; if (redirect) return goto(redirect.location, { replaceState: true }); - await render(branch, data, page, scroll_history[id], noscroll, hash, token); + await render(branch, props, page, scroll_history[id], noscroll, hash, token); if (document.activeElement) document.activeElement.blur(); } -async function render(branch: any[], props: any, page: PageData, scroll: ScrollPosition, noscroll: boolean, hash: string, token: {}) { +async function render(branch: any[], props: any, page: Page, scroll: ScrollPosition, noscroll: boolean, hash: string, token: {}) { if (current_token !== token) return; stores.page.set(page); @@ -191,17 +205,31 @@ async function render(branch: any[], props: any, page: PageData, scroll: ScrollP ready = true; } -export async function prepare_page(target: Target): Promise<{ +export async function hydrate_target(target: Target): Promise<{ redirect?: Redirect; - data?: any; - page: PageData + props?: any; + page?: Page; + branch?: Array<{ Component: ComponentConstructor, preload: (page) => Promise, segment: string }> }> { - const { page, path, query } = target; + const { route, path, query, params } = target; const segments = path.split('/').filter(Boolean); let redirect: Redirect = null; let error: { statusCode: number, message: Error | string } = null; + const preload_context = { + fetch: (url: string, opts?: any) => fetch(url, opts), + redirect: (statusCode: number, location: string) => { + if (redirect && (redirect.statusCode !== statusCode || redirect.location !== location)) { + throw new Error(`Conflicting redirects`); + } + redirect = { statusCode, location }; + }, + error: (statusCode: number, message: Error | string) => { + error = { statusCode, message }; + } + }; + if (!root_preload) { const preload_fn = RootStatic['pre' + 'load']; // Rollup makes us jump through these hoops :( root_preload = preload_fn @@ -216,20 +244,7 @@ export async function prepare_page(target: Target): Promise<{ let branch; try { - const preload_context = { - fetch: (url: string, opts?: any) => fetch(url, opts), - redirect: (statusCode: number, location: string) => { - if (redirect && (redirect.statusCode !== statusCode || redirect.location !== location)) { - throw new Error(`Conflicting redirects`); - } - redirect = { statusCode, location }; - }, - error: (statusCode: number, message: Error | string) => { - error = { statusCode, message }; - } - }; - - branch = await Promise.all(page.parts.map(async (part, i) => { + branch = await Promise.all(route.parts.map(async (part, i) => { if (!part) return null; const segment = segments[i]; @@ -259,22 +274,18 @@ export async function prepare_page(target: Target): Promise<{ if (!root_data) root_data = await root_preload; - if (redirect) { - return { redirect, page: null }; - } - - const deepest = page.parts[page.parts.length - 1]; + if (redirect) return { redirect }; const page_data = { path, query, - params: deepest.params ? deepest.params(target.match) : {} + params }; if (error) { return { page: page_data, - data: { + props: { child: { component: ErrorComponent, props: { @@ -287,28 +298,20 @@ export async function prepare_page(target: Target): Promise<{ }; } - const props = { - child: { - segment: segments[0] - } - }; - + const props = { child: {} }; let level = props.child; - for (let i = 0; i < page.parts.length; i += 1) { - const part = page.parts[i]; - if (!part) continue; + branch.forEach(node => { + if (!node) return; - level.component = branch[i].Component; - level.props = Object.assign({}, branch[i].preloaded, { - child: {} - }); + level.segment = node.segment; + level.component = node.Component; + level.props = Object.assign({}, node.preloaded, { child: {} }); level = level.props.child; - level.segment = segments[i + 1]; - } + }); - return { data: props, page: page_data, branch }; + return { props, page: page_data, branch }; } function load_css(chunk: string) { diff --git a/templates/src/app/goto/index.ts b/templates/src/app/goto/index.ts index 29757ed..0241b2c 100644 --- a/templates/src/app/goto/index.ts +++ b/templates/src/app/goto/index.ts @@ -1,7 +1,7 @@ -import { history, select_route, navigate, cid } from '../app'; +import { history, select_target, navigate, cid } from '../app'; export default function goto(href: string, opts = { replaceState: false }) { - const target = select_route(new URL(href, document.baseURI)); + const target = select_target(new URL(href, document.baseURI)); if (target) { history[opts.replaceState ? 'replaceState' : 'pushState']({ id: cid }, '', href); diff --git a/templates/src/app/prefetch/index.ts b/templates/src/app/prefetch/index.ts index cd22c63..d959ef3 100644 --- a/templates/src/app/prefetch/index.ts +++ b/templates/src/app/prefetch/index.ts @@ -1,12 +1,12 @@ -import { select_route, prefetching, set_prefetching, prepare_page } from '../app'; +import { select_target, prefetching, set_prefetching, hydrate_target } from '../app'; import { Target } from '../types'; export default function prefetch(href: string) { - const target: Target = select_route(new URL(href, document.baseURI)); + const target: Target = select_target(new URL(href, document.baseURI)); if (target) { if (!prefetching || href !== prefetching.href) { - set_prefetching(href, prepare_page(target)); + set_prefetching(href, hydrate_target(target)); } return prefetching.promise; diff --git a/templates/src/app/prefetchRoutes/index.ts b/templates/src/app/prefetchRoutes/index.ts index b3c2f3c..b062814 100644 --- a/templates/src/app/prefetchRoutes/index.ts +++ b/templates/src/app/prefetchRoutes/index.ts @@ -1,11 +1,11 @@ -import { components, pages, load_component } from "../app"; +import { components, routes, load_component } from "../app"; export default function prefetchRoutes(pathnames: string[]) { - return pages - .filter(route => { - if (!pathnames) return true; - return pathnames.some(pathname => route.pattern.test(pathname)); - }) + return routes + .filter(pathnames + ? route => pathnames.some(pathname => route.pattern.test(pathname)) + : () => true + ) .reduce((promise: Promise, route) => promise.then(() => { return Promise.all(route.parts.map(part => part && load_component(components[part.i]))); }), Promise.resolve()); diff --git a/templates/src/app/start/index.ts b/templates/src/app/start/index.ts index ab9d669..e371d83 100644 --- a/templates/src/app/start/index.ts +++ b/templates/src/app/start/index.ts @@ -5,7 +5,7 @@ import { navigate, scroll_history, scroll_state, - select_route, + select_target, set_target, uid, set_uid, @@ -35,7 +35,7 @@ export default function start(opts: { history.replaceState({ id: uid }, '', href); if (!initial_data.error) { - const target = select_route(new URL(location.href)); + const target = select_target(new URL(location.href)); if (target) return navigate(target, uid, false, hash); } }); @@ -92,7 +92,7 @@ function handle_click(event: MouseEvent) { // Don't handle hash changes if (url.pathname === location.pathname && url.search === location.search) return; - const target = select_route(url); + const target = select_target(url); if (target) { const noscroll = a.hasAttribute('sapper-noscroll'); navigate(target, null, noscroll, url.hash); @@ -115,7 +115,7 @@ function handle_popstate(event: PopStateEvent) { if (event.state) { const url = new URL(location.href); - const target = select_route(url); + const target = select_target(url); if (target) { navigate(target, event.state.id); } else { diff --git a/templates/src/app/types.ts b/templates/src/app/types.ts index 04ade83..52f89c8 100644 --- a/templates/src/app/types.ts +++ b/templates/src/app/types.ts @@ -23,7 +23,7 @@ export type ComponentLoader = { css: string[] }; -export type Page = { +export type Route = { pattern: RegExp; parts: Array<{ i: number; @@ -35,7 +35,7 @@ export type Manifest = { ignore: RegExp[]; root: ComponentConstructor; error: () => Promise<{ default: ComponentConstructor }>; - pages: Page[] + pages: Route[] }; export type ScrollPosition = { @@ -44,11 +44,12 @@ export type ScrollPosition = { }; export type Target = { - url: URL; + href: string; path: string; - page: Page; + route: Route; match: RegExpExecArray; query: Record; + params: Record; }; export type Redirect = { @@ -60,7 +61,7 @@ export type Store = { get: () => any; } -export type PageData = { +export type Page = { path: string; params: Record; query: Record;