diff --git a/rollup.config.js b/rollup.config.js index 6972fad..90e1f45 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -35,7 +35,7 @@ function template(kind, external) { } export default [ - template('client', []), + template('client', ['__ROOT__', '__ERROR__']), template('server', builtinModules), { diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index d3e9ac8..f3d9441 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -75,62 +75,57 @@ function generate_client( const component_indexes: Record = {}; - let code = ` - import root from ${stringify(get_file(path_to_routes, manifest_data.root))}; - import error from ${stringify(posixify(`${path_to_routes}/_error.html`))}; + const components = `[ + ${manifest_data.components.map((component, i) => { + const annotation = bundler === 'webpack' + ? `/* webpackChunkName: "${component.name}" */ ` + : ''; - const d = decodeURIComponent; + const source = get_file(path_to_routes, component); - const components = [ - ${manifest_data.components.map((component, i) => { - const annotation = bundler === 'webpack' - ? `/* webpackChunkName: "${component.name}" */ ` - : ''; + component_indexes[component.name] = i; - const source = get_file(path_to_routes, component); + return `{ + js: () => import(${annotation}${stringify(source)}), + css: "__SAPPER_CSS_PLACEHOLDER:${stringify(component.file, false)}__" + }`; + }).join(',\n\t\t')} + ]`.replace(/^\t/gm, '').trim(); - component_indexes[component.name] = i; + let needs_decode = false; - return `{ - js: () => import(${annotation}${stringify(source)}), - css: "__SAPPER_CSS_PLACEHOLDER:${stringify(component.file, false)}__" - }`; - }).join(',\n\t\t\t')} - ]; + let pages = `[ + ${manifest_data.pages.map(page => `{ + // ${page.parts[page.parts.length - 1].component.file} + pattern: ${page.pattern}, + parts: [ + ${page.parts.map(part => { + if (part === null) return 'null'; - const manifest = { - ignore: [${server_routes_to_ignore.map(route => route.pattern).join(', ')}], + if (part.params.length > 0) { + needs_decode = true; + const props = part.params.map((param, i) => `${param}: d(match[${i + 1}])`); + return `{ i: ${component_indexes[part.component.name]}, params: match => ({ ${props.join(', ')} }) }`; + } - pages: [ - ${manifest_data.pages.map(page => `{ - // ${page.parts[page.parts.length - 1].component.file} - pattern: ${page.pattern}, - parts: [ - ${page.parts.map(part => { - if (part === null) return 'null'; + return `{ i: ${component_indexes[part.component.name]} }`; + }).join(',\n\t\t\t\t')} + ] + }`).join(',\n\n\t\t')} + ]`.replace(/^\t/gm, '').trim(); - if (part.params.length > 0) { - const props = part.params.map((param, i) => `${param}: d(match[${i + 1}])`); - return `{ component: components[${component_indexes[part.component.name]}], params: match => ({ ${props.join(', ')} }) }`; - } + if (needs_decode) { + pages = `(d => ${pages})(decodeURIComponent)` + } - return `{ component: components[${component_indexes[part.component.name]}] }`; - }).join(',\n\t\t\t\t\t\t')} - ] - }`).join(',\n\n\t\t\t\t')} - ], - - root, - - error - };`.replace(/^\t\t/gm, '').trim(); + let footer = ''; if (dev()) { const sapper_dev_client = posixify( path.resolve(__dirname, '../sapper-dev-client.js') ); - code += ` + footer = ` import(${stringify(sapper_dev_client)}).then(client => { client.connect(${dev_port}); @@ -138,7 +133,12 @@ function generate_client( } return `// This file is generated by Sapper — do not edit it!\n` + template - .replace(/const manifest = __MANIFEST__;/, code); + .replace('__ROOT__', stringify(get_file(path_to_routes, manifest_data.root), false)) + .replace('__ERROR__', stringify(posixify(`${path_to_routes}/_error.html`), false)) + .replace('__IGNORE__', `[${server_routes_to_ignore.map(route => route.pattern).join(', ')}]`) + .replace('__COMPONENTS__', components) + .replace('__PAGES__', pages) + + footer; } function generate_server( diff --git a/templates/src/client/app.ts b/templates/src/client/app.ts index db1e644..9456a63 100644 --- a/templates/src/client/app.ts +++ b/templates/src/client/app.ts @@ -1,7 +1,20 @@ -import { Manifest, Target, ScrollPosition, Component, Redirect, ComponentLoader, ComponentConstructor, RootProps } from './types'; +import RootComponent from '__ROOT__'; +import ErrorComponent from '__ERROR__'; +import { + Target, + ScrollPosition, + Component, + Redirect, + ComponentLoader, + ComponentConstructor, + RootProps, + Page +} from './types'; import goto from './goto'; -export const manifest: Manifest = __MANIFEST__; +const ignore = __IGNORE__; +export const components: ComponentLoader[] = __COMPONENTS__; +export const pages: Page[] = __PAGES__; let ready = false; let root_component: Component; @@ -61,16 +74,16 @@ export { _history as history }; export const scroll_history: Record = {}; export function select_route(url: URL): Target { - if (url.origin !== window.location.origin) return null; + 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 - if (manifest.ignore.some(pattern => pattern.test(path))) return; + if (ignore.some(pattern => pattern.test(path))) return; - for (let i = 0; i < manifest.pages.length; i += 1) { - const page = manifest.pages[i]; + for (let i = 0; i < pages.length; i += 1) { + const page = pages[i]; const match = page.pattern.exec(path); if (match) { @@ -88,8 +101,8 @@ export function select_route(url: URL): Target { export function scroll_state() { return { - x: window.scrollX, - y: window.scrollY + x: scrollX, + y: scrollY }; } @@ -159,7 +172,7 @@ function render(data: any, nullable_depth: number, scroll: ScrollPosition, token Object.assign(data, root_data); - root_component = new manifest.root({ + root_component = new RootComponent({ target, data, store, @@ -168,7 +181,7 @@ function render(data: any, nullable_depth: number, scroll: ScrollPosition, token } if (scroll) { - window.scrollTo(scroll.x, scroll.y); + scrollTo(scroll.x, scroll.y); } Object.assign(root_props, data); @@ -195,7 +208,7 @@ export function prepare_page(target: Target): Promise<{ const preload_context = { store, - fetch: (url: string, opts?: any) => window.fetch(url, opts), + 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`); @@ -208,8 +221,8 @@ export function prepare_page(target: Target): Promise<{ }; if (!root_preload) { - root_preload = manifest.root.preload - ? initial_data.preloaded[0] || manifest.root.preload.call(preload_context, { + root_preload = RootComponent.preload + ? initial_data.preloaded[0] || RootComponent.preload.call(preload_context, { path, query, params: {} @@ -221,7 +234,7 @@ export function prepare_page(target: Target): Promise<{ if (i < changed_from) return null; if (!part) return null; - return load_component(part.component).then(Component => { + return load_component(components[part.i]).then(Component => { const req = { path, query, @@ -276,7 +289,7 @@ export function prepare_page(target: Target): Promise<{ data: Object.assign({}, props, { preloading: false, child: { - component: manifest.error, + component: ErrorComponent, props } }) diff --git a/templates/src/client/goto/index.ts b/templates/src/client/goto/index.ts index 547c6e2..0f58786 100644 --- a/templates/src/client/goto/index.ts +++ b/templates/src/client/goto/index.ts @@ -8,7 +8,7 @@ export default function goto(href: string, opts = { replaceState: false }) { promise = navigate(target, null).then(() => {}); if (history) history[opts.replaceState ? 'replaceState' : 'pushState']({ id: cid }, '', href); } else { - window.location.href = href; + location.href = href; promise = new Promise(f => {}); // never resolves } diff --git a/templates/src/client/prefetchRoutes/index.ts b/templates/src/client/prefetchRoutes/index.ts index 8ed7a48..b3c2f3c 100644 --- a/templates/src/client/prefetchRoutes/index.ts +++ b/templates/src/client/prefetchRoutes/index.ts @@ -1,14 +1,12 @@ -import { manifest, load_component } from "../app"; +import { components, pages, load_component } from "../app"; export default function prefetchRoutes(pathnames: string[]) { - if (!manifest) throw new Error(`You must call init() first`); - - return manifest.pages + return pages .filter(route => { if (!pathnames) return true; return pathnames.some(pathname => route.pattern.test(pathname)); }) .reduce((promise: Promise, route) => promise.then(() => { - return Promise.all(route.parts.map(part => part && load_component(part.component))); + return Promise.all(route.parts.map(part => part && load_component(components[part.i]))); }), Promise.resolve()); } \ No newline at end of file diff --git a/templates/src/client/start/index.ts b/templates/src/client/start/index.ts index 1a26ca9..240fdd8 100644 --- a/templates/src/client/start/index.ts +++ b/templates/src/client/start/index.ts @@ -26,15 +26,15 @@ export default function start(opts: { set_target(opts.target); if (opts.store) set_store(opts.store); - window.addEventListener('click', handle_click); - window.addEventListener('popstate', handle_popstate); + addEventListener('click', handle_click); + addEventListener('popstate', handle_popstate); // prefetch - window.addEventListener('touchstart', trigger_prefetch); - window.addEventListener('mousemove', handle_mousemove); + addEventListener('touchstart', trigger_prefetch); + addEventListener('mousemove', handle_mousemove); return Promise.resolve().then(() => { - const { hash, href } = window.location; + const { hash, href } = location; const deep_linked = hash && document.getElementById(hash.slice(1)); scroll_history[uid] = deep_linked ? @@ -44,7 +44,7 @@ export default function start(opts: { history.replaceState({ id: uid }, '', href); if (!initial_data.error) { - const target = select_route(new URL(window.location.href)); + const target = select_route(new URL(location.href)); if (target) return navigate(target, uid); } }); @@ -83,7 +83,7 @@ function handle_click(event: MouseEvent) { const svg = typeof a.href === 'object' && a.href.constructor.name === 'SVGAnimatedString'; const href = String(svg ? (a).href.baseVal : a.href); - if (href === window.location.href) { + if (href === location.href) { event.preventDefault(); return; } @@ -99,7 +99,7 @@ function handle_click(event: MouseEvent) { const url = new URL(href); // Don't handle hash changes - if (url.pathname === window.location.pathname && url.search === window.location.search) return; + if (url.pathname === location.pathname && url.search === location.search) return; const target = select_route(url); if (target) { @@ -122,17 +122,17 @@ function handle_popstate(event: PopStateEvent) { scroll_history[cid] = scroll_state(); if (event.state) { - const url = new URL(window.location.href); + const url = new URL(location.href); const target = select_route(url); if (target) { navigate(target, event.state.id); } else { - window.location.href = window.location.href; + location.href = location.href; } } else { // hashchange set_uid(uid + 1); set_cid(uid); - history.replaceState({ id: cid }, '', window.location.href); + history.replaceState({ id: cid }, '', location.href); } } \ No newline at end of file diff --git a/templates/src/client/types.ts b/templates/src/client/types.ts index 647a94a..08e7a04 100644 --- a/templates/src/client/types.ts +++ b/templates/src/client/types.ts @@ -33,7 +33,7 @@ export type ComponentLoader = { export type Page = { pattern: RegExp; parts: Array<{ - component: ComponentLoader; + i: number; params?: (match: RegExpExecArray) => Record; }>; };