diff --git a/.travis.yml b/.travis.yml
index 4c62059..a37458b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,7 @@ sudo: false
language: node_js
node_js:
+ - "6"
- "stable"
env:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b9f0425..03fb0ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# sapper changelog
+## 0.4.0
+
+* `%sapper.main%` has been replaced with `%sapper.scripts%` ([#86](https://github.com/sveltejs/sapper/issues/86))
+* Node 6 support ([#67](https://github.com/sveltejs/sapper/pull/67))
+* Explicitly load css-loader and style-loader ([#72](https://github.com/sveltejs/sapper/pull/72))
+* DELETE requests are handled with `del` exports ([#77](https://github.com/sveltejs/sapper/issues/77))
+* Send preloaded data for first route to client, where possible ([#3](https://github.com/sveltejs/sapper/issues/3))
+
## 0.3.2
* Expose `prefetch` function ([#61](https://github.com/sveltejs/sapper/pull/61))
diff --git a/lib/index.js b/lib/index.js
index 5279ff3..4d76472 100644
--- a/lib/index.js
+++ b/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 = ``;
+
+ if (serialized) {
+ return `${main}`;
+ }
+
+ return main;
+ }),
+ html: promise.then(({ rendered }) => rendered.html),
+ head: promise.then(({ rendered }) => `${rendered.head}`),
+ styles: promise.then(({ rendered }) => (rendered.css && rendered.css.code ? `` : ''))
+ });
+ } else {
+ const { html, head, css } = mod.render(data);
+
+ const page = templates.render(200, {
+ scripts: ``,
+ html,
+ head: `${head}`,
+ styles: (css && css.code ? `` : '')
+ });
+
+ 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 }) => `${head}`),
- styles: promise.then(({ css }) => (css && css.code ? `` : ''))
- });
- } else {
- const { html, head, css } = mod.render(data);
-
- const page = templates.render(200, {
- main: client.main_file,
- html,
- head: `${head}`,
- styles: (css && css.code ? `` : '')
- });
-
- 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: ``,
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;
+ }
}
\ No newline at end of file
diff --git a/lib/templates.js b/lib/templates.js
index 34360d1..36f1be4 100644
--- a/lib/templates.js
+++ b/lib/templates.js
@@ -1,10 +1,22 @@
const fs = require('fs');
const glob = require('glob');
+const chalk = require('chalk');
const chokidar = require('chokidar');
+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 => {
@@ -12,7 +24,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: `
\ No newline at end of file
diff --git a/test/app/routes/show-url.html b/test/app/routes/show-url.html
index f0d4bb0..bf73f7a 100644
--- a/test/app/routes/show-url.html
+++ b/test/app/routes/show-url.html
@@ -3,7 +3,7 @@
\ No newline at end of file
+
diff --git a/test/app/templates/2xx.html b/test/app/templates/2xx.html
index e75cd12..e1444c5 100644
--- a/test/app/templates/2xx.html
+++ b/test/app/templates/2xx.html
@@ -32,6 +32,6 @@
-
+ %sapper.scripts%