From 9c4a3592ff22cad7dd0a22e1cd879ffe4186f4c0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 14 Jun 2018 16:34:16 -0400 Subject: [PATCH 01/23] remove some unused code --- src/middleware.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index bf9d3bd..d605fbe 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,15 +1,12 @@ import * as fs from 'fs'; import * as path from 'path'; -import { resolve, URL } from 'url'; +import { URL } from 'url'; import { ClientRequest, ServerResponse } from 'http'; import cookie from 'cookie'; -import mkdirp from 'mkdirp'; -import rimraf from 'rimraf'; import devalue from 'devalue'; import fetch from 'node-fetch'; import { lookup } from './middleware/mime'; import { locations, dev } from './config'; -import { Route, Template } from './interfaces'; import sourceMapSupport from 'source-map-support'; sourceMapSupport.install(); @@ -139,8 +136,6 @@ function serve({ prefix, pathname, cache_control }: { }; } -const resolved = Promise.resolve(); - function get_route_handler(chunks: Record, App: Component, routes: RouteObject[], store_getter: (req: Req) => Store) { const template = dev() ? () => fs.readFileSync(`${locations.app()}/template.html`, 'utf-8') @@ -451,10 +446,6 @@ function compose_handlers(handlers: Handler[]) { }; } -function read_json(file: string) { - return JSON.parse(fs.readFileSync(file, 'utf-8')); -} - function try_serialize(data: any) { try { return devalue(data); From 9eeeaa24c1bae2e86894e29b0da8cc189190f221 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 14 Jun 2018 17:20:46 -0400 Subject: [PATCH 02/23] emit a basepath event on first run --- src/api/dev.ts | 9 ++++++++- src/api/export.ts | 2 +- src/middleware.ts | 14 ++++++++++++++ test/common/test.js | 13 ++++++++++++- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/api/dev.ts b/src/api/dev.ts index 524a4f9..4a20a3c 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -6,7 +6,6 @@ import * as ports from 'port-authority'; import mkdirp from 'mkdirp'; import rimraf from 'rimraf'; import format_messages from 'webpack-format-messages'; -import prettyMs from 'pretty-ms'; import { locations } from '../config'; import { EventEmitter } from 'events'; import { create_routes, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core'; @@ -172,6 +171,14 @@ class Watcher extends EventEmitter { }, process.env), stdio: ['ipc'] }); + + this.proc.on('message', message => { + if (message.__sapper__ && message.event === 'basepath') { + this.emit('basepath', { + basepath: message.basepath + }); + } + }); }); } }); diff --git a/src/api/export.ts b/src/api/export.ts index 9e764c4..f0a0a57 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -66,7 +66,7 @@ async function execute(emitter: EventEmitter, { const saved = new Set(); 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 { body } = message; diff --git a/src/middleware.ts b/src/middleware.ts index d605fbe..1c5ef09 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -58,6 +58,8 @@ export default function middleware({ App, routes, store }: { const client_assets = JSON.parse(fs.readFileSync(path.join(output, 'client_assets.json'), 'utf-8')); + let emitted_basepath = false; + const middleware = compose_handlers([ (req: Req, res: ServerResponse, next: () => void) => { if (req.baseUrl === undefined) { @@ -66,6 +68,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) { req.path = req.url.replace(/\?.*/, ''); } @@ -276,6 +288,7 @@ function get_route_handler(chunks: Record, App: Component, route if (process.send) { process.send({ __sapper__: true, + event: 'file', url: req.url, method: req.method, status: 200, @@ -315,6 +328,7 @@ function get_route_handler(chunks: Record, App: Component, route process.send({ __sapper__: true, + event: 'file', url: req.url, method: req.method, status: res.statusCode, diff --git a/test/common/test.js b/test/common/test.js index 2ed5311..19028dc 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -133,6 +133,7 @@ function run({ mode, basepath = '' }) { let capture; let base; + let captured_basepath; const nightmare = new Nightmare(); @@ -179,7 +180,13 @@ function run({ mode, basepath = '' }) { let handler; proc.on('message', message => { - if (message.__sapper__) return; + if (message.__sapper__) { + if (message.event === 'basepath') { + captured_basepath = basepath; + } + return; + } + if (handler) handler(message); }); @@ -597,6 +604,10 @@ function run({ mode, basepath = '' }) { assert.ok(!hasProgressIndicator); }); }); + + it('emits a basepath', () => { + assert.equal(captured_basepath, basepath); + }); }); describe('headers', () => { From 8240595d705a3c99c6aac03c90992a33d90fdec7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 15 Jun 2018 10:58:55 -0400 Subject: [PATCH 03/23] -> v0.13.2 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e3772..8bd6d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.13.2 + +* Emit a `basepath` event ([#284](https://github.com/sveltejs/sapper/pull/284)) + ## 0.13.1 * Reinstate ten-second interval between dev server heartbeats ([#276](https://github.com/sveltejs/sapper/issues/276)) diff --git a/package.json b/package.json index 4d188c4..9454c70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.13.1", + "version": "0.13.2", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.ts.js", "bin": { From f0d7a1aaab156b176232c208a7ee008be2e6fd79 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 16 Jun 2018 13:49:19 -0400 Subject: [PATCH 04/23] change fatal events to be clonable, for IPC purposes --- src/api/dev.ts | 2 +- src/api/interfaces.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/dev.ts b/src/api/dev.ts index 4a20a3c..7f87781 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -90,7 +90,7 @@ class Watcher extends EventEmitter { if (this.port) { if (!await ports.check(this.port)) { this.emit('fatal', { - error: new Error(`Port ${this.port} is unavailable`) + message: `Port ${this.port} is unavailable` }); return; } diff --git a/src/api/interfaces.ts b/src/api/interfaces.ts index 623ae63..b896477 100644 --- a/src/api/interfaces.ts +++ b/src/api/interfaces.ts @@ -7,11 +7,11 @@ export type ReadyEvent = { export type ErrorEvent = { type: string; - error: Error; + message: string; }; export type FatalEvent = { - error: Error; + message: string; }; export type InvalidEvent = { From 8f3454c3b191e88e5518cd00b3d5c8116c2fa315 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 16 Jun 2018 13:49:44 -0400 Subject: [PATCH 05/23] -> v0.13.3 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bd6d8a..26095e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 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)) diff --git a/package.json b/package.json index 9454c70..4a7fcb2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.13.2", + "version": "0.13.3", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.ts.js", "bin": { From 0800fa016b315b2dc9e7d48c872a2bfd66446faa Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 16 Jun 2018 20:18:32 -0400 Subject: [PATCH 06/23] emit a fatal event if server crashes --- src/api/dev.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/api/dev.ts b/src/api/dev.ts index 7f87781..a70bdd6 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -130,6 +130,12 @@ class Watcher extends EventEmitter { // TODO watch the configs themselves? const compilers = create_compilers({ webpack: this.dirs.webpack }); + const emitFatal = () => { + this.emit('fatal', { + message: `Server crashed` + }); + }; + this.watch(compilers.server, { name: 'server', @@ -158,6 +164,7 @@ class Watcher extends EventEmitter { }; if (this.proc) { + this.proc.removeListener('exit', emitFatal); this.proc.kill(); this.proc.on('exit', restart); } else { @@ -172,6 +179,14 @@ class Watcher extends EventEmitter { stdio: ['ipc'] }); + this.proc.stdout.on('data', chunk => { + this.emit('stdout', chunk); + }); + + this.proc.stderr.on('data', chunk => { + this.emit('stderr', chunk); + }); + this.proc.on('message', message => { if (message.__sapper__ && message.event === 'basepath') { this.emit('basepath', { @@ -179,6 +194,8 @@ class Watcher extends EventEmitter { }); } }); + + this.proc.on('exit', emitFatal); }); } }); From e1a33c6a9bc4d0919793199e426bdf259e1de402 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 17 Jun 2018 13:00:27 -0400 Subject: [PATCH 07/23] always refresh client_assets in dev --- src/middleware.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index 1c5ef09..78c49fc 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -56,8 +56,6 @@ export default function middleware({ App, routes, store }: { 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([ @@ -105,7 +103,7 @@ export default function middleware({ App, routes, store }: { cache_control: 'max-age=31536000' }), - get_route_handler(client_assets, App, routes, store) + get_route_handler(App, routes, store) ].filter(Boolean)); return middleware; @@ -148,7 +146,13 @@ function serve({ prefix, pathname, cache_control }: { }; } -function get_route_handler(chunks: Record, App: Component, routes: RouteObject[], store_getter: (req: Req) => Store) { +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'))); + const template = dev() ? () => fs.readFileSync(`${locations.app()}/template.html`, 'utf-8') : (str => () => str)(fs.readFileSync(`${locations.dest()}/template.html`, 'utf-8')); @@ -159,6 +163,8 @@ function get_route_handler(chunks: Record, App: Component, route const handlers = route.handlers[Symbol.iterator](); function next() { + const chunks: Record = get_chunks(); + try { const { value: handler, done } = handlers.next(); @@ -384,7 +390,7 @@ function get_route_handler(chunks: Record, App: Component, route function render_page({ head, css, html }) { const page = template() .replace('%sapper.base%', ``) - .replace('%sapper.scripts%', ``) + .replace('%sapper.scripts%', ``) .replace('%sapper.html%', html) .replace('%sapper.head%', `${head}`) .replace('%sapper.styles%', (css && css.code ? `` : '')); From 1dafe934b0c7ce74a2105849fb58682443d04a71 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 17 Jun 2018 13:01:51 -0400 Subject: [PATCH 08/23] initialise rebuild stats --- src/api/dev.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/dev.ts b/src/api/dev.ts index 7f87781..68b677e 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -250,8 +250,8 @@ class Watcher extends EventEmitter { this.restarting = true; this.current_build = { - changed: new Set(), - rebuilding: new Set(), + changed: new Set([filename]), + rebuilding: new Set([type]), unique_warnings: new Set(), unique_errors: new Set() }; From 47cdc1c4c871b9bb5ff9c6788bf8bf4def75917c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 18 Jun 2018 22:31:10 -0400 Subject: [PATCH 09/23] wait until server restarts before emitting hot reload update --- src/api/dev.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/dev.ts b/src/api/dev.ts index 6c70a71..0b92097 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -148,10 +148,6 @@ class Watcher extends EventEmitter { fs.writeFileSync(path.join(dest, 'server_info.json'), JSON.stringify(info, null, ' ')); this.deferreds.client.promise.then(() => { - this.dev_server.send({ - status: 'completed' - }); - const restart = () => { ports.wait(this.port).then((() => { this.emit('ready', { @@ -160,6 +156,10 @@ class Watcher extends EventEmitter { }); this.deferreds.server.fulfil(); + + this.dev_server.send({ + status: 'completed' + }); })); }; From 0b43eaa992ac209f9f20c14ccf4ed0bd140bd55a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 18 Jun 2018 22:44:55 -0400 Subject: [PATCH 10/23] blur active element on navigation - fixes #287 --- src/runtime/index.ts | 1 + test/common/test.js | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 9575b74..e890bcf 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -161,6 +161,7 @@ function navigate(target: Target, id: number) { } render(Page, props, scroll_history[id], token); + document.activeElement.blur(); }); } diff --git a/test/common/test.js b/test/common/test.js index 19028dc..3c637eb 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -608,6 +608,19 @@ function run({ mode, basepath = '' }) { it('emits a basepath', () => { assert.equal(captured_basepath, basepath); }); + + // skipped because Nightmare doesn't seem to focus the 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', () => { From 611dc4f6be2aefd478e8e941676ad3a83fd79f58 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 18 Jun 2018 22:45:51 -0400 Subject: [PATCH 11/23] -> v0.13.4 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26095e2..8a32ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # sapper changelog +## 0.13.4 + +* Focus `` 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 diff --git a/package.json b/package.json index 4a7fcb2..9e0a0cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.13.3", + "version": "0.13.4", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.ts.js", "bin": { From 5075981a902e9ea7810d068cc3a84cbc15ff6292 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 26 Jun 2018 13:49:11 -0400 Subject: [PATCH 12/23] fix handling of fatal errors - fixes #289 --- src/api/dev.ts | 43 ++++++++++++++++++++++++++++++++----------- src/api/interfaces.ts | 1 + src/cli/dev.ts | 5 +++-- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/api/dev.ts b/src/api/dev.ts index 0b92097..712b302 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -33,6 +33,7 @@ class Watcher extends EventEmitter { server: Deferred; }; + crashed: boolean; restarting: boolean; current_build: { changed: Set; @@ -130,10 +131,16 @@ class Watcher extends EventEmitter { // TODO watch the configs themselves? const compilers = create_compilers({ webpack: this.dirs.webpack }); + let log = ''; + const emitFatal = () => { this.emit('fatal', { - message: `Server crashed` + message: `Server crashed`, + log }); + + this.crashed = true; + this.proc = null; }; this.watch(compilers.server, { @@ -149,18 +156,30 @@ class Watcher extends EventEmitter { this.deferreds.client.promise.then(() => { const restart = () => { - ports.wait(this.port).then((() => { - this.emit('ready', { - port: this.port, - process: this.proc - }); + log = ''; + this.crashed = false; - this.deferreds.server.fulfil(); + ports.wait(this.port) + .then((() => { + this.emit('ready', { + port: this.port, + process: this.proc + }); - this.dev_server.send({ - status: 'completed' + this.deferreds.server.fulfil(); + + this.dev_server.send({ + status: 'completed' + }); + })) + .catch(err => { + if (this.crashed) return; + + this.emit('fatal', { + message: `Server is not listening on port ${this.port}`, + log + }); }); - })); }; if (this.proc) { @@ -180,10 +199,12 @@ class Watcher extends EventEmitter { }); this.proc.stdout.on('data', chunk => { + log += chunk; this.emit('stdout', chunk); }); this.proc.stderr.on('data', chunk => { + log += chunk; this.emit('stderr', chunk); }); @@ -301,7 +322,7 @@ class Watcher extends EventEmitter { if (err) { this.emit('error', { type: name, - error: err + message: err.message }); } else { const messages = format_messages(stats); diff --git a/src/api/interfaces.ts b/src/api/interfaces.ts index b896477..e1ac627 100644 --- a/src/api/interfaces.ts +++ b/src/api/interfaces.ts @@ -12,6 +12,7 @@ export type ErrorEvent = { export type FatalEvent = { message: string; + log?: string; }; export type InvalidEvent = { diff --git a/src/cli/dev.ts b/src/cli/dev.ts index daf1306..cfc3414 100644 --- a/src/cli/dev.ts +++ b/src/cli/dev.ts @@ -36,11 +36,12 @@ export function dev(opts: { port: number, open: boolean }) { watcher.on('error', (event: events.ErrorEvent) => { 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) => { - 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) => { From 31110a53262e910034404c0f022861d6af5bdc82 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 26 Jun 2018 18:30:46 -0400 Subject: [PATCH 13/23] -> v0.13.5 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a32ef7..b8afa0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.13.5 + +* Fix handling of fatal errors ([#289](https://github.com/sveltejs/sapper/issues/289)) + ## 0.13.4 * Focus `` after navigation ([#287](https://github.com/sveltejs/sapper/issues/287)) diff --git a/package.json b/package.json index 9e0a0cb..7a4a0d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.13.4", + "version": "0.13.5", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.ts.js", "bin": { From 087356f781b359230ffd8fdcec1e66a3b3969bb2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 27 Jun 2018 16:57:04 -0400 Subject: [PATCH 14/23] fix req.baseUrl synthesis --- src/middleware.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index 78c49fc..05bb025 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -61,8 +61,13 @@ export default function middleware({ App, routes, store }: { const middleware = compose_handlers([ (req: Req, res: ServerResponse, next: () => void) => { if (req.baseUrl === undefined) { - req.baseUrl = req.originalUrl - ? req.originalUrl.slice(0, -req.url.length) + let { originalUrl } = req; + if (req.url === '/' && originalUrl[originalUrl.length - 1] !== '/') { + originalUrl += '/'; + } + + req.baseUrl = originalUrl + ? originalUrl.slice(0, -req.url.length) : ''; } From f821c19528e02045ba9d7b9f41aa156f46ca98d2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 27 Jun 2018 17:20:45 -0400 Subject: [PATCH 15/23] -> v0.13.6 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8afa0d..8d5d778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.13.6 + +* Fix `baseUrl` synthesis ([#296](https://github.com/sveltejs/sapper/issues/296)) + ## 0.13.5 * Fix handling of fatal errors ([#289](https://github.com/sveltejs/sapper/issues/289)) diff --git a/package.json b/package.json index 7a4a0d6..6f569ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.13.5", + "version": "0.13.6", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.ts.js", "bin": { From 57eeb5659aaa9a0979ffccf552f2dfcbd6edbe9c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 27 Jun 2018 17:32:20 -0400 Subject: [PATCH 16/23] return a Promise from goto - fixes #270 --- src/runtime/index.ts | 8 ++++++-- test/app/routes/about.html | 10 ++++++++-- test/common/test.js | 4 +--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/runtime/index.ts b/src/runtime/index.ts index e890bcf..0c62478 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -133,7 +133,7 @@ function prepare_route(Page: ComponentConstructor, props: RouteData) { }); } -function navigate(target: Target, id: number) { +function navigate(target: Target, id: number): Promise { if (id) { // popstate or initial navigation cid = id; @@ -299,13 +299,17 @@ export function init(opts: { App: ComponentConstructor, target: Node, routes: Ro export function goto(href: string, opts = { replaceState: false }) { const target = select_route(new URL(href, document.baseURI)); + let promise; if (target) { - navigate(target, null); + promise = navigate(target, null); if (history) history[opts.replaceState ? 'replaceState' : 'pushState']({ id: cid }, '', href); } else { window.location.href = href; + promise = new Promise(f => {}); // never resolves } + + return promise; } export function prefetchRoutes(pathnames: string[]) { diff --git a/test/app/routes/about.html b/test/app/routes/about.html index 605e0db..65ebb57 100644 --- a/test/app/routes/about.html +++ b/test/app/routes/about.html @@ -6,15 +6,21 @@

This is the 'about' page. There's not much here.

- `) - .replace('%sapper.html%', html) - .replace('%sapper.head%', `${head}`) - .replace('%sapper.styles%', (css && css.code ? `` : '')); - - res.end(page); - } - - function handle_notfound() { - const title: string = not_found - ? 'Not found' - : `Internal server error: ${error.message}`; - - render_page({ head: '', css: null, html: title }); - } - - if (route) { - const handlers = route.handlers[Symbol.iterator](); - - function next() { - const { value: handler, done } = handlers.next(); - - if (done) { - handle_notfound(); - } else if (handler.type === 'page') { - render_page(handler.module.render({ - status: statusCode, - error - }, { - store: store_getter && store_getter(req) - })); - } else { - const handle_method = mod[method_export]; - if (handle_method) { - handle_method(req, res, next); - } else { - next(); - } - } - } - - next(); - } else { - handle_notfound(); - } - } - return function find_route(req: Req, res: ServerResponse) { for (const route of routes) { if (!route.error && route.pattern.test(req.path)) return handle_route(route, req, res); } - handle_error(req, res, 404, 'Not found'); + handle_route(not_found_route, req, res, 404, 'Not found'); }; } From e87247493f1b865aaffe143a3df82f9836421563 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 28 Jun 2018 11:38:21 -0400 Subject: [PATCH 18/23] replace 4xx.html and 5xx.html with _error.html --- src/core/create_manifests.ts | 8 ++++---- src/core/create_routes.ts | 8 +++----- src/middleware.ts | 23 +++++++++++++++++------ src/runtime/index.ts | 13 +++---------- src/runtime/interfaces.ts | 2 +- test/app/routes/5xx.html | 6 ------ test/app/routes/{4xx.html => _error.html} | 2 +- test/app/routes/blog/[slug].html | 2 +- test/common/test.js | 8 ++++---- test/unit/create_routes.test.js | 12 ++++-------- 10 files changed, 38 insertions(+), 46 deletions(-) delete mode 100644 test/app/routes/5xx.html rename test/app/routes/{4xx.html => _error.html} (80%) diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index 1135a8c..58f3075 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -52,8 +52,8 @@ function generate_client(routes: Route[], path_to_routes: string, dev_port?: num const file = posixify(`${path_to_routes}/${page.file}`); - if (route.id === '_4xx' || route.id === '_5xx') { - return `{ error: '${route.id.slice(1)}', load: () => import(/* webpackChunkName: "${route.id}" */ '${file}') }`; + if (route.id === '_error') { + return `{ error: true, load: () => import(/* webpackChunkName: "${route.id}" */ '${file}') }`; } const params = route.params.length === 0 @@ -107,8 +107,8 @@ function generate_server(routes: Route[], path_to_routes: string) { `{ type: '${type}', module: ${route.id}${index} }`) .join(', '); - if (route.id === '_4xx' || route.id === '_5xx') { - return `{ error: '${route.id.slice(1)}', handlers: [${handlers}] }`; + if (route.id === '_error') { + return `{ error: true, handlers: [${handlers}] }`; } const params = route.params.length === 0 diff --git a/src/core/create_routes.ts b/src/core/create_routes.ts index a6239ff..4515076 100644 --- a/src/core/create_routes.ts +++ b/src/core/create_routes.ts @@ -5,10 +5,8 @@ import { Route } from '../interfaces'; export default function create_routes({ files } = { files: glob.sync('**/*.*', { cwd: locations.routes(), dot: true, nodir: true }) }) { const routes: Route[] = files - .filter((file: string) => !/(^|\/|\\)_/.test(file)) + .filter((file: string) => !/(^|\/|\\)(_(?!error\.html)|\.(?!well-known))/.test(file)) .map((file: string) => { - if (/(^|\/|\\)(_|\.(?!well-known))/.test(file)) return; - if (/]\[/.test(file)) { throw new Error(`Invalid route ${file} — parameters must be separated`); } @@ -30,8 +28,8 @@ export default function create_routes({ files } = { files: glob.sync('**/*.*', { return !found; }) .sort((a, b) => { - if (a.parts[0] === '4xx' || a.parts[0] === '5xx') return -1; - if (b.parts[0] === '4xx' || b.parts[0] === '5xx') return 1; + if (a.parts[0] === '_error') return -1; + if (b.parts[0] === '_error') return 1; const max = Math.max(a.parts.length, b.parts.length); diff --git a/src/middleware.ts b/src/middleware.ts index 49720c0..0ca1a99 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -162,8 +162,7 @@ function get_route_handler(App: Component, routes: RouteObject[], store_getter: ? () => fs.readFileSync(`${locations.app()}/template.html`, 'utf-8') : (str => () => str)(fs.readFileSync(`${locations.dest()}/template.html`, 'utf-8')); - const not_found_route = routes.find((route: RouteObject) => route.error === '4xx'); - const error_route = routes.find((route: RouteObject) => route.error === '5xx'); + const error_route = routes.find((route: RouteObject) => route.error); function handle_route(route: RouteObject, req: Req, res: ServerResponse, status = 200, error: Error | string = null) { req.params = error @@ -184,7 +183,7 @@ function get_route_handler(App: Component, routes: RouteObject[], store_getter: res.statusCode = status; res.end(error instanceof Error ? error.message : error); } else { - handle_route(not_found_route, req, res, 404, 'Not found'); + handle_route(error_route, req, res, 404, 'Not found'); } return; @@ -198,7 +197,7 @@ function get_route_handler(App: Component, routes: RouteObject[], store_getter: // preload main.js and current route // TODO detect other stuff we can preload? images, CSS, fonts? const link = [] - .concat(chunks.main, chunks[route.id] || chunks[`_${route.error}`]) // TODO this is gross + .concat(chunks.main, chunks[route.id] || chunks._error) // TODO this is gross .filter(file => !file.match(/\.map$/)) .map(file => `<${req.baseUrl}/client/${file}>;rel="preload";as="script"`) .join(', '); @@ -208,6 +207,11 @@ function get_route_handler(App: Component, routes: RouteObject[], store_getter: const store = store_getter ? store_getter(req) : null; const props = { params: req.params, query: req.query, path: req.path }; + if (route.error) { + props.error = error instanceof Error ? error : { message: error }; + props.status = status; + } + let redirect: { statusCode: number, location: string }; let preload_error: { statusCode: number, message: Error | string }; @@ -306,6 +310,7 @@ function get_route_handler(App: Component, routes: RouteObject[], store_getter: .replace('%sapper.head%', `${head}`) .replace('%sapper.styles%', (css && css.code ? `` : '')); + res.statusCode = status; res.end(page); if (process.send) { @@ -382,7 +387,13 @@ function get_route_handler(App: Component, routes: RouteObject[], store_getter: } } } catch (error) { - handle_route(error_route, req, res, 500, error || 'Internal server error'); + if (route.error) { + // there was an error rendering the error page! + res.statusCode = status; + res.end(error instanceof Error ? error.message : error); + } else { + handle_route(error_route, req, res, 500, error || 'Internal server error'); + } } } @@ -394,7 +405,7 @@ function get_route_handler(App: Component, routes: RouteObject[], store_getter: if (!route.error && route.pattern.test(req.path)) return handle_route(route, req, res); } - handle_route(not_found_route, req, res, 404, 'Not found'); + handle_route(error_route, req, res, 404, 'Not found'); }; } diff --git a/src/runtime/index.ts b/src/runtime/index.ts index e890bcf..5185385 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -8,7 +8,7 @@ export let component: Component; let target: Node; let store: Store; let routes: Route[]; -let errors: { '4xx': Route, '5xx': Route }; +let error_route: Route; const history = typeof window !== 'undefined' ? window.history : { pushState: (state: any, title: string, href: string) => {}, @@ -117,11 +117,7 @@ function prepare_route(Page: ComponentConstructor, props: RouteData) { error = { statusCode: 500, message: err }; }).then(preloaded => { if (error) { - const route = error.statusCode >= 400 && error.statusCode < 500 - ? errors['4xx'] - : errors['5xx']; - - return route.load().then(({ default: Page }: { default: ComponentConstructor }) => { + return error_route.load().then(({ default: Page }: { default: ComponentConstructor }) => { const err = error.message instanceof Error ? error.message : new Error(error.message); Object.assign(props, { status: error.statusCode, error: err }); return { Page, props, redirect: null }; @@ -262,10 +258,7 @@ export function init(opts: { App: ComponentConstructor, target: Node, routes: Ro App = opts.App; target = opts.target; routes = opts.routes.filter(r => !r.error); - errors = { - '4xx': opts.routes.find(r => r.error === '4xx'), - '5xx': opts.routes.find(r => r.error === '5xx') - }; + error_route = opts.routes.find(r => r.error); if (opts && opts.store) { store = opts.store(manifest.store); diff --git a/src/runtime/interfaces.ts b/src/runtime/interfaces.ts index d141039..00f814c 100644 --- a/src/runtime/interfaces.ts +++ b/src/runtime/interfaces.ts @@ -17,7 +17,7 @@ export interface Component { export type Route = { pattern: RegExp; load: () => Promise<{ default: ComponentConstructor }>; - error?: string; + error?: boolean; params?: (match: RegExpExecArray) => Record; ignore?: boolean; }; diff --git a/test/app/routes/5xx.html b/test/app/routes/5xx.html deleted file mode 100644 index 1425e2a..0000000 --- a/test/app/routes/5xx.html +++ /dev/null @@ -1,6 +0,0 @@ - - Internal server error - - -

Internal server error

-

{error.message}

\ No newline at end of file diff --git a/test/app/routes/4xx.html b/test/app/routes/_error.html similarity index 80% rename from test/app/routes/4xx.html rename to test/app/routes/_error.html index c63ca84..9974a17 100644 --- a/test/app/routes/4xx.html +++ b/test/app/routes/_error.html @@ -2,5 +2,5 @@ {status} -

Not found

+

{status}

{error.message}

\ No newline at end of file diff --git a/test/app/routes/blog/[slug].html b/test/app/routes/blog/[slug].html index 7fdc8b4..7836fc7 100644 --- a/test/app/routes/blog/[slug].html +++ b/test/app/routes/blog/[slug].html @@ -19,7 +19,7 @@ return this.error(500, 'something went wrong'); } - return fetch(`blog/${slug}.json`).then(r => { + return fetch(`blog/${slug}.json`).then(async r => { if (r.status === 200) { return r.json().then(post => ({ post })); this.error(r.status, '') diff --git a/test/common/test.js b/test/common/test.js index 3c637eb..84fdd17 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -441,7 +441,7 @@ function run({ mode, basepath = '' }) { }) .then(() => nightmare.page.title()) .then(title => { - assert.equal(title, 'Not found') + assert.equal(title, '404') }); }); @@ -456,7 +456,7 @@ function run({ mode, basepath = '' }) { }) .then(() => nightmare.page.title()) .then(title => { - assert.equal(title, 'Not found'); + assert.equal(title, '404'); }); }); @@ -468,7 +468,7 @@ function run({ mode, basepath = '' }) { }) .then(() => nightmare.page.title()) .then(title => { - assert.equal(title, 'Internal server error') + assert.equal(title, '500') }); }); @@ -483,7 +483,7 @@ function run({ mode, basepath = '' }) { }) .then(() => nightmare.page.title()) .then(title => { - assert.equal(title, 'Internal server error'); + assert.equal(title, '500'); }); }); diff --git a/test/unit/create_routes.test.js b/test/unit/create_routes.test.js index e4e33f6..f299464 100644 --- a/test/unit/create_routes.test.js +++ b/test/unit/create_routes.test.js @@ -71,8 +71,7 @@ describe('create_routes', () => { 'blog/[slug].html', 'api/gists/[id].js', 'api/gists/index.js', - '4xx.html', - '5xx.html', + '_error.html', 'blog/index.html', 'blog/rss.xml.js', 'guide/index.html', @@ -83,8 +82,7 @@ describe('create_routes', () => { assert.deepEqual( routes.map(r => r.handlers[0].file), [ - '4xx.html', - '5xx.html', + '_error.html', 'index.html', 'guide/index.html', 'blog/index.html', @@ -99,8 +97,7 @@ describe('create_routes', () => { routes = create_routes({ files: [ - '4xx.html', - '5xx.html', + '_error.html', 'api/blog/[slug].js', 'api/blog/index.js', 'api/guide/contents.js', @@ -119,8 +116,7 @@ describe('create_routes', () => { assert.deepEqual( routes.map(r => r.handlers[0].file), [ - '4xx.html', - '5xx.html', + '_error.html', 'index.html', 'guide/index.html', 'blog/index.html', From 5c4e4d5d36e2a2de128efc27e3eb01bf3ebcf31b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 28 Jun 2018 11:45:38 -0400 Subject: [PATCH 19/23] only navigate if route is found - fixes #279 --- src/runtime/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 5185385..b2308f6 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -208,7 +208,11 @@ function handle_popstate(event: PopStateEvent) { if (event.state) { const url = new URL(window.location.href); const target = select_route(url); - navigate(target, event.state.id); + if (target) { + navigate(target, event.state.id); + } else { + window.location.href = window.location.href; + } } else { // hashchange cid = ++uid; @@ -286,7 +290,7 @@ export function init(opts: { App: ComponentConstructor, target: Node, routes: Ro history.replaceState({ id: uid }, '', href); const target = select_route(new URL(window.location.href)); - return navigate(target, uid); + if (target) return navigate(target, uid); }); } From 2205b8aec53aff53ccdfdca4e3d31e431ad8cb75 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 28 Jun 2018 12:03:27 -0400 Subject: [PATCH 20/23] oops --- test/app/routes/blog/[slug].html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/app/routes/blog/[slug].html b/test/app/routes/blog/[slug].html index 7836fc7..7fdc8b4 100644 --- a/test/app/routes/blog/[slug].html +++ b/test/app/routes/blog/[slug].html @@ -19,7 +19,7 @@ return this.error(500, 'something went wrong'); } - return fetch(`blog/${slug}.json`).then(async r => { + return fetch(`blog/${slug}.json`).then(r => { if (r.status === 200) { return r.json().then(post => ({ post })); this.error(r.status, '') From 5b5f33d3cf484abab01573520ddbe881beca2669 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 28 Jun 2018 12:57:59 -0400 Subject: [PATCH 21/23] error on 4xx.html and 5xx.html --- src/core/create_routes.ts | 4 ++++ test/unit/create_routes.test.js | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/core/create_routes.ts b/src/core/create_routes.ts index 4515076..0b6c3e8 100644 --- a/src/core/create_routes.ts +++ b/src/core/create_routes.ts @@ -11,6 +11,10 @@ export default function create_routes({ files } = { files: glob.sync('**/*.*', { throw new Error(`Invalid route ${file} — parameters must be separated`); } + if (file === '4xx.html' || file === '5xx.html') { + throw new Error('As of Sapper 0.14, 4xx.html and 5xx.html should be replaced with _error.html'); + } + const base = file.replace(/\.[^/.]+$/, ''); const parts = base.split('/'); // glob output is always posix-style if (parts[parts.length - 1] === 'index') parts.pop(); diff --git a/test/unit/create_routes.test.js b/test/unit/create_routes.test.js index f299464..95eb116 100644 --- a/test/unit/create_routes.test.js +++ b/test/unit/create_routes.test.js @@ -280,4 +280,20 @@ describe('create_routes', () => { }); }, /Invalid route \[foo\]\[bar\]\.js — parameters must be separated/); }); + + it('errors on 4xx.html', () => { + assert.throws(() => { + create_routes({ + files: ['4xx.html'] + }); + }, /As of Sapper 0.14, 4xx.html and 5xx.html should be replaced with _error.html/); + }); + + it('errors on 5xx.html', () => { + assert.throws(() => { + create_routes({ + files: ['5xx.html'] + }); + }, /As of Sapper 0.14, 4xx.html and 5xx.html should be replaced with _error.html/); + }); }); \ No newline at end of file From b7bb4db8c15d8c64f0e36c31ba65d45ba0b1f707 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 28 Jun 2018 13:20:41 -0400 Subject: [PATCH 22/23] treat foo/index.json.js as foo.json.js - fixes #297 --- src/core/create_routes.ts | 5 ++++- test/app/routes/{blog.json.js => blog/index.json.js} | 2 +- test/unit/create_routes.test.js | 8 ++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) rename test/app/routes/{blog.json.js => blog/index.json.js} (88%) diff --git a/src/core/create_routes.ts b/src/core/create_routes.ts index 0b6c3e8..fd02d88 100644 --- a/src/core/create_routes.ts +++ b/src/core/create_routes.ts @@ -17,7 +17,10 @@ export default function create_routes({ files } = { files: glob.sync('**/*.*', { const base = file.replace(/\.[^/.]+$/, ''); const parts = base.split('/'); // glob output is always posix-style - if (parts[parts.length - 1] === 'index') parts.pop(); + if (/^index(\..+)?/.test(parts[parts.length - 1])) { + const part = parts.pop(); + if (parts.length > 0) parts[parts.length - 1] += part.slice(5); + } return { files: [file], diff --git a/test/app/routes/blog.json.js b/test/app/routes/blog/index.json.js similarity index 88% rename from test/app/routes/blog.json.js rename to test/app/routes/blog/index.json.js index 9dd74e0..9d73d78 100644 --- a/test/app/routes/blog.json.js +++ b/test/app/routes/blog/index.json.js @@ -1,4 +1,4 @@ -import posts from './blog/_posts.js'; +import posts from './_posts.js'; const contents = JSON.stringify(posts.map(post => { return { diff --git a/test/unit/create_routes.test.js b/test/unit/create_routes.test.js index 95eb116..cae5dc2 100644 --- a/test/unit/create_routes.test.js +++ b/test/unit/create_routes.test.js @@ -296,4 +296,12 @@ describe('create_routes', () => { }); }, /As of Sapper 0.14, 4xx.html and 5xx.html should be replaced with _error.html/); }); + + it('treats foo/index.json.js the same as foo.json.js', () => { + const route = create_routes({ + files: ['foo/index.json.js'] + })[0]; + + assert.ok(route.test('/foo.json')); + }); }); \ No newline at end of file From 09b4dc1b9a59e3e40e168a519787e9da286e4b9f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 28 Jun 2018 13:35:21 -0400 Subject: [PATCH 23/23] -> v0.14.0 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d5d778..bf64274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # sapper changelog +## 0.14.0 + +* `4xx.html` and `5xx.html` are replaced with `_error.html` ([#209](https://github.com/sveltejs/sapper/issues/209)) +* Treat `foo/index.json.js` and `foo.json.js` as equivalents ([#297](https://github.com/sveltejs/sapper/issues/297)) +* Return a promise from `goto` ([#270](https://github.com/sveltejs/sapper/issues/270)) +* Use store when rendering error pages ([#293](https://github.com/sveltejs/sapper/issues/293)) +* Prevent console errors when visiting an error page ([#279](https://github.com/sveltejs/sapper/issues/279)) + ## 0.13.6 * Fix `baseUrl` synthesis ([#296](https://github.com/sveltejs/sapper/issues/296)) diff --git a/package.json b/package.json index 6f569ab..d97f372 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.13.6", + "version": "0.14.0", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.ts.js", "bin": {