prevent unnecessary promise chains, and hoist handler function

This commit is contained in:
Rich Harris
2018-01-14 00:01:10 -05:00
3 changed files with 258 additions and 219 deletions

View File

@@ -23,9 +23,11 @@ function connect_dev() {
heartbeat: 10 * 1000
}),
async (req, res, next) => {
asset_cache = await watcher.ready;
next();
(req, res, next) => {
watcher.ready.then(cache => {
asset_cache = cache;
next();
});
},
set_req_pathname,
@@ -125,81 +127,88 @@ function get_asset_handler(opts) {
};
}
function get_route_handler(fn) {
return async function handle_route(req, res, next) {
const url = req.pathname;
const resolved = Promise.resolve();
const { client, server } = fn();
function get_route_handler(fn) {
function handle_route(route, req, res, next, { client, server }) {
req.params = route.exec(req.pathname);
const mod = require(server.entry)[route.id];
if (route.type === 'page') {
// preload main.js and current route
// TODO detect other stuff we can preload? images, CSS, fonts?
res.set('Link', `<${client.main_file}>;rel="preload";as="script", <${client.routes[route.id]}>;rel="preload";as="script"`);
const data = { params: req.params, query: req.query };
if (mod.preload) {
const promise = Promise.resolve(mod.preload(req)).then(preloaded => {
Object.assign(data, preloaded);
return mod.render(data);
});
return templates.stream(res, 200, {
main: client.main_file,
html: promise.then(rendered => rendered.html),
head: promise.then(({ head }) => `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`),
styles: promise.then(({ css }) => (css && css.code ? `<style>${css.code}</style>` : ''))
});
} else {
const { html, head, css } = mod.render(data);
const page = templates.render(200, {
main: client.main_file,
html,
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
styles: (css && css.code ? `<style>${css.code}</style>` : '')
});
res.end(page);
}
}
else {
const method = req.method.toLowerCase();
// 'delete' cannot be exported from a module because it is a keyword,
// so check for 'del' instead
const method_export = method === 'delete' ? 'del' : method;
const handler = mod[method_export];
if (handler) {
handler(req, res, next);
} else {
// no matching handler for method — 404
next();
}
}
}
return function find_route(req, res, next) {
const url = req.pathname;
// whatever happens, we're going to serve some HTML
res.set({
'Content-Type': 'text/html'
});
try {
for (const route of route_manager.routes) {
if (route.test(url)) {
req.params = route.exec(url);
const mod = require(server.entry)[route.id];
if (route.type === 'page') {
// preload main.js and current route
// TODO detect other stuff we can preload? images, CSS, fonts?
res.set('Link', `<${client.main_file}>;rel="preload";as="script", <${client.routes[route.id]}>;rel="preload";as="script"`);
const data = { params: req.params, query: req.query };
if (mod.preload) {
const promise = Promise.resolve(mod.preload(req)).then(preloaded => {
Object.assign(data, preloaded);
return mod.render(data);
});
await templates.stream(res, 200, {
main: client.main_file,
html: promise.then(rendered => rendered.html),
head: promise.then(({ head }) => `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`),
styles: promise.then(({ css }) => (css && css.code ? `<style>${css.code}</style>` : ''))
});
} else {
const { html, head, css } = mod.render(data);
const page = templates.render(200, {
main: client.main_file,
html,
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
styles: (css && css.code ? `<style>${css.code}</style>` : '')
});
res.end(page);
}
return;
}
const method = req.method.toLowerCase();
// 'delete' cannot be exported from a module because it is a keyword,
// so check for 'del' instead
const method_export = method === 'delete' ? 'del' : method;
const handler = mod[method_export];
if (handler) {
handler(req, res, next);
return;
}
resolved
.then(() => {
for (const route of route_manager.routes) {
if (route.test(url)) return handle_route(route, req, res, next, fn());
}
}
next();
} catch(err) {
res.status(500);
res.end(templates.render(500, {
title: (err && err.name) || 'Internal server error',
url,
error: escape_html(err && (err.details || err.message || err) || 'Unknown error'),
stack: err && err.stack.split('\n').slice(1).join('\n')
}));
}
// no matching route — 404
next();
})
.catch(err => {
res.status(500);
res.end(templates.render(500, {
title: (err && err.name) || 'Internal server error',
url,
error: escape_html(err && (err.details || err.message || err) || 'Unknown error'),
stack: err && err.stack.split('\n').slice(1).join('\n')
}));
});
};
}

View File

@@ -31,10 +31,14 @@ function create_templates() {
return key in data ? data[key] : '';
});
},
stream: async (res, data) => {
stream: (res, data) => {
let i = 0;
do {
function stream_inner() {
if (i >= template.length) {
return;
}
const start = template.indexOf('%sapper', i);
if (start === -1) {
@@ -53,9 +57,14 @@ function create_templates() {
const match = /sapper\.(\w+)/.exec(tag);
if (!match || !(match[1] in data)) throw new Error(`Bad template`); // TODO ditto
res.write(await data[match[1]]);
i = end + 1;
} while (i < template.length);
return Promise.resolve(data[match[1]]).then(datamatch => {
res.write(datamatch);
i = end + 1;
return stream_inner();
});
}
return Promise.resolve().then(stream_inner);
}
};
})