Compare commits

...

19 Commits

Author SHA1 Message Date
Rich Harris
31110a5326 -> v0.13.5 2018-06-26 18:30:46 -04:00
Rich Harris
667a68768c Merge pull request #294 from sveltejs/gh-289
fix handling of fatal errors
2018-06-26 18:29:56 -04:00
Rich Harris
5075981a90 fix handling of fatal errors - fixes #289 2018-06-26 13:49:11 -04:00
Rich Harris
611dc4f6be -> v0.13.4 2018-06-18 22:45:51 -04:00
Rich Harris
0b43eaa992 blur active element on navigation - fixes #287 2018-06-18 22:44:55 -04:00
Rich Harris
47cdc1c4c8 wait until server restarts before emitting hot reload update 2018-06-18 22:31:10 -04:00
Rich Harris
31c071ad72 Merge branch 'master' of github.com:sveltejs/sapper 2018-06-18 12:22:42 -04:00
Rich Harris
e91edaee12 Merge pull request #286 from sveltejs/invalidate-client-assets
always refresh client_assets in dev
2018-06-18 12:22:37 -04:00
Rich Harris
34c1fee5db Merge branch 'master' of github.com:sveltejs/sapper 2018-06-17 13:02:13 -04:00
Rich Harris
5375422633 Merge pull request #285 from sveltejs/event-on-exit
emit a fatal event if server crashes
2018-06-17 13:02:03 -04:00
Rich Harris
1dafe934b0 initialise rebuild stats 2018-06-17 13:01:51 -04:00
Rich Harris
e1a33c6a9b always refresh client_assets in dev 2018-06-17 13:00:27 -04:00
Rich Harris
0800fa016b emit a fatal event if server crashes 2018-06-16 20:18:32 -04:00
Rich Harris
8f3454c3b1 -> v0.13.3 2018-06-16 13:49:44 -04:00
Rich Harris
f0d7a1aaab change fatal events to be clonable, for IPC purposes 2018-06-16 13:49:19 -04:00
Rich Harris
8240595d70 -> v0.13.2 2018-06-15 10:58:55 -04:00
Rich Harris
658d8dd50c Merge pull request #284 from sveltejs/emit-basepath
Emit basepath event
2018-06-15 10:57:04 -04:00
Rich Harris
9eeeaa24c1 emit a basepath event on first run 2018-06-14 17:20:46 -04:00
Rich Harris
9c4a3592ff remove some unused code 2018-06-14 16:34:16 -04:00
9 changed files with 140 additions and 36 deletions

View File

@@ -1,5 +1,26 @@
# sapper changelog # sapper changelog
## 0.13.5
* Fix handling of fatal errors ([#289](https://github.com/sveltejs/sapper/issues/289))
## 0.13.4
* Focus `<body>` after navigation ([#287](https://github.com/sveltejs/sapper/issues/287))
* Fix timing of hot reload updates
* Emit `fatal` event if server crashes ([#285](https://github.com/sveltejs/sapper/pull/285))
* Emit `stdout` and `stderr` events on dev watcher ([#285](https://github.com/sveltejs/sapper/pull/285))
* Always refresh client assets in dev ([#286](https://github.com/sveltejs/sapper/pull/286))
* Correctly initialise rebuild stats
## 0.13.3
* Make `fatal` events clonable for IPC purposes
## 0.13.2
* Emit a `basepath` event ([#284](https://github.com/sveltejs/sapper/pull/284))
## 0.13.1 ## 0.13.1
* Reinstate ten-second interval between dev server heartbeats ([#276](https://github.com/sveltejs/sapper/issues/276)) * Reinstate ten-second interval between dev server heartbeats ([#276](https://github.com/sveltejs/sapper/issues/276))

View File

@@ -1,6 +1,6 @@
{ {
"name": "sapper", "name": "sapper",
"version": "0.13.1", "version": "0.13.5",
"description": "Military-grade apps, engineered by Svelte", "description": "Military-grade apps, engineered by Svelte",
"main": "dist/middleware.ts.js", "main": "dist/middleware.ts.js",
"bin": { "bin": {

View File

@@ -6,7 +6,6 @@ import * as ports from 'port-authority';
import mkdirp from 'mkdirp'; import mkdirp from 'mkdirp';
import rimraf from 'rimraf'; import rimraf from 'rimraf';
import format_messages from 'webpack-format-messages'; import format_messages from 'webpack-format-messages';
import prettyMs from 'pretty-ms';
import { locations } from '../config'; import { locations } from '../config';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { create_routes, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core'; import { create_routes, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core';
@@ -34,6 +33,7 @@ class Watcher extends EventEmitter {
server: Deferred; server: Deferred;
}; };
crashed: boolean;
restarting: boolean; restarting: boolean;
current_build: { current_build: {
changed: Set<string>; changed: Set<string>;
@@ -91,7 +91,7 @@ class Watcher extends EventEmitter {
if (this.port) { if (this.port) {
if (!await ports.check(this.port)) { if (!await ports.check(this.port)) {
this.emit('fatal', <events.FatalEvent>{ this.emit('fatal', <events.FatalEvent>{
error: new Error(`Port ${this.port} is unavailable`) message: `Port ${this.port} is unavailable`
}); });
return; return;
} }
@@ -131,6 +131,18 @@ class Watcher extends EventEmitter {
// TODO watch the configs themselves? // TODO watch the configs themselves?
const compilers = create_compilers({ webpack: this.dirs.webpack }); const compilers = create_compilers({ webpack: this.dirs.webpack });
let log = '';
const emitFatal = () => {
this.emit('fatal', <events.FatalEvent>{
message: `Server crashed`,
log
});
this.crashed = true;
this.proc = null;
};
this.watch(compilers.server, { this.watch(compilers.server, {
name: 'server', name: 'server',
@@ -143,22 +155,35 @@ class Watcher extends EventEmitter {
fs.writeFileSync(path.join(dest, 'server_info.json'), JSON.stringify(info, null, ' ')); fs.writeFileSync(path.join(dest, 'server_info.json'), JSON.stringify(info, null, ' '));
this.deferreds.client.promise.then(() => { this.deferreds.client.promise.then(() => {
this.dev_server.send({
status: 'completed'
});
const restart = () => { const restart = () => {
ports.wait(this.port).then((() => { log = '';
this.emit('ready', <events.ReadyEvent>{ this.crashed = false;
port: this.port,
process: this.proc
});
this.deferreds.server.fulfil(); ports.wait(this.port)
})); .then((() => {
this.emit('ready', <events.ReadyEvent>{
port: this.port,
process: this.proc
});
this.deferreds.server.fulfil();
this.dev_server.send({
status: 'completed'
});
}))
.catch(err => {
if (this.crashed) return;
this.emit('fatal', <events.FatalEvent>{
message: `Server is not listening on port ${this.port}`,
log
});
});
}; };
if (this.proc) { if (this.proc) {
this.proc.removeListener('exit', emitFatal);
this.proc.kill(); this.proc.kill();
this.proc.on('exit', restart); this.proc.on('exit', restart);
} else { } else {
@@ -172,6 +197,26 @@ class Watcher extends EventEmitter {
}, process.env), }, process.env),
stdio: ['ipc'] stdio: ['ipc']
}); });
this.proc.stdout.on('data', chunk => {
log += chunk;
this.emit('stdout', chunk);
});
this.proc.stderr.on('data', chunk => {
log += chunk;
this.emit('stderr', chunk);
});
this.proc.on('message', message => {
if (message.__sapper__ && message.event === 'basepath') {
this.emit('basepath', {
basepath: message.basepath
});
}
});
this.proc.on('exit', emitFatal);
}); });
} }
}); });
@@ -243,8 +288,8 @@ class Watcher extends EventEmitter {
this.restarting = true; this.restarting = true;
this.current_build = { this.current_build = {
changed: new Set(), changed: new Set([filename]),
rebuilding: new Set(), rebuilding: new Set([type]),
unique_warnings: new Set(), unique_warnings: new Set(),
unique_errors: new Set() unique_errors: new Set()
}; };
@@ -277,7 +322,7 @@ class Watcher extends EventEmitter {
if (err) { if (err) {
this.emit('error', <events.ErrorEvent>{ this.emit('error', <events.ErrorEvent>{
type: name, type: name,
error: err message: err.message
}); });
} else { } else {
const messages = format_messages(stats); const messages = format_messages(stats);

View File

@@ -66,7 +66,7 @@ async function execute(emitter: EventEmitter, {
const saved = new Set(); const saved = new Set();
proc.on('message', message => { proc.on('message', message => {
if (!message.__sapper__) return; if (!message.__sapper__ || message.event !== 'file') return;
let file = new URL(message.url, origin).pathname.slice(1); let file = new URL(message.url, origin).pathname.slice(1);
let { body } = message; let { body } = message;

View File

@@ -7,11 +7,12 @@ export type ReadyEvent = {
export type ErrorEvent = { export type ErrorEvent = {
type: string; type: string;
error: Error; message: string;
}; };
export type FatalEvent = { export type FatalEvent = {
error: Error; message: string;
log?: string;
}; };
export type InvalidEvent = { export type InvalidEvent = {

View File

@@ -36,11 +36,12 @@ export function dev(opts: { port: number, open: boolean }) {
watcher.on('error', (event: events.ErrorEvent) => { watcher.on('error', (event: events.ErrorEvent) => {
console.log(`${colors.red(`${event.type}`)}`); console.log(`${colors.red(`${event.type}`)}`);
console.log(`${colors.red(event.error.message)}`); console.log(`${colors.red(event.message)}`);
}); });
watcher.on('fatal', (event: events.FatalEvent) => { watcher.on('fatal', (event: events.FatalEvent) => {
console.log(`${colors.bold.red(`> ${event.error.message}`)}`); console.log(`${colors.bold.red(`> ${event.message}`)}`);
if (event.log) console.log(event.log);
}); });
watcher.on('build', (event: events.BuildEvent) => { watcher.on('build', (event: events.BuildEvent) => {

View File

@@ -1,15 +1,12 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { resolve, URL } from 'url'; import { URL } from 'url';
import { ClientRequest, ServerResponse } from 'http'; import { ClientRequest, ServerResponse } from 'http';
import cookie from 'cookie'; import cookie from 'cookie';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import devalue from 'devalue'; import devalue from 'devalue';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import { lookup } from './middleware/mime'; import { lookup } from './middleware/mime';
import { locations, dev } from './config'; import { locations, dev } from './config';
import { Route, Template } from './interfaces';
import sourceMapSupport from 'source-map-support'; import sourceMapSupport from 'source-map-support';
sourceMapSupport.install(); sourceMapSupport.install();
@@ -59,7 +56,7 @@ export default function middleware({ App, routes, store }: {
const output = locations.dest(); const output = locations.dest();
const client_assets = JSON.parse(fs.readFileSync(path.join(output, 'client_assets.json'), 'utf-8')); let emitted_basepath = false;
const middleware = compose_handlers([ const middleware = compose_handlers([
(req: Req, res: ServerResponse, next: () => void) => { (req: Req, res: ServerResponse, next: () => void) => {
@@ -69,6 +66,16 @@ export default function middleware({ App, routes, store }: {
: ''; : '';
} }
if (!emitted_basepath && process.send) {
process.send({
__sapper__: true,
event: 'basepath',
basepath: req.baseUrl
});
emitted_basepath = true;
}
if (req.path === undefined) { if (req.path === undefined) {
req.path = req.url.replace(/\?.*/, ''); req.path = req.url.replace(/\?.*/, '');
} }
@@ -96,7 +103,7 @@ export default function middleware({ App, routes, store }: {
cache_control: 'max-age=31536000' cache_control: 'max-age=31536000'
}), }),
get_route_handler(client_assets, App, routes, store) get_route_handler(App, routes, store)
].filter(Boolean)); ].filter(Boolean));
return middleware; return middleware;
@@ -139,9 +146,13 @@ function serve({ prefix, pathname, cache_control }: {
}; };
} }
const resolved = Promise.resolve(); function get_route_handler(App: Component, routes: RouteObject[], store_getter: (req: Req) => Store) {
const output = locations.dest();
const get_chunks = dev()
? () => JSON.parse(fs.readFileSync(path.join(output, 'client_assets.json'), 'utf-8'))
: (assets => () => assets)(JSON.parse(fs.readFileSync(path.join(output, 'client_assets.json'), 'utf-8')));
function get_route_handler(chunks: Record<string, string>, App: Component, routes: RouteObject[], store_getter: (req: Req) => Store) {
const template = dev() const template = dev()
? () => fs.readFileSync(`${locations.app()}/template.html`, 'utf-8') ? () => fs.readFileSync(`${locations.app()}/template.html`, 'utf-8')
: (str => () => str)(fs.readFileSync(`${locations.dest()}/template.html`, 'utf-8')); : (str => () => str)(fs.readFileSync(`${locations.dest()}/template.html`, 'utf-8'));
@@ -152,6 +163,8 @@ function get_route_handler(chunks: Record<string, string>, App: Component, route
const handlers = route.handlers[Symbol.iterator](); const handlers = route.handlers[Symbol.iterator]();
function next() { function next() {
const chunks: Record<string, string> = get_chunks();
try { try {
const { value: handler, done } = handlers.next(); const { value: handler, done } = handlers.next();
@@ -281,6 +294,7 @@ function get_route_handler(chunks: Record<string, string>, App: Component, route
if (process.send) { if (process.send) {
process.send({ process.send({
__sapper__: true, __sapper__: true,
event: 'file',
url: req.url, url: req.url,
method: req.method, method: req.method,
status: 200, status: 200,
@@ -320,6 +334,7 @@ function get_route_handler(chunks: Record<string, string>, App: Component, route
process.send({ process.send({
__sapper__: true, __sapper__: true,
event: 'file',
url: req.url, url: req.url,
method: req.method, method: req.method,
status: res.statusCode, status: res.statusCode,
@@ -375,7 +390,7 @@ function get_route_handler(chunks: Record<string, string>, App: Component, route
function render_page({ head, css, html }) { function render_page({ head, css, html }) {
const page = template() const page = template()
.replace('%sapper.base%', `<base href="${req.baseUrl}/">`) .replace('%sapper.base%', `<base href="${req.baseUrl}/">`)
.replace('%sapper.scripts%', `<script>__SAPPER__={baseUrl: "${req.baseUrl}"}</script><script src='${req.baseUrl}/client/${chunks.main}'></script>`) .replace('%sapper.scripts%', `<script>__SAPPER__={baseUrl: "${req.baseUrl}"}</script><script src='${req.baseUrl}/client/${get_chunks().main}'></script>`)
.replace('%sapper.html%', html) .replace('%sapper.html%', html)
.replace('%sapper.head%', `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`) .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>` : '')); .replace('%sapper.styles%', (css && css.code ? `<style>${css.code}</style>` : ''));
@@ -451,10 +466,6 @@ function compose_handlers(handlers: Handler[]) {
}; };
} }
function read_json(file: string) {
return JSON.parse(fs.readFileSync(file, 'utf-8'));
}
function try_serialize(data: any) { function try_serialize(data: any) {
try { try {
return devalue(data); return devalue(data);

View File

@@ -161,6 +161,7 @@ function navigate(target: Target, id: number) {
} }
render(Page, props, scroll_history[id], token); render(Page, props, scroll_history[id], token);
document.activeElement.blur();
}); });
} }

View File

@@ -133,6 +133,7 @@ function run({ mode, basepath = '' }) {
let capture; let capture;
let base; let base;
let captured_basepath;
const nightmare = new Nightmare(); const nightmare = new Nightmare();
@@ -179,7 +180,13 @@ function run({ mode, basepath = '' }) {
let handler; let handler;
proc.on('message', message => { proc.on('message', message => {
if (message.__sapper__) return; if (message.__sapper__) {
if (message.event === 'basepath') {
captured_basepath = basepath;
}
return;
}
if (handler) handler(message); if (handler) handler(message);
}); });
@@ -597,6 +604,23 @@ function run({ mode, basepath = '' }) {
assert.ok(!hasProgressIndicator); assert.ok(!hasProgressIndicator);
}); });
}); });
it('emits a basepath', () => {
assert.equal(captured_basepath, basepath);
});
// skipped because Nightmare doesn't seem to focus the <a> correctly
it.skip('resets the active element after navigation', () => {
return nightmare
.goto(base)
.init()
.click('a[href="about"]')
.wait(100)
.evaluate(() => document.activeElement.nodeName)
.then(name => {
assert.equal(name, 'BODY');
});
});
}); });
describe('headers', () => { describe('headers', () => {