From 8abc01551ef6e99da05af141cad8af193d3c8228 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 Aug 2018 14:25:48 -0400 Subject: [PATCH] export should fail on timeouts --- src/api/export.ts | 57 ++++++++++++++++++++++++++++++----------------- src/cli.ts | 10 +++++++-- src/cli/export.ts | 11 +++++++-- 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/api/export.ts b/src/api/export.ts index 0191383f3..9e80ac5c5 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -10,7 +10,14 @@ import minify_html from './utils/minify_html'; import Deferred from './utils/Deferred'; import * as events from './interfaces'; -export function exporter(opts: {}) { +type Opts = { + build: string, + dest: string, + basepath: string, + timeout: number | false +}; + +export function exporter(opts: Opts) { const emitter = new EventEmitter(); execute(emitter, opts).then( @@ -27,42 +34,38 @@ export function exporter(opts: {}) { return emitter; } -async function execute(emitter: EventEmitter, { - build = 'build', - dest = 'export', - basepath = '' -} = {}) { - const export_dir = path.join(dest, basepath); +async function execute(emitter: EventEmitter, opts: Opts) { + const export_dir = path.join(opts.dest, opts.basepath); // Prep output directory sander.rimrafSync(export_dir); sander.copydirSync('assets').to(export_dir); - sander.copydirSync(build, 'client').to(export_dir, 'client'); + sander.copydirSync(opts.build, 'client').to(export_dir, 'client'); - if (sander.existsSync(build, 'service-worker.js')) { - sander.copyFileSync(build, 'service-worker.js').to(export_dir, 'service-worker.js'); + if (sander.existsSync(opts.build, 'service-worker.js')) { + sander.copyFileSync(opts.build, 'service-worker.js').to(export_dir, 'service-worker.js'); } - if (sander.existsSync(build, 'service-worker.js.map')) { - sander.copyFileSync(build, 'service-worker.js.map').to(export_dir, 'service-worker.js.map'); + if (sander.existsSync(opts.build, 'service-worker.js.map')) { + sander.copyFileSync(opts.build, 'service-worker.js.map').to(export_dir, 'service-worker.js.map'); } const port = await ports.find(3000); const origin = `http://localhost:${port}`; - const root = new URL(basepath || '', origin); + const root = new URL(opts.basepath || '', origin); emitter.emit('info', { message: `Crawling ${root.href}` }); - const proc = child_process.fork(path.resolve(`${build}/server.js`), [], { + const proc = child_process.fork(path.resolve(`${opts.build}/server.js`), [], { cwd: process.cwd(), env: Object.assign({ PORT: port, NODE_ENV: 'production', - SAPPER_DEST: build, + SAPPER_DEST: opts.build, SAPPER_EXPORT: 'true' }, process.env) }); @@ -117,7 +120,18 @@ async function execute(emitter: EventEmitter, { const deferred = get_deferred(pathname); - const r = await fetch(url.href); + const timeout_deferred = new Deferred(); + const timeout = setTimeout(() => { + timeout_deferred.reject(new Error(`Timed out waiting for ${url.href}`)); + }, opts.timeout); + + const r = await Promise.race([ + fetch(url.href), + timeout_deferred.promise + ]); + + clearTimeout(timeout); // prevent it hanging at the end + const range = ~~(r.status / 100); if (range === 2) { @@ -152,11 +166,12 @@ async function execute(emitter: EventEmitter, { } return ports.wait(port) - .then(() => { - // TODO all static routes - return handle(root); - }) - .then(() => proc.kill()); + .then(() => handle(root)) + .then(() => proc.kill()) + .catch(err => { + proc.kill(); + throw err; + }); } function get_href(attrs: string) { diff --git a/src/cli.ts b/src/cli.ts index 303f5d178..289bffadb 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -65,7 +65,13 @@ prog.command('export [dest]') .option('--build', '(Re)build app before exporting', true) .option('--build-dir', 'Specify a custom temporary build directory', '.sapper/prod') .option('--basepath', 'Specify a base path') - .action(async (dest = 'export', opts: { build: boolean, 'build-dir': string, basepath?: string }) => { + .option('--timeout', 'Milliseconds to wait for a page (--no-timeout to disable)', 5000) + .action(async (dest = 'export', opts: { + build: boolean, + 'build-dir': string, + basepath?: string, + timeout: number | false + }) => { process.env.NODE_ENV = 'production'; process.env.SAPPER_DEST = opts['build-dir']; @@ -83,7 +89,7 @@ prog.command('export [dest]') await exporter(dest, opts); console.error(`\n> Finished in ${elapsed(start)}. Type ${colors.bold.cyan(`npx serve ${dest}`)} to run the app.`); } catch (err) { - console.error(err ? err.details || err.stack || err.message || err : 'Unknown error'); + console.error(colors.bold.red(`> ${err.message}`)); process.exit(1); } }); diff --git a/src/cli/export.ts b/src/cli/export.ts index 0a7737aab..04aa0f967 100644 --- a/src/cli/export.ts +++ b/src/cli/export.ts @@ -8,13 +8,20 @@ function left_pad(str: string, len: number) { return str; } -export function exporter(export_dir: string, { basepath = '' }) { +export function exporter(export_dir: string, { + basepath = '', + timeout +}: { + basepath: string, + timeout: number | false +}) { return new Promise((fulfil, reject) => { try { const emitter = _exporter({ build: locations.dest(), dest: export_dir, - basepath + basepath, + timeout }); emitter.on('file', event => {