mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-23 07:31:25 +00:00
prevent unnecessary promise chains, and hoist handler function
This commit is contained in:
53
lib/index.js
53
lib/index.js
@@ -23,9 +23,11 @@ function connect_dev() {
|
|||||||
heartbeat: 10 * 1000
|
heartbeat: 10 * 1000
|
||||||
}),
|
}),
|
||||||
|
|
||||||
async (req, res, next) => {
|
(req, res, next) => {
|
||||||
asset_cache = await watcher.ready;
|
watcher.ready.then(cache => {
|
||||||
|
asset_cache = cache;
|
||||||
next();
|
next();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
set_req_pathname,
|
set_req_pathname,
|
||||||
@@ -125,21 +127,11 @@ function get_asset_handler(opts) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolved = Promise.resolve();
|
||||||
|
|
||||||
function get_route_handler(fn) {
|
function get_route_handler(fn) {
|
||||||
return async function handle_route(req, res, next) {
|
function handle_route(route, req, res, next, { client, server }) {
|
||||||
const url = req.pathname;
|
req.params = route.exec(req.pathname);
|
||||||
|
|
||||||
const { client, server } = fn();
|
|
||||||
|
|
||||||
// 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];
|
const mod = require(server.entry)[route.id];
|
||||||
|
|
||||||
@@ -156,7 +148,7 @@ function get_route_handler(fn) {
|
|||||||
return mod.render(data);
|
return mod.render(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
await templates.stream(res, 200, {
|
return templates.stream(res, 200, {
|
||||||
main: client.main_file,
|
main: client.main_file,
|
||||||
html: promise.then(rendered => rendered.html),
|
html: promise.then(rendered => rendered.html),
|
||||||
head: promise.then(({ head }) => `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`),
|
head: promise.then(({ head }) => `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`),
|
||||||
@@ -174,10 +166,9 @@ function get_route_handler(fn) {
|
|||||||
|
|
||||||
res.end(page);
|
res.end(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
const method = req.method.toLowerCase();
|
const method = req.method.toLowerCase();
|
||||||
// 'delete' cannot be exported from a module because it is a keyword,
|
// 'delete' cannot be exported from a module because it is a keyword,
|
||||||
// so check for 'del' instead
|
// so check for 'del' instead
|
||||||
@@ -185,13 +176,31 @@ function get_route_handler(fn) {
|
|||||||
const handler = mod[method_export];
|
const handler = mod[method_export];
|
||||||
if (handler) {
|
if (handler) {
|
||||||
handler(req, res, next);
|
handler(req, res, next);
|
||||||
return;
|
} 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'
|
||||||
|
});
|
||||||
|
|
||||||
|
resolved
|
||||||
|
.then(() => {
|
||||||
|
for (const route of route_manager.routes) {
|
||||||
|
if (route.test(url)) return handle_route(route, req, res, next, fn());
|
||||||
|
}
|
||||||
|
|
||||||
|
// no matching route — 404
|
||||||
next();
|
next();
|
||||||
} catch(err) {
|
})
|
||||||
|
.catch(err => {
|
||||||
res.status(500);
|
res.status(500);
|
||||||
res.end(templates.render(500, {
|
res.end(templates.render(500, {
|
||||||
title: (err && err.name) || 'Internal server error',
|
title: (err && err.name) || 'Internal server error',
|
||||||
@@ -199,7 +208,7 @@ function get_route_handler(fn) {
|
|||||||
error: escape_html(err && (err.details || err.message || err) || 'Unknown error'),
|
error: escape_html(err && (err.details || err.message || err) || 'Unknown error'),
|
||||||
stack: err && err.stack.split('\n').slice(1).join('\n')
|
stack: err && err.stack.split('\n').slice(1).join('\n')
|
||||||
}));
|
}));
|
||||||
}
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,14 @@ function create_templates() {
|
|||||||
return key in data ? data[key] : '';
|
return key in data ? data[key] : '';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
stream: async (res, data) => {
|
stream: (res, data) => {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
do {
|
function stream_inner() {
|
||||||
|
if (i >= template.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const start = template.indexOf('%sapper', i);
|
const start = template.indexOf('%sapper', i);
|
||||||
|
|
||||||
if (start === -1) {
|
if (start === -1) {
|
||||||
@@ -53,9 +57,14 @@ function create_templates() {
|
|||||||
const match = /sapper\.(\w+)/.exec(tag);
|
const match = /sapper\.(\w+)/.exec(tag);
|
||||||
if (!match || !(match[1] in data)) throw new Error(`Bad template`); // TODO ditto
|
if (!match || !(match[1] in data)) throw new Error(`Bad template`); // TODO ditto
|
||||||
|
|
||||||
res.write(await data[match[1]]);
|
return Promise.resolve(data[match[1]]).then(datamatch => {
|
||||||
|
res.write(datamatch);
|
||||||
i = end + 1;
|
i = end + 1;
|
||||||
} while (i < template.length);
|
return stream_inner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve().then(stream_inner);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -62,21 +62,27 @@ function run(env) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
before(async () => {
|
before(() => {
|
||||||
process.chdir(path.resolve(__dirname, '../app'));
|
process.chdir(path.resolve(__dirname, '../app'));
|
||||||
|
|
||||||
process.env.NODE_ENV = env;
|
process.env.NODE_ENV = env;
|
||||||
|
|
||||||
|
let exec_promise = Promise.resolve();
|
||||||
|
let sapper;
|
||||||
|
|
||||||
if (env === 'production') {
|
if (env === 'production') {
|
||||||
const cli = path.resolve(__dirname, '../../cli/index.js');
|
const cli = path.resolve(__dirname, '../../cli/index.js');
|
||||||
await exec(`${cli} build`);
|
exec_promise = exec(`${cli} build`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return exec_promise.then(() => {
|
||||||
const resolved = require.resolve('../..');
|
const resolved = require.resolve('../..');
|
||||||
delete require.cache[resolved];
|
delete require.cache[resolved];
|
||||||
const sapper = require(resolved);
|
sapper = require(resolved);
|
||||||
|
|
||||||
PORT = await getPort();
|
return getPort();
|
||||||
|
}).then(port => {
|
||||||
|
PORT = port;
|
||||||
base = `http://localhost:${PORT}`;
|
base = `http://localhost:${PORT}`;
|
||||||
|
|
||||||
global.fetch = (url, opts) => {
|
global.fetch = (url, opts) => {
|
||||||
@@ -85,11 +91,12 @@ function run(env) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let captured;
|
let captured;
|
||||||
capture = async fn => {
|
capture = fn => {
|
||||||
const result = captured = [];
|
const result = captured = [];
|
||||||
await fn();
|
return fn().then(() => {
|
||||||
captured = null;
|
captured = null;
|
||||||
return result;
|
return result;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -111,6 +118,7 @@ function run(env) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
after(() => {
|
after(() => {
|
||||||
server.close();
|
server.close();
|
||||||
@@ -137,158 +145,171 @@ function run(env) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(() => {
|
||||||
await nightmare.end();
|
return nightmare.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('serves /', async () => {
|
it('serves /', () => {
|
||||||
const title = await nightmare
|
return nightmare
|
||||||
.goto(base)
|
.goto(base)
|
||||||
.evaluate(() => document.querySelector('h1').textContent);
|
.evaluate(() => document.querySelector('h1').textContent)
|
||||||
|
.then(title => {
|
||||||
assert.equal(title, 'Great success!');
|
assert.equal(title, 'Great success!');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('serves static route', async () => {
|
it('serves static route', () => {
|
||||||
const title = await nightmare
|
return nightmare
|
||||||
.goto(`${base}/about`)
|
.goto(`${base}/about`)
|
||||||
.evaluate(() => document.querySelector('h1').textContent);
|
.evaluate(() => document.querySelector('h1').textContent)
|
||||||
|
.then(title => {
|
||||||
assert.equal(title, 'About this site');
|
assert.equal(title, 'About this site');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('serves dynamic route', async () => {
|
it('serves dynamic route', () => {
|
||||||
const title = await nightmare
|
return nightmare
|
||||||
.goto(`${base}/blog/what-is-sapper`)
|
.goto(`${base}/blog/what-is-sapper`)
|
||||||
.evaluate(() => document.querySelector('h1').textContent);
|
.evaluate(() => document.querySelector('h1').textContent)
|
||||||
|
.then(title => {
|
||||||
assert.equal(title, 'What is Sapper?');
|
assert.equal(title, 'What is Sapper?');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('navigates to a new page without reloading', async () => {
|
|
||||||
await nightmare.goto(base).wait(() => window.READY).wait(200);
|
|
||||||
|
|
||||||
const requests = await capture(async () => {
|
|
||||||
await nightmare.click('a[href="/about"]');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(
|
it('navigates to a new page without reloading', () => {
|
||||||
await nightmare.path(),
|
let requests;
|
||||||
'/about'
|
return nightmare
|
||||||
);
|
.goto(base).wait(() => window.READY).wait(200)
|
||||||
|
.then(() => {
|
||||||
|
return capture(() => {
|
||||||
|
return nightmare.click('a[href="/about"]');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(reqs => {
|
||||||
|
requests = reqs;
|
||||||
|
|
||||||
assert.equal(
|
return nightmare.path();
|
||||||
await nightmare.evaluate(() => document.title),
|
})
|
||||||
'About'
|
.then(path => {
|
||||||
);
|
assert.equal(path, '/about');
|
||||||
|
|
||||||
|
return nightmare.evaluate(() => document.title);
|
||||||
|
})
|
||||||
|
.then(title => {
|
||||||
|
assert.equal(title, 'About');
|
||||||
|
|
||||||
assert.deepEqual(requests.map(r => r.url), []);
|
assert.deepEqual(requests.map(r => r.url), []);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('navigates programmatically', async () => {
|
it('navigates programmatically', () => {
|
||||||
await nightmare
|
return nightmare
|
||||||
.goto(`${base}/about`)
|
.goto(`${base}/about`)
|
||||||
.wait(() => window.READY)
|
.wait(() => window.READY)
|
||||||
.click('.goto')
|
.click('.goto')
|
||||||
.wait(() => window.location.pathname === '/blog/what-is-sapper')
|
.wait(() => window.location.pathname === '/blog/what-is-sapper')
|
||||||
.wait(100);
|
.wait(100)
|
||||||
|
.then(() => nightmare.evaluate(() => document.title))
|
||||||
assert.equal(
|
.then(title => {
|
||||||
await nightmare.evaluate(() => document.title),
|
assert.equal(title, 'What is Sapper?');
|
||||||
'What is Sapper?'
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prefetches programmatically', async () => {
|
it('prefetches programmatically', () => {
|
||||||
await nightmare
|
return nightmare
|
||||||
.goto(`${base}/about`)
|
.goto(`${base}/about`)
|
||||||
.wait(() => window.READY);
|
.wait(() => window.READY)
|
||||||
|
.then(() => {
|
||||||
const requests = await capture(async () => {
|
return capture(() => {
|
||||||
return await nightmare
|
return nightmare
|
||||||
.click('.prefetch')
|
.click('.prefetch')
|
||||||
.wait(100);
|
.wait(100);
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.then(requests => {
|
||||||
assert.ok(!!requests.find(r => r.url === '/api/blog/why-the-name'));
|
assert.ok(!!requests.find(r => r.url === '/api/blog/why-the-name'));
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('scrolls to active deeplink', async () => {
|
it('scrolls to active deeplink', () => {
|
||||||
const scrollY = await nightmare
|
return nightmare
|
||||||
.goto(`${base}/blog/a-very-long-post#four`)
|
.goto(`${base}/blog/a-very-long-post#four`)
|
||||||
.wait(() => window.READY)
|
.wait(() => window.READY)
|
||||||
.wait(100)
|
.wait(100)
|
||||||
.evaluate(() => window.scrollY);
|
.evaluate(() => window.scrollY)
|
||||||
|
.then(scrollY => {
|
||||||
assert.ok(scrollY > 0, scrollY);
|
assert.ok(scrollY > 0, scrollY);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('reuses prefetch promise', async () => {
|
it('reuses prefetch promise', () => {
|
||||||
await nightmare
|
return nightmare
|
||||||
.goto(`${base}/blog`)
|
.goto(`${base}/blog`)
|
||||||
.wait(() => window.READY)
|
.wait(() => window.READY)
|
||||||
.wait(200);
|
.wait(200)
|
||||||
|
.then(() => {
|
||||||
const mouseover_requests = (await capture(async () => {
|
return capture(() => {
|
||||||
await nightmare
|
return nightmare
|
||||||
.mouseover('[href="/blog/what-is-sapper"]')
|
.mouseover('[href="/blog/what-is-sapper"]')
|
||||||
.wait(200);
|
.wait(200);
|
||||||
})).map(r => r.url);
|
});
|
||||||
|
})
|
||||||
assert.deepEqual(mouseover_requests, [
|
.then(mouseover_requests => {
|
||||||
|
assert.deepEqual(mouseover_requests.map(r => r.url), [
|
||||||
'/api/blog/what-is-sapper'
|
'/api/blog/what-is-sapper'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const click_requests = (await capture(async () => {
|
return capture(() => {
|
||||||
await nightmare
|
return nightmare
|
||||||
.click('[href="/blog/what-is-sapper"]')
|
.click('[href="/blog/what-is-sapper"]')
|
||||||
.wait(200);
|
.wait(200);
|
||||||
})).map(r => r.url);
|
});
|
||||||
|
})
|
||||||
assert.deepEqual(click_requests, []);
|
.then(click_requests => {
|
||||||
|
assert.deepEqual(click_requests.map(r => r.url), []);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cancels navigation if subsequent navigation occurs during preload', async () => {
|
it('cancels navigation if subsequent navigation occurs during preload', () => {
|
||||||
await nightmare
|
return nightmare
|
||||||
.goto(base)
|
.goto(base)
|
||||||
.wait(() => window.READY)
|
.wait(() => window.READY)
|
||||||
.click('a[href="/slow-preload"]')
|
.click('a[href="/slow-preload"]')
|
||||||
.wait(100)
|
.wait(100)
|
||||||
.click('a[href="/about"]')
|
.click('a[href="/about"]')
|
||||||
.wait(100);
|
.wait(100)
|
||||||
|
.then(() => nightmare.path())
|
||||||
|
.then(path => {
|
||||||
|
assert.equal(path, '/about');
|
||||||
|
|
||||||
assert.equal(
|
return nightmare.evaluate(() => document.querySelector('h1').textContent);
|
||||||
await nightmare.path(),
|
})
|
||||||
'/about'
|
.then(header_text => {
|
||||||
);
|
assert.equal(header_text, 'About this site');
|
||||||
|
|
||||||
assert.equal(
|
return nightmare.evaluate(() => window.fulfil({})).wait(100);
|
||||||
await nightmare.evaluate(() => document.querySelector('h1').textContent),
|
})
|
||||||
'About this site'
|
.then(() => nightmare.path())
|
||||||
);
|
.then(path => {
|
||||||
|
assert.equal(path, '/about');
|
||||||
|
|
||||||
await nightmare
|
return nightmare.evaluate(() => document.querySelector('h1').textContent);
|
||||||
.evaluate(() => window.fulfil({}))
|
})
|
||||||
.wait(100);
|
.then(header_text => {
|
||||||
|
assert.equal(header_text, 'About this site');
|
||||||
|
|
||||||
assert.equal(
|
return nightmare.evaluate(() => window.fulfil({})).wait(100);
|
||||||
await nightmare.path(),
|
});
|
||||||
'/about'
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
await nightmare.evaluate(() => document.querySelector('h1').textContent),
|
|
||||||
'About this site'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes entire request object to preload', async () => {
|
it('passes entire request object to preload', () => {
|
||||||
const html = await nightmare
|
return nightmare
|
||||||
.goto(`${base}/show-url`)
|
.goto(`${base}/show-url`)
|
||||||
.evaluate(() => document.querySelector('p').innerHTML);
|
.evaluate(() => document.querySelector('p').innerHTML)
|
||||||
|
.then(html => {
|
||||||
assert.equal(html, `URL is /show-url`);
|
assert.equal(html, `URL is /show-url`);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('calls a delete handler', async () => {
|
it('calls a delete handler', async () => {
|
||||||
await nightmare
|
await nightmare
|
||||||
@@ -305,9 +326,8 @@ function run(env) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('headers', () => {
|
describe('headers', () => {
|
||||||
it('sets Content-Type and Link...preload headers', async () => {
|
it('sets Content-Type and Link...preload headers', () => {
|
||||||
const { headers } = await get('/');
|
return get('/').then(({ headers }) => {
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
headers['Content-Type'],
|
headers['Content-Type'],
|
||||||
'text/html'
|
'text/html'
|
||||||
@@ -320,6 +340,7 @@ function run(env) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function exec(cmd) {
|
function exec(cmd) {
|
||||||
|
|||||||
Reference in New Issue
Block a user