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

Commit

Permalink
Merge pull request #87 from sveltejs/gh-3
Browse files Browse the repository at this point in the history
Serialize preloaded data for initial page and serve to client
  • Loading branch information
Rich-Harris authored Jan 14, 2018
2 parents cfeeafd + 44285cd commit 14df138
Show file tree
Hide file tree
Showing 8 changed files with 4,479 additions and 75 deletions.
33 changes: 26 additions & 7 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -144,21 +145,31 @@ function get_route_handler(fn) {

if (mod.preload) {
const promise = Promise.resolve(mod.preload(req)).then(preloaded => {
const serialized = try_serialize(preloaded);
Object.assign(data, preloaded);
return mod.render(data);

return { rendered: mod.render(data), serialized };
});

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>` : ''))
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, {
main: client.main_file,
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>` : '')
Expand Down Expand Up @@ -221,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
}));
};
Expand Down Expand Up @@ -249,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;
}
}
31 changes: 30 additions & 1 deletion lib/templates.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,47 @@
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 => {
const template = fs.readFileSync(`templates/${file}`, 'utf-8');
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%'> has been removed — use %sapper.scripts% (without the <script> tag) instead`,
url: 'https://github.com/sveltejs/sapper/issues/86',
frame
});
}

const specificity = (
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
"dependencies": {
"chalk": "^2.3.0",
"chokidar": "^1.7.0",
"code-frame": "^5.0.0",
"escape-html": "^1.0.3",
"locate-character": "^2.0.5",
"mkdirp": "^0.5.1",
"relative": "^3.0.2",
"require-relative": "^0.8.7",
"rimraf": "^2.6.2",
"serialize-javascript": "^1.4.0",
"webpack": "^3.10.0",
"webpack-hot-middleware": "^2.21.0"
},
Expand Down
20 changes: 13 additions & 7 deletions src/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,16 @@ function render(Component: ComponentConstructor, data: any, scroll: ScrollPositi
}
}

function prepare_route(Component, data) {
return Promise.resolve(
Component.preload ? Component.preload(data) : {}
).then(preloaded => {
function prepare_route(Component: ComponentConstructor, data: RouteData) {
if (!Component.preload) {
return { Component, data };
}

if (!component && window.__SAPPER__ && window.__SAPPER__.preloaded) {
return { Component, data: Object.assign(data, window.__SAPPER__.preloaded) };
}

return Promise.resolve(Component.preload(data)).then(preloaded => {
Object.assign(data, preloaded)
return { Component, data };
});
Expand Down Expand Up @@ -176,10 +182,10 @@ export function prefetch(href: string) {
}

function handle_touchstart_mouseover(event: MouseEvent | TouchEvent) {
const a: HTMLAnchorElement = <HTMLAnchorElement>findAnchor(<Node>event.target);
if (!a || a.rel !== 'prefetch') return;
const a: HTMLAnchorElement = <HTMLAnchorElement>findAnchor(<Node>event.target);
if (!a || a.rel !== 'prefetch') return;

prefetch(a.href);
prefetch(a.href);
}

let inited: boolean;
Expand Down
4 changes: 2 additions & 2 deletions test/app/routes/show-url.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<script>
export default {
preload({ url }) {
return { url };
if (url) return { url };
}
};
</script>
</script>
2 changes: 1 addition & 1 deletion test/app/templates/2xx.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@
<!-- Sapper creates a <script> tag containing `templates/main.js`
and anything else it needs to hydrate the app and
initialise the router -->
<script src='%sapper.main%'></script>
%sapper.scripts%
</body>
</html>
87 changes: 30 additions & 57 deletions test/common/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ const fetch = require('node-fetch');
run('production');
run('development');

Nightmare.action('page', {
title(done) {
this.evaluate_now(() => document.querySelector('h1').textContent, done);
}
});

function run(env) {
describe(`env=${env}`, function () {
this.timeout(20000);
Expand Down Expand Up @@ -150,55 +156,38 @@ function run(env) {
});

it('serves /', () => {
return nightmare
.goto(base)
.evaluate(() => document.querySelector('h1').textContent)
.then(title => {
assert.equal(title, 'Great success!');
});
return nightmare.goto(base).page.title().then(title => {
assert.equal(title, 'Great success!');
});
});

it('serves static route', () => {
return nightmare
.goto(`${base}/about`)
.evaluate(() => document.querySelector('h1').textContent)
.then(title => {
assert.equal(title, 'About this site');
});
return nightmare.goto(`${base}/about`).page.title().then(title => {
assert.equal(title, 'About this site');
});
});

it('serves dynamic route', () => {
return nightmare
.goto(`${base}/blog/what-is-sapper`)
.evaluate(() => document.querySelector('h1').textContent)
.then(title => {
assert.equal(title, 'What is Sapper?');
});
return nightmare.goto(`${base}/blog/what-is-sapper`).page.title().then(title => {
assert.equal(title, 'What is Sapper?');
});
});

it('navigates to a new page without reloading', () => {
let requests;
return nightmare
.goto(base).wait(() => window.READY).wait(200)
return nightmare.goto(base).wait(() => window.READY).wait(200)
.then(() => {
return capture(() => {
return nightmare.click('a[href="/about"]');
});
return capture(() => nightmare.click('a[href="/about"]'));
})
.then(reqs => {
requests = reqs;

.then(requests => {
assert.deepEqual(requests.map(r => r.url), []);
return nightmare.path();
})
.then(path => {
assert.equal(path, '/about');

return nightmare.evaluate(() => document.title);
return nightmare.title();
})
.then(title => {
assert.equal(title, 'About');

assert.deepEqual(requests.map(r => r.url), []);
});
});

Expand All @@ -209,7 +198,7 @@ function run(env) {
.click('.goto')
.wait(() => window.location.pathname === '/blog/what-is-sapper')
.wait(100)
.then(() => nightmare.evaluate(() => document.title))
.title()
.then(title => {
assert.equal(title, 'What is Sapper?');
});
Expand Down Expand Up @@ -281,47 +270,31 @@ function run(env) {
.then(() => nightmare.path())
.then(path => {
assert.equal(path, '/about');

return nightmare.evaluate(() => document.querySelector('h1').textContent);
return nightmare.title();
})
.then(header_text => {
assert.equal(header_text, 'About this site');

.then(title => {
assert.equal(title, 'About');
return nightmare.evaluate(() => window.fulfil({})).wait(100);
})
.then(() => nightmare.path())
.then(path => {
assert.equal(path, '/about');

return nightmare.evaluate(() => document.querySelector('h1').textContent);
return nightmare.title();
})
.then(header_text => {
assert.equal(header_text, 'About this site');

return nightmare.evaluate(() => window.fulfil({})).wait(100);
.then(title => {
assert.equal(title, 'About');
});
});

it('passes entire request object to preload', () => {
return nightmare
.goto(`${base}/show-url`)
.wait(() => window.READY)
.evaluate(() => document.querySelector('p').innerHTML)
.then(html => {
.end().then(html => {
assert.equal(html, `URL is /show-url`);
});
});

it('calls a delete handler', () => {
return nightmare
.goto(`${base}/delete-test`)
.wait(() => window.READY)
.click('.del')
.wait(() => window.deleted)
.evaluate(() => window.deleted.id)
.then(id => {
assert.equal(id, 42);
});
});
});

describe('headers', () => {
Expand Down Expand Up @@ -355,4 +328,4 @@ function exec(cmd) {
fulfil();
});
});
}
}
Loading

0 comments on commit 14df138

Please sign in to comment.