mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-13 03:25:24 +00:00
Merge branch 'master' into lazy-chokidar
This commit is contained in:
163
lib/index.js
163
lib/index.js
@@ -1,5 +1,6 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const serialize = require('serialize-javascript');
|
||||
const route_manager = require('./route_manager.js');
|
||||
const templates = require('./templates.js');
|
||||
const create_app = require('./utils/create_app.js');
|
||||
@@ -23,9 +24,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,76 +128,98 @@ 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 => {
|
||||
const serialized = try_serialize(preloaded);
|
||||
Object.assign(data, preloaded);
|
||||
|
||||
return { rendered: mod.render(data), serialized };
|
||||
});
|
||||
|
||||
return templates.stream(res, 200, {
|
||||
scripts: promise.then(({ serialized }) => {
|
||||
const main = `<script src='${client.main_file}'></script>`;
|
||||
|
||||
if (serialized) {
|
||||
return `<script>__SAPPER__ = { preloaded: ${serialized} };</script>${main}`;
|
||||
}
|
||||
|
||||
return main;
|
||||
}),
|
||||
html: promise.then(({ rendered }) => rendered.html),
|
||||
head: promise.then(({ rendered }) => `<noscript id='sapper-head-start'></noscript>${rendered.head}<noscript id='sapper-head-end'></noscript>`),
|
||||
styles: promise.then(({ rendered }) => (rendered.css && rendered.css.code ? `<style>${rendered.css.code}</style>` : ''))
|
||||
});
|
||||
} else {
|
||||
const { html, head, css } = mod.render(data);
|
||||
|
||||
const page = templates.render(200, {
|
||||
scripts: `<script src='${client.main_file}'></script>`,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
const handler = mod[req.method.toLowerCase()];
|
||||
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')
|
||||
}));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -207,7 +232,7 @@ function get_not_found_handler(fn) {
|
||||
title: 'Not found',
|
||||
status: 404,
|
||||
method: req.method,
|
||||
main: asset_cache.client.main_file,
|
||||
scripts: `<script src='${asset_cache.client.main_file}'></script>`,
|
||||
url: req.url
|
||||
}));
|
||||
};
|
||||
@@ -235,4 +260,12 @@ function compose_handlers(handlers) {
|
||||
|
||||
function read_json(file) {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
||||
}
|
||||
|
||||
function try_serialize(data) {
|
||||
try {
|
||||
return serialize(data);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,21 @@
|
||||
const fs = require('fs');
|
||||
const glob = require('glob');
|
||||
const chalk = require('chalk');
|
||||
const framer = require('code-frame');
|
||||
const { locate } = require('locate-character');
|
||||
const { dev } = require('./config.js');
|
||||
|
||||
let templates;
|
||||
|
||||
function error(e) {
|
||||
if (e.title) console.error(chalk.bold.red(e.title));
|
||||
if (e.body) console.error(chalk.red(e.body));
|
||||
if (e.url) console.error(chalk.cyan(e.url));
|
||||
if (e.frame) console.error(chalk.grey(e.frame));
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function create_templates() {
|
||||
templates = glob.sync('*.html', { cwd: 'templates' })
|
||||
.map(file => {
|
||||
@@ -11,7 +23,24 @@ function create_templates() {
|
||||
const status = file.replace('.html', '').toLowerCase();
|
||||
|
||||
if (!/^[0-9x]{3}$/.test(status)) {
|
||||
throw new Error(`Bad template — should be a valid status code like 404.html, or a wildcard like 2xx.html`);
|
||||
error({
|
||||
title: `templates/${file}`,
|
||||
body: `Bad template — should be a valid status code like 404.html, or a wildcard like 2xx.html`
|
||||
});
|
||||
}
|
||||
|
||||
const index = template.indexOf('%sapper.main%');
|
||||
if (index !== -1) {
|
||||
// TODO remove this in a future version
|
||||
const { line, column } = locate(template, index, { offsetLine: 1 });
|
||||
const frame = framer(template, line, column);
|
||||
|
||||
error({
|
||||
title: `templates/${file}`,
|
||||
body: `<script src='%sapper.main%'> is unsupported — use %sapper.scripts% (without the <script> tag) instead`,
|
||||
url: 'https://github.com/sveltejs/sapper/issues/86',
|
||||
frame
|
||||
});
|
||||
}
|
||||
|
||||
const specificity = (
|
||||
@@ -30,10 +59,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) {
|
||||
@@ -52,9 +85,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);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user