import * as fs from 'fs'; import * as path from 'path'; import * as glob from 'glob'; import { posixify, write_if_changed } from './utils'; import { dev, locations } from '../config'; import { Page, PageComponent, ServerRoute } from '../interfaces'; export function create_main_manifests({ routes, dev_port }: { routes: { components: PageComponent[], pages: Page[], server_routes: ServerRoute[] }; dev_port?: number; }) { const path_to_routes = path.relative(`${locations.app()}/manifest`, locations.routes()); const client_manifest = generate_client(routes, path_to_routes, dev_port); const server_manifest = generate_server(routes, path_to_routes); write_if_changed( `${locations.app()}/manifest/default-layout.html`, `` ); write_if_changed(`${locations.app()}/manifest/client.js`, client_manifest); write_if_changed(`${locations.app()}/manifest/server.js`, server_manifest); } export function create_serviceworker_manifest({ routes, client_files }: { routes: { components: PageComponent[], pages: Page[], server_routes: ServerRoute[] }; client_files: string[]; }) { const assets = glob.sync('**', { cwd: 'assets', nodir: true }); let code = ` // This file is generated by Sapper — do not edit it! export const timestamp = ${Date.now()}; export const assets = [\n\t${assets.map((x: string) => `"${x}"`).join(',\n\t')}\n]; export const shell = [\n\t${client_files.map((x: string) => `"${x}"`).join(',\n\t')}\n]; export const routes = [\n\t${routes.pages.map((r: Page) => `{ pattern: ${r.pattern} }`).join(',\n\t')}\n]; `.replace(/^\t\t/gm, '').trim(); write_if_changed(`${locations.app()}/manifest/service-worker.js`, code); } function right_pad(str: string, len: number) { while (str.length < len) str += ' '; return str; } function generate_client( routes: { root: PageComponent, components: PageComponent[], pages: Page[], server_routes: ServerRoute[] }, path_to_routes: string, dev_port?: number ) { const page_ids = new Set(routes.pages.map(page => page.pattern.toString())); const server_routes_to_ignore = routes.server_routes.filter(route => !page_ids.has(route.pattern.toString())); const len = Math.max(...routes.components.map(c => c.name.length)); let code = ` // This file is generated by Sapper — do not edit it! import root from '${get_file(path_to_routes, routes.root)}'; import error from '${posixify(`${path_to_routes}/_error.html`)}'; ${routes.components.map(component => `const ${component.name} = () => import(/* webpackChunkName: "${component.name}" */ '${get_file(path_to_routes, component)}');`) .join('\n')} export const manifest = { ignore: [${server_routes_to_ignore.map(route => route.pattern).join(', ')}], pages: [ ${routes.pages.map(page => `{ // ${page.parts[page.parts.length - 1].component.file} pattern: ${page.pattern}, parts: [ ${page.parts.map(part => { if (part === null) return 'null'; if (part.params.length > 0) { const props = part.params.map((param, i) => `${param}: match[${i + 1}]`); return `{ component: ${part.component.name}, params: match => ({ ${props.join(', ')} }) }`; } return `{ component: ${part.component.name} }`; }).join(',\n\t\t\t\t\t\t')} ] }`).join(',\n\n\t\t\t\t')} ], root, error }; // this is included for legacy reasons export const routes = {};`.replace(/^\t\t/gm, '').trim(); if (dev()) { const sapper_dev_client = posixify( path.resolve(__dirname, '../sapper-dev-client.js') ); code += ` if (module.hot) { import('${sapper_dev_client}').then(client => { client.connect(${dev_port}); }); }`.replace(/^\t{3}/gm, ''); } return code; } function generate_server( routes: { root: PageComponent, components: PageComponent[], pages: Page[], server_routes: ServerRoute[] }, path_to_routes: string ) { const imports = [].concat( routes.server_routes.map(route => `import * as ${route.name} from '${posixify(`${path_to_routes}/${route.file}`)}';`), routes.components.map(component => `import ${component.name} from '${get_file(path_to_routes, component)}';`), `import root from '${get_file(path_to_routes, routes.root)}';`, `import error from '${posixify(`${path_to_routes}/_error.html`)}';` ); let code = ` // This file is generated by Sapper — do not edit it! ${imports.join('\n')} export const manifest = { server_routes: [ ${routes.server_routes.map(route => `{ // ${route.file} pattern: ${route.pattern}, handlers: ${route.name}, params: ${route.params.length > 0 ? `match => ({ ${route.params.map((param, i) => `${param}: match[${i + 1}]`).join(', ')} })` : `() => ({})`} }`).join(',\n\n\t\t\t\t')} ], pages: [ ${routes.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 props = [ `name: "${part.component.name}"`, `component: ${part.component.name}` ]; if (part.params.length > 0) { const params = part.params.map((param, i) => `${param}: match[${i + 1}]`); props.push(`params: match => ({ ${params.join(', ')} })`); } return `{ ${props.join(', ')} }`; }).join(',\n\t\t\t\t\t\t')} ] }`).join(',\n\n\t\t\t\t')} ], root, error }; // this is included for legacy reasons export const routes = {};`.replace(/^\t\t/gm, '').trim(); return code; } function get_file(path_to_routes: string, component: PageComponent) { if (component.default) { return `./default-layout.html`; } return posixify(`${path_to_routes}/${component.file}`); }