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

[WIP] Nested routes #308

Merged
merged 46 commits into from
Jul 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
80ece8b
delete App.html
Rich-Harris Jul 5, 2018
1af0f13
Merge branch 'master' into gh-262
Rich-Harris Jul 15, 2018
5b024f2
small tidy up
Rich-Harris Jul 16, 2018
bf8e567
change how routes are created
Rich-Harris Jul 16, 2018
6c5630f
add more tests
Rich-Harris Jul 16, 2018
c90d8ee
more tests
Rich-Harris Jul 16, 2018
624f173
tidy up
Rich-Harris Jul 16, 2018
9694307
update manifest generation
Rich-Harris Jul 16, 2018
9dc5ba5
server-side rendering
Rich-Harris Jul 16, 2018
f221281
client-side rendering
Rich-Harris Jul 16, 2018
8299c68
inject fallback index.html where necessary
Rich-Harris Jul 16, 2018
34cf953
use serialized preloaded data
Rich-Harris Jul 16, 2018
af0bd15
handle errors
Rich-Harris Jul 16, 2018
61e3b6c
handle errors in client
Rich-Harris Jul 16, 2018
7d3e191
identify leaf index.html components
Rich-Harris Jul 16, 2018
b0b8b78
set preloading true when appropriate
Rich-Harris Jul 16, 2018
1f66e4c
all tests passing
Rich-Harris Jul 16, 2018
dd190af
ugh windows
Rich-Harris Jul 16, 2018
5428a7e
try locking node version to get tests running on windows
Rich-Harris Jul 16, 2018
6c68b31
maybe fix some windows errors
Rich-Harris Jul 16, 2018
d2ee6ff
argh
Rich-Harris Jul 16, 2018
e751cbe
once more, with feeling
Rich-Harris Jul 16, 2018
84aaf3b
please let this work
Rich-Harris Jul 17, 2018
f126c6a
only replace components for changed segments
Rich-Harris Jul 17, 2018
9e1207c
fix error pages
Rich-Harris Jul 17, 2018
c867f05
fix fallback index file
Rich-Harris Jul 17, 2018
3d39ef9
add segment, params and query to server-rendered pages
Rich-Harris Jul 17, 2018
20015d6
oops
Rich-Harris Jul 17, 2018
99b4cd4
minor tweak
Rich-Harris Jul 17, 2018
aa92342
oops
Rich-Harris Jul 17, 2018
d927597
doh
Rich-Harris Jul 20, 2018
448e91a
update find_page API
Rich-Harris Jul 20, 2018
5d7a715
emit error event if manifest creation fails
Rich-Harris Jul 21, 2018
3dc582d
render error pages properly
Rich-Harris Jul 21, 2018
c50a252
tweak build
Rich-Harris Jul 21, 2018
d9c2381
failing test for root preload
Rich-Harris Jul 22, 2018
3c68f8a
dont pass req object to preload - no longer makes sense
Rich-Harris Jul 22, 2018
24fc6a1
reorganise things a bit
Rich-Harris Jul 22, 2018
fdf3be8
preload routes/index.html on server
Rich-Harris Jul 22, 2018
38f6f05
allow preload stages to fail independently
Rich-Harris Jul 22, 2018
50cbc84
preload routes/index.html on client
Rich-Harris Jul 22, 2018
994f4ca
switch to index.html and optional _layout.html
Rich-Harris Jul 22, 2018
b701216
compile down to ES5 for benefit of Node 6
Rich-Harris Jul 22, 2018
559ff3e
manifest, not routes
Rich-Harris Jul 22, 2018
0a155e1
write default layout to manifest directory
Rich-Harris Jul 23, 2018
6128f65
fix tests
Rich-Harris Jul 23, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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