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

Commit

Permalink
Nested routes
Browse files Browse the repository at this point in the history
Fixes #262
  • Loading branch information
Rich-Harris authored Jul 23, 2018
1 parent b75ae7b commit 58de0f9
Show file tree
Hide file tree
Showing 67 changed files with 1,151 additions and 772 deletions.
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ build: off
environment:
matrix:
# node.js
- nodejs_version: stable
- nodejs_version: 10.5

install:
- ps: Install-Product node $env:nodejs_version
Expand Down
1 change: 1 addition & 0 deletions components/default-layout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<svelte:component this={child.component} {...child.props}/>
2 changes: 1 addition & 1 deletion mocha.opts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
--require source-map-support/register
--recursive
test/unit/**/*.js
test/unit/*/*.js
test/common/test.js
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"runtime",
"webpack",
"sapper",
"components",
"dist"
],
"directories": {
Expand Down Expand Up @@ -67,7 +68,7 @@
"cy:open": "cypress open",
"test": "mocha --opts mocha.opts",
"pretest": "npm run build",
"build": "rollup -c",
"build": "rm -rf dist && rollup -c",
"dev": "rollup -cw",
"prepublishOnly": "npm test",
"update_mime_types": "curl http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types | grep -e \"^[^#]\" > src/middleware/mime-types.md"
Expand Down
3 changes: 2 additions & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export default [
},
plugins: [
typescript({
typescript: require('typescript')
typescript: require('typescript'),
target: "ES2017"
})
]
},
Expand Down
22 changes: 19 additions & 3 deletions src/api/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,31 @@ class Watcher extends EventEmitter {

const dev_port = await ports.find(10000);

const routes = create_routes();
create_main_manifests({ routes, dev_port });
try {
const routes = create_routes();
create_main_manifests({ routes, dev_port });
} catch (err) {
this.emit('fatal', <events.FatalEvent>{
message: err.message
});
return;
}

this.dev_server = new DevServer(dev_port);

this.filewatchers.push(
watch_files(locations.routes(), ['add', 'unlink'], () => {
const routes = create_routes();
create_main_manifests({ routes, dev_port });

try {
const routes = create_routes();
create_main_manifests({ routes, dev_port });
} catch (err) {
this.emit('error', <events.ErrorEvent>{
message: err.message
});
}
}),

watch_files(`${locations.app()}/template.html`, ['change'], () => {
Expand Down Expand Up @@ -272,7 +288,7 @@ class Watcher extends EventEmitter {
if (this.closed) return;
this.closed = true;

this.dev_server.close();
if (this.dev_server) this.dev_server.close();

if (this.proc) this.proc.kill();
this.filewatchers.forEach(watcher => {
Expand Down
13 changes: 6 additions & 7 deletions src/api/find_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import * as glob from 'glob';
import { locations } from '../config';
import { create_routes } from '../core';

export function find_page(pathname: string, files: string[] = glob.sync('**/*.*', { cwd: locations.routes(), dot: true, nodir: true })) {
const routes = create_routes({ files });
export function find_page(pathname: string, cwd = locations.routes()) {
const { pages } = create_routes(cwd);

for (let i = 0; i < routes.length; i += 1) {
const route = routes[i];
for (let i = 0; i < pages.length; i += 1) {
const page = pages[i];

if (route.pattern.test(pathname)) {
const page = route.handlers.find(handler => handler.type === 'page');
if (page) return page.file;
if (page.pattern.test(pathname)) {
return page.parts[page.parts.length - 1].component.file;
}
}
}
156 changes: 105 additions & 51 deletions src/core/create_manifests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@ import * as path from 'path';
import * as glob from 'glob';
import { posixify, write_if_changed } from './utils';
import { dev, locations } from '../config';
import { Route } from '../interfaces';
import { Page, PageComponent, ServerRoute } from '../interfaces';

export function create_main_manifests({ routes, dev_port }: {
routes: Route[];
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`,
`<svelte:component this={child.component} {...child.props}/>`
);
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: Route[];
routes: { components: PageComponent[], pages: Page[], server_routes: ServerRoute[] };
client_files: string[];
}) {
const assets = glob.sync('**', { cwd: 'assets', nodir: true });
Expand All @@ -32,42 +36,67 @@ export function create_serviceworker_manifest({ routes, client_files }: {
export const shell = [\n\t${client_files.map((x: string) => `"${x}"`).join(',\n\t')}\n];
export const routes = [\n\t${routes.pages.filter(r => r.id !== '_error').map((r: Route) => `{ pattern: ${r.pattern} }`).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 generate_client(routes: Route[], path_to_routes: string, dev_port?: number) {
const page_ids = new Set(routes.pages.map(page => page.id));
const server_routes_to_ignore = routes.server_routes.filter(route => !page_ids.has(route.id));
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 pages = routes.pages.filter(page => page.id !== '_error');
const error_route = routes.pages.find(page => page.id === '_error');
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!
export const routes = {
import root from '${posixify(`${path_to_routes}/${routes.root.file}`)}';
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: [
${pages.map(page => {
const file = posixify(`${path_to_routes}/${page.file}`);
if (page.id === '_error') {
return `{ error: true, load: () => import(/* webpackChunkName: "${page.id}" */ '${file}') }`;
}
${routes.pages.map(page => `{
// ${page.parts[page.parts.length - 1].component.file}
pattern: ${page.pattern},
parts: [
${page.parts.map(part => {
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')}
],
const params = page.params.length === 0
? '{}'
: `{ ${page.params.map((part, i) => `${part}: match[${i + 1}]`).join(', ')} }`;
root,
return `{ pattern: ${page.pattern}, params: ${page.params.length > 0 ? `match` : `()`} => (${params}), load: () => import(/* webpackChunkName: "${page.id}" */ '${file}') }`;
}).join(',\n\t\t\t\t')}
],
error
};
error: () => import(/* webpackChunkName: '_error' */ '${posixify(`${path_to_routes}/${error_route.file}`)}')
};`.replace(/^\t\t/gm, '').trim();
// this is included for legacy reasons
export const routes = {};`.replace(/^\t\t/gm, '').trim();

if (dev()) {
const sapper_dev_client = posixify(
Expand All @@ -86,47 +115,72 @@ function generate_client(routes: Route[], path_to_routes: string, dev_port?: num
return code;
}

function generate_server(routes: Route[], path_to_routes: string) {
const error_route = routes.pages.find(page => page.id === '_error');

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_${route.id} from '${posixify(`${path_to_routes}/${route.file}`)}';`),
routes.pages.map(page =>
`import page_${page.id} from '${posixify(`${path_to_routes}/${page.file}`)}';`),
`import error from '${posixify(`${path_to_routes}/${error_route.file}`)}';`
`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 '${posixify(`${path_to_routes}/${routes.root.file}`)}';`,
`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 routes = {
export const manifest = {
server_routes: [
${routes.server_routes.map(route => {
const params = route.params.length === 0
? '{}'
: `{ ${route.params.map((part, i) => `${part}: match[${i + 1}]`).join(', ')} }`;
return `{ id: '${route.id}', pattern: ${route.pattern}, params: ${route.params.length > 0 ? `match` : `()`} => (${params}), handlers: route_${route.id} }`;
}).join(',\n\t\t\t\t')}
${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 => {
const params = page.params.length === 0
? '{}'
: `{ ${page.params.map((part, i) => `${part}: match[${i + 1}]`).join(', ')} }`;
return `{ id: '${page.id}', pattern: ${page.pattern}, params: ${page.params.length > 0 ? `match` : `()`} => (${params}), handler: page_${page.id} }`;
}).join(',\n\t\t\t\t')}
${routes.pages.map(page => `{
// ${page.parts[page.parts.length - 1].component.file}
pattern: ${page.pattern},
parts: [
${page.parts.map(part => {
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')}
],
error: {
error: true,
handler: error
}
};`.replace(/^\t\t/gm, '').trim();
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}`);
}
Loading

0 comments on commit 58de0f9

Please sign in to comment.