various stability improvements

This commit is contained in:
Rich Harris
2018-03-03 21:48:50 -05:00
parent ff3b43443e
commit 99853c5181
7 changed files with 102 additions and 111 deletions

View File

@@ -12,7 +12,7 @@
"middleware.js",
"runtime",
"runtime.js",
"hmr-client.js",
"sapper-dev-client.js",
"webpack"
],
"directories": {

View File

@@ -3,7 +3,7 @@ let source;
function check() {
if (module.hot.status() === 'idle') {
module.hot.check(true).then(modules => {
console.log(`HMR updated`);
console.log(`[SAPPER] applied HMR update`);
});
}
}
@@ -11,16 +11,26 @@ function check() {
export function connect(port) {
if (source || !window.EventSource) return;
source = new EventSource(`http://localhost:${port}/hmr`);
source = new EventSource(`http://localhost:${port}/__sapper__`);
window.source = source;
source.onopen = function(event) {
console.log(`HMR connected`);
console.log(`[SAPPER] dev client connected`);
};
source.onerror = function(error) {
console.error(error);
};
source.onmessage = function(event) {
const data = JSON.parse(event.data);
if (!data) return; // just a heartbeat
if (data.action === 'reload') {
window.location.reload();
}
if (data.status === 'completed') {
check();
}

View File

@@ -8,7 +8,7 @@ import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import { wait_for_port } from './utils';
import { dest } from '../config';
import { create_compilers, create_app, create_routes, create_serviceworker, create_template } from 'sapper/core.js';
import { create_compilers, create_app, create_routes, create_serviceworker } from 'sapper/core.js';
type Deferred = {
promise?: Promise<any>;
@@ -31,11 +31,12 @@ function create_hot_update_server(port: number, interval = 10000) {
const clients = new Set();
const server = http.createServer((req, res) => {
if (req.url !== '/hmr') return;
if (req.url !== '/__sapper__') return;
req.socket.setKeepAlive(true);
res.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Cache-Control',
'Content-Type': 'text/event-stream;charset=utf-8',
'Cache-Control': 'no-cache, no-transform',
'Connection': 'keep-alive',
@@ -103,8 +104,9 @@ export default async function dev() {
});
watch_files('app/template.html', ['change'], () => {
const template = create_template();
// TODO reload current page?
hot_update_server.send({
action: 'reload'
});
});
let proc: child_process.ChildProcess;

View File

@@ -56,14 +56,14 @@ function generate_client(routes: Route[], dev_port?: number) {
];`.replace(/^\t\t/gm, '').trim();
if (dev()) {
const hmr_client = posixify(
path.resolve(__dirname, 'hmr-client.js')
const sapper_dev_client = posixify(
path.resolve(__dirname, 'sapper-dev-client.js')
);
code += `
if (module.hot) {
import('${hmr_client}').then(client => {
import('${sapper_dev_client}').then(client => {
client.connect(${dev_port});
});
}`.replace(/^\t{3}/gm, '');

View File

@@ -1,39 +0,0 @@
import * as fs from 'fs';
import chalk from 'chalk';
import framer from 'code-frame';
import { locate } from 'locate-character';
function error(e: any) {
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);
}
export default function create_templates() {
const template = fs.readFileSync(`app/template.html`, 'utf-8');
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: `app/template.html`,
body: `<script src='%sapper.main%'> is unsupported — use %sapper.scripts% (without the <script> tag) instead`,
url: 'https://github.com/sveltejs/sapper/issues/86',
frame
});
}
return {
render: (data: Record<string, string>) => {
return template.replace(/%sapper\.(\w+)%/g, (match, key) => {
return key in data ? data[key] : '';
});
}
};
}

View File

@@ -1,5 +1,4 @@
export { default as create_app } from './create_app';
export { default as create_serviceworker } from './create_serviceworker';
export { default as create_compilers } from './create_compilers';
export { default as create_routes } from './create_routes';
export { default as create_template } from './create_template';
export { default as create_routes } from './create_routes';

View File

@@ -1,13 +1,13 @@
import * as fs from 'fs';
import * as path from 'path';
import { ClientRequest, ServerResponse } from 'http';
// import * as mime from 'mime';
import * as mime from 'mime';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import serialize from 'serialize-javascript';
import escape_html from 'escape-html';
import { create_routes, templates, create_compilers, create_template } from 'sapper/core.js';
import { dest } from '../config';
import { create_routes, templates, create_compilers } from 'sapper/core.js';
import { dest, dev } from '../config';
import { Route, Template } from '../interfaces';
import sourceMapSupport from 'source-map-support';
@@ -45,58 +45,70 @@ export default function middleware({ routes }: {
const client_info = JSON.parse(fs.readFileSync(path.join(output, 'client_info.json'), 'utf-8'));
const template = create_template();
const shell = try_read(path.join(output, 'index.html'));
const serviceworker = try_read(path.join(output, 'service-worker.js'));
const middleware = compose_handlers([
(req: Req, res: ServerResponse, next: () => void) => {
req.pathname = req.url.replace(/\?.*/, '');
next();
},
shell && get_asset_handler({
exists(path.join(output, 'index.html')) && serve({
pathname: '/index.html',
type: 'text/html',
cache: 'max-age=600',
body: shell
cache_control: 'max-age=600'
}),
serviceworker && get_asset_handler({
exists(path.join(output, 'service-worker.js')) && serve({
pathname: '/service-worker.js',
type: 'application/javascript',
cache: 'max-age=600',
body: serviceworker
cache_control: 'max-age=600'
}),
(req: Req, res: ServerResponse, next: () => void) => {
if (req.pathname.startsWith('/client/')) {
// const type = mime.getType(req.pathname);
const type = 'application/javascript'; // TODO might not be, if using e.g. CSS plugin
serve({
prefix: '/client/',
cache_control: 'max-age=31536000'
}),
// TODO cache?
const rs = fs.createReadStream(path.join(output, req.pathname.slice(1)));
rs.on('error', error => {
res.statusCode = 404;
res.end('not found');
});
res.setHeader('Content-Type', type);
res.setHeader('Cache-Control', 'max-age=31536000');
rs.pipe(res);
} else {
next();
}
},
get_route_handler(client_info.assetsByChunkName, routes, template)
get_route_handler(client_info.assetsByChunkName, routes)
].filter(Boolean));
return middleware;
}
function serve({ prefix, pathname, cache_control }: {
prefix?: string,
pathname?: string,
cache_control: string
}) {
const filter = pathname
? (req: Req) => req.pathname === pathname
: (req: Req) => req.pathname.startsWith(prefix);
const output = dest();
const cache: Map<string, Buffer> = new Map();
const read = dev()
? (file: string) => fs.readFileSync(path.resolve(output, file))
: (file: string) => (cache.has(file) ? cache : cache.set(file, fs.readFileSync(path.resolve(output, file)))).get(file)
return (req: Req, res: ServerResponse, next: () => void) => {
if (filter(req)) {
const type = mime.getType(req.pathname);
try {
const data = read(req.pathname.slice(1));
res.setHeader('Content-Type', type);
res.setHeader('Cache-Control', cache_control);
res.end(data);
} catch (err) {
res.statusCode = 404;
res.end('not found');
}
} else {
next();
}
};
}
function get_asset_handler({ pathname, type, cache, body }: {
pathname: string;
type: string;
@@ -114,7 +126,11 @@ function get_asset_handler({ pathname, type, cache, body }: {
const resolved = Promise.resolve();
function get_route_handler(chunks: Record<string, string>, routes: RouteObject[], template: Template) {
function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]) {
const template = dev()
? () => fs.readFileSync('app/template.html', 'utf-8')
: (str => () => str)(fs.readFileSync('app/template.html', 'utf-8'));
function handle_route(route: RouteObject, req: Req, res: ServerResponse) {
req.params = route.params(route.pattern.exec(req.pathname));
@@ -174,12 +190,11 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
scripts = `<script>__SAPPER__ = { preloaded: ${serialized} };</script>${scripts}`;
const page = template.render({
scripts,
html,
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
styles: (css && css.code ? `<style>${css.code}</style>` : '')
});
const page = template()
.replace('%sapper.scripts%', scripts)
.replace('%sapper.html%', html)
.replace('%sapper.head%', `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
.replace('%sapper.styles%', (css && css.code ? `<style>${css.code}</style>` : ''));
res.end(page);
@@ -271,17 +286,19 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
const { head, css, html } = rendered;
res.end(template.render({
scripts: `<script src='/client/${chunks.main}'></script>`,
html,
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
styles: (css && css.code ? `<style>${css.code}</style>` : '')
}));
const page = template()
.replace('%sapper.scripts%', `<script src='/client/${chunks.main}'></script>`)
.replace('%sapper.html%', html)
.replace('%sapper.head%', `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
.replace('%sapper.styles%', (css && css.code ? `<style>${css.code}</style>` : ''));
res.end(page);
}
const error_route = routes.find((route: RouteObject) => route.error === '5xx');
function handle_error(req: Req, res: ServerResponse, statusCode: number, message: Error | string) {
// TODO lot of repetition between this and handle_not_found
if (statusCode >= 400 && statusCode < 500) {
return handle_not_found(req, res, statusCode, message);
}
@@ -298,12 +315,13 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
const { head, css, html } = rendered;
res.end(template.render({
scripts: `<script src='/client/${chunks.main}'></script>`,
html,
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
styles: (css && css.code ? `<style>${css.code}</style>` : '')
}));
const page = template()
.replace('%sapper.scripts%', `<script src='/client/${chunks.main}'></script>`)
.replace('%sapper.html%', html)
.replace('%sapper.head%', `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
.replace('%sapper.styles%', (css && css.code ? `<style>${css.code}</style>` : ''));
res.end(page);
}
return function find_route(req: Req, res: ServerResponse) {
@@ -353,10 +371,11 @@ function try_serialize(data: any) {
}
}
function try_read(file: string) {
function exists(file: string) {
try {
return fs.readFileSync(file, 'utf-8');
fs.statSync(file);
return true;
} catch (err) {
return null;
return false;
}
}