mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-22 07:05:24 +00:00
tidy up
This commit is contained in:
@@ -9,14 +9,17 @@ import {
|
|||||||
Redirect,
|
Redirect,
|
||||||
ComponentLoader,
|
ComponentLoader,
|
||||||
ComponentConstructor,
|
ComponentConstructor,
|
||||||
Page,
|
Route,
|
||||||
PageData
|
Page
|
||||||
} from './types';
|
} from './types';
|
||||||
import goto from './goto';
|
import goto from './goto';
|
||||||
|
|
||||||
|
// injected at build time
|
||||||
|
declare const __IGNORE__, __COMPONENTS__, __PAGES__, __SAPPER__;
|
||||||
|
|
||||||
const ignore = __IGNORE__;
|
const ignore = __IGNORE__;
|
||||||
export const components: ComponentLoader[] = __COMPONENTS__;
|
export const components: ComponentLoader[] = __COMPONENTS__;
|
||||||
export const pages: Page[] = __PAGES__;
|
export const routes: Route[] = __PAGES__;
|
||||||
|
|
||||||
let ready = false;
|
let ready = false;
|
||||||
let root_component: Component;
|
let root_component: Component;
|
||||||
@@ -64,19 +67,19 @@ export { _history as history };
|
|||||||
|
|
||||||
export const scroll_history: Record<string, ScrollPosition> = {};
|
export const scroll_history: Record<string, ScrollPosition> = {};
|
||||||
|
|
||||||
export function select_route(url: URL): Target {
|
export function select_target(url: URL): Target {
|
||||||
if (url.origin !== location.origin) return null;
|
if (url.origin !== location.origin) return null;
|
||||||
if (!url.pathname.startsWith(initial_data.baseUrl)) return null;
|
if (!url.pathname.startsWith(initial_data.baseUrl)) return null;
|
||||||
|
|
||||||
const path = url.pathname.slice(initial_data.baseUrl.length);
|
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;
|
if (ignore.some(pattern => pattern.test(path))) return;
|
||||||
|
|
||||||
for (let i = 0; i < pages.length; i += 1) {
|
for (let i = 0; i < routes.length; i += 1) {
|
||||||
const page = pages[i];
|
const route = routes[i];
|
||||||
|
|
||||||
const match = page.pattern.exec(path);
|
const match = route.pattern.exec(path);
|
||||||
if (match) {
|
if (match) {
|
||||||
const query: Record<string, string | string[]> = Object.create(null);
|
const query: Record<string, string | string[]> = Object.create(null);
|
||||||
if (url.search.length > 0) {
|
if (url.search.length > 0) {
|
||||||
@@ -84,11 +87,22 @@ export function select_route(url: URL): Target {
|
|||||||
let [, key, value] = /([^=]*)(?:=(.*))?/.exec(decodeURIComponent(searchParam));
|
let [, key, value] = /([^=]*)(?:=(.*))?/.exec(decodeURIComponent(searchParam));
|
||||||
value = (value || '').replace(/\+/g, ' ');
|
value = (value || '').replace(/\+/g, ' ');
|
||||||
if (typeof query[key] === 'string') query[key] = [<string>query[key]];
|
if (typeof query[key] === 'string') query[key] = [<string>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;
|
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
|
// TODO path, params, query
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const loaded = prefetching && prefetching.href === target.url.href ?
|
const loaded = prefetching && prefetching.href === target.href ?
|
||||||
prefetching.promise :
|
prefetching.promise :
|
||||||
prepare_page(target);
|
hydrate_target(target);
|
||||||
|
|
||||||
prefetching = null;
|
prefetching = null;
|
||||||
|
|
||||||
const token = current_token = {};
|
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 });
|
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();
|
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;
|
if (current_token !== token) return;
|
||||||
|
|
||||||
stores.page.set(page);
|
stores.page.set(page);
|
||||||
@@ -191,17 +205,31 @@ async function render(branch: any[], props: any, page: PageData, scroll: ScrollP
|
|||||||
ready = true;
|
ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function prepare_page(target: Target): Promise<{
|
export async function hydrate_target(target: Target): Promise<{
|
||||||
redirect?: Redirect;
|
redirect?: Redirect;
|
||||||
data?: any;
|
props?: any;
|
||||||
page: PageData
|
page?: Page;
|
||||||
|
branch?: Array<{ Component: ComponentConstructor, preload: (page) => Promise<any>, segment: string }>
|
||||||
}> {
|
}> {
|
||||||
const { page, path, query } = target;
|
const { route, path, query, params } = target;
|
||||||
const segments = path.split('/').filter(Boolean);
|
const segments = path.split('/').filter(Boolean);
|
||||||
|
|
||||||
let redirect: Redirect = null;
|
let redirect: Redirect = null;
|
||||||
let error: { statusCode: number, message: Error | string } = 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) {
|
if (!root_preload) {
|
||||||
const preload_fn = RootStatic['pre' + 'load']; // Rollup makes us jump through these hoops :(
|
const preload_fn = RootStatic['pre' + 'load']; // Rollup makes us jump through these hoops :(
|
||||||
root_preload = preload_fn
|
root_preload = preload_fn
|
||||||
@@ -216,20 +244,7 @@ export async function prepare_page(target: Target): Promise<{
|
|||||||
let branch;
|
let branch;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const preload_context = {
|
branch = await Promise.all(route.parts.map(async (part, i) => {
|
||||||
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) => {
|
|
||||||
if (!part) return null;
|
if (!part) return null;
|
||||||
|
|
||||||
const segment = segments[i];
|
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 (!root_data) root_data = await root_preload;
|
||||||
|
|
||||||
if (redirect) {
|
if (redirect) return { redirect };
|
||||||
return { redirect, page: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
const deepest = page.parts[page.parts.length - 1];
|
|
||||||
|
|
||||||
const page_data = {
|
const page_data = {
|
||||||
path,
|
path,
|
||||||
query,
|
query,
|
||||||
params: deepest.params ? deepest.params(target.match) : {}
|
params
|
||||||
};
|
};
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return {
|
return {
|
||||||
page: page_data,
|
page: page_data,
|
||||||
data: {
|
props: {
|
||||||
child: {
|
child: {
|
||||||
component: ErrorComponent,
|
component: ErrorComponent,
|
||||||
props: {
|
props: {
|
||||||
@@ -287,28 +298,20 @@ export async function prepare_page(target: Target): Promise<{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = {
|
const props = { child: {} };
|
||||||
child: {
|
|
||||||
segment: segments[0]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let level = props.child;
|
let level = props.child;
|
||||||
|
|
||||||
for (let i = 0; i < page.parts.length; i += 1) {
|
branch.forEach(node => {
|
||||||
const part = page.parts[i];
|
if (!node) return;
|
||||||
if (!part) continue;
|
|
||||||
|
|
||||||
level.component = branch[i].Component;
|
level.segment = node.segment;
|
||||||
level.props = Object.assign({}, branch[i].preloaded, {
|
level.component = node.Component;
|
||||||
child: {}
|
level.props = Object.assign({}, node.preloaded, { child: {} });
|
||||||
});
|
|
||||||
|
|
||||||
level = level.props.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) {
|
function load_css(chunk: string) {
|
||||||
|
|||||||
@@ -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 }) {
|
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) {
|
if (target) {
|
||||||
history[opts.replaceState ? 'replaceState' : 'pushState']({ id: cid }, '', href);
|
history[opts.replaceState ? 'replaceState' : 'pushState']({ id: cid }, '', href);
|
||||||
|
|||||||
@@ -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';
|
import { Target } from '../types';
|
||||||
|
|
||||||
export default function prefetch(href: string) {
|
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 (target) {
|
||||||
if (!prefetching || href !== prefetching.href) {
|
if (!prefetching || href !== prefetching.href) {
|
||||||
set_prefetching(href, prepare_page(target));
|
set_prefetching(href, hydrate_target(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
return prefetching.promise;
|
return prefetching.promise;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { components, pages, load_component } from "../app";
|
import { components, routes, load_component } from "../app";
|
||||||
|
|
||||||
export default function prefetchRoutes(pathnames: string[]) {
|
export default function prefetchRoutes(pathnames: string[]) {
|
||||||
return pages
|
return routes
|
||||||
.filter(route => {
|
.filter(pathnames
|
||||||
if (!pathnames) return true;
|
? route => pathnames.some(pathname => route.pattern.test(pathname))
|
||||||
return pathnames.some(pathname => route.pattern.test(pathname));
|
: () => true
|
||||||
})
|
)
|
||||||
.reduce((promise: Promise<any>, route) => promise.then(() => {
|
.reduce((promise: Promise<any>, route) => promise.then(() => {
|
||||||
return Promise.all(route.parts.map(part => part && load_component(components[part.i])));
|
return Promise.all(route.parts.map(part => part && load_component(components[part.i])));
|
||||||
}), Promise.resolve());
|
}), Promise.resolve());
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
navigate,
|
navigate,
|
||||||
scroll_history,
|
scroll_history,
|
||||||
scroll_state,
|
scroll_state,
|
||||||
select_route,
|
select_target,
|
||||||
set_target,
|
set_target,
|
||||||
uid,
|
uid,
|
||||||
set_uid,
|
set_uid,
|
||||||
@@ -35,7 +35,7 @@ export default function start(opts: {
|
|||||||
history.replaceState({ id: uid }, '', href);
|
history.replaceState({ id: uid }, '', href);
|
||||||
|
|
||||||
if (!initial_data.error) {
|
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);
|
if (target) return navigate(target, uid, false, hash);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -92,7 +92,7 @@ function handle_click(event: MouseEvent) {
|
|||||||
// Don't handle hash changes
|
// Don't handle hash changes
|
||||||
if (url.pathname === location.pathname && url.search === location.search) return;
|
if (url.pathname === location.pathname && url.search === location.search) return;
|
||||||
|
|
||||||
const target = select_route(url);
|
const target = select_target(url);
|
||||||
if (target) {
|
if (target) {
|
||||||
const noscroll = a.hasAttribute('sapper-noscroll');
|
const noscroll = a.hasAttribute('sapper-noscroll');
|
||||||
navigate(target, null, noscroll, url.hash);
|
navigate(target, null, noscroll, url.hash);
|
||||||
@@ -115,7 +115,7 @@ function handle_popstate(event: PopStateEvent) {
|
|||||||
|
|
||||||
if (event.state) {
|
if (event.state) {
|
||||||
const url = new URL(location.href);
|
const url = new URL(location.href);
|
||||||
const target = select_route(url);
|
const target = select_target(url);
|
||||||
if (target) {
|
if (target) {
|
||||||
navigate(target, event.state.id);
|
navigate(target, event.state.id);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export type ComponentLoader = {
|
|||||||
css: string[]
|
css: string[]
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Page = {
|
export type Route = {
|
||||||
pattern: RegExp;
|
pattern: RegExp;
|
||||||
parts: Array<{
|
parts: Array<{
|
||||||
i: number;
|
i: number;
|
||||||
@@ -35,7 +35,7 @@ export type Manifest = {
|
|||||||
ignore: RegExp[];
|
ignore: RegExp[];
|
||||||
root: ComponentConstructor;
|
root: ComponentConstructor;
|
||||||
error: () => Promise<{ default: ComponentConstructor }>;
|
error: () => Promise<{ default: ComponentConstructor }>;
|
||||||
pages: Page[]
|
pages: Route[]
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ScrollPosition = {
|
export type ScrollPosition = {
|
||||||
@@ -44,11 +44,12 @@ export type ScrollPosition = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type Target = {
|
export type Target = {
|
||||||
url: URL;
|
href: string;
|
||||||
path: string;
|
path: string;
|
||||||
page: Page;
|
route: Route;
|
||||||
match: RegExpExecArray;
|
match: RegExpExecArray;
|
||||||
query: Record<string, string | string[]>;
|
query: Record<string, string | string[]>;
|
||||||
|
params: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Redirect = {
|
export type Redirect = {
|
||||||
@@ -60,7 +61,7 @@ export type Store = {
|
|||||||
get: () => any;
|
get: () => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PageData = {
|
export type Page = {
|
||||||
path: string;
|
path: string;
|
||||||
params: Record<string, string>;
|
params: Record<string, string>;
|
||||||
query: Record<string, string | string[]>;
|
query: Record<string, string | string[]>;
|
||||||
|
|||||||
Reference in New Issue
Block a user