From 32f4a50f25e139676f53ad19de9b3f2760d02203 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Aug 2018 11:09:02 -0400 Subject: [PATCH 001/178] show which file is causing an error/warning --- src/api/interfaces.ts | 4 ++-- src/cli/dev.ts | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/api/interfaces.ts b/src/api/interfaces.ts index e1ac627..381877a 100644 --- a/src/api/interfaces.ts +++ b/src/api/interfaces.ts @@ -26,8 +26,8 @@ export type InvalidEvent = { export type BuildEvent = { type: string; - errors: Array<{ message: string, duplicate: boolean }>; - warnings: Array<{ message: string, duplicate: boolean }>; + errors: Array<{ file: string, message: string, duplicate: boolean }>; + warnings: Array<{ file: string, message: string, duplicate: boolean }>; duration: number; webpack_stats: any; } diff --git a/src/cli/dev.ts b/src/cli/dev.ts index 17eab11..afbbc2f 100644 --- a/src/cli/dev.ts +++ b/src/cli/dev.ts @@ -13,7 +13,7 @@ export function dev(opts: { port: number, open: boolean }) { watcher.on('ready', (event: events.ReadyEvent) => { if (first) { - console.log(`${colors.bold.cyan(`> Listening on http://localhost:${event.port}`)}`); + console.log(colors.bold.cyan(`> Listening on http://localhost:${event.port}`)); if (opts.open) child_process.exec(`open http://localhost:${event.port}`); first = false; } @@ -35,20 +35,21 @@ 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.message)}`); + console.log(colors.red(`✗ ${event.type}`)); + console.log(colors.red(event.message)); }); watcher.on('fatal', (event: events.FatalEvent) => { - console.log(`${colors.bold.red(`> ${event.message}`)}`); + console.log(colors.bold.red(`> ${event.message}`)); if (event.log) console.log(event.log); }); watcher.on('build', (event: events.BuildEvent) => { if (event.errors.length) { - console.log(`${colors.bold.red(`✗ ${event.type}`)}`); + console.log(colors.bold.red(`✗ ${event.type}`)); event.errors.filter(e => !e.duplicate).forEach(error => { + if (error.file) console.log(colors.bold(error.file)); console.log(error.message); }); @@ -57,9 +58,10 @@ export function dev(opts: { port: number, open: boolean }) { console.log(`${hidden} duplicate ${hidden === 1 ? 'error' : 'errors'} hidden\n`); } } else if (event.warnings.length) { - console.log(`${colors.bold.yellow(`• ${event.type}`)}`); + console.log(colors.bold.yellow(`• ${event.type}`)); event.warnings.filter(e => !e.duplicate).forEach(warning => { + if (warning.file) console.log(colors.bold(warning.file)); console.log(warning.message); }); @@ -72,7 +74,7 @@ export function dev(opts: { port: number, open: boolean }) { } }); } catch (err) { - console.log(`${colors.bold.red(`> ${err.message}`)}`); + console.log(colors.bold.red(`> ${err.message}`)); process.exit(1); } } \ No newline at end of file From 62b8a79e9f1fa48b2fc42573077b6c6a34ac2812 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 Aug 2018 11:23:40 -0400 Subject: [PATCH 002/178] -> v0.17.1 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8717841..5173907 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.17.1 + +* Print which file is causing build errors/warnings ([#371](https://github.com/sveltejs/sapper/pull/371)) + ## 0.17.0 * Use `cheap-watch` instead of `chokidar` ([#364](https://github.com/sveltejs/sapper/issues/364)) diff --git a/package.json b/package.json index 6cbb85d..7ecf9ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.17.0", + "version": "0.17.1", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.ts.js", "bin": { From 8abc01551ef6e99da05af141cad8af193d3c8228 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 Aug 2018 14:25:48 -0400 Subject: [PATCH 003/178] 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 0191383..9e80ac5 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 303f5d1..289bffa 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 0a7737a..04aa0f9 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 => { From 24f2855f8945597b3edf99e87509ba2fe4771b10 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 25 Aug 2018 09:11:45 -0400 Subject: [PATCH 004/178] create a facade over webpack, to support alternative compilers --- src/api/build.ts | 32 +++-------- src/api/dev.ts | 14 +++-- src/cli/build.ts | 3 +- src/core/create_compilers.ts | 105 +++++++++++++++++++++++++++++------ 4 files changed, 104 insertions(+), 50 deletions(-) diff --git a/src/api/build.ts b/src/api/build.ts index 2330a8e..0dce116 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -5,6 +5,7 @@ import rimraf from 'rimraf'; import { EventEmitter } from 'events'; import minify_html from './utils/minify_html'; import { create_compilers, create_main_manifests, create_routes, create_serviceworker_manifest } from '../core'; +import { Compilers, Compiler } from '../core/create_compilers'; import * as events from './interfaces'; export function build(opts: {}) { @@ -28,6 +29,7 @@ async function execute(emitter: EventEmitter, { dest = 'build', app = 'app', webpack = 'webpack', + rollup = 'rollup', routes = 'routes' } = {}) { mkdirp.sync(dest); @@ -51,9 +53,9 @@ async function execute(emitter: EventEmitter, { // create app/manifest/client.js and app/manifest/server.js create_main_manifests({ routes: route_objects }); - const { client, server, serviceworker } = create_compilers({ webpack }); + const { client, server, serviceworker } = create_compilers({ webpack, rollup }); - const client_stats = await compile(client); + const client_stats = await client.compile(); emitter.emit('build', { type: 'client', // TODO duration/warnings @@ -63,7 +65,7 @@ async function execute(emitter: EventEmitter, { const client_info = client_stats.toJson(); fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(client_info.assetsByChunkName)); - const server_stats = await compile(server); + const server_stats = await server.compile(); emitter.emit('build', { type: 'server', // TODO duration/warnings @@ -78,7 +80,7 @@ async function execute(emitter: EventEmitter, { client_files: client_stats.toJson().assets.map((chunk: { name: string }) => `client/${chunk.name}`) }); - serviceworker_stats = await compile(serviceworker); + serviceworker_stats = await serviceworker.compile(); emitter.emit('build', { type: 'serviceworker', @@ -86,24 +88,4 @@ async function execute(emitter: EventEmitter, { webpack_stats: serviceworker_stats }); } -} - -function compile(compiler: any) { - return new Promise((fulfil, reject) => { - compiler.run((err: Error, stats: any) => { - if (err) { - reject(err); - process.exit(1); - } - - if (stats.hasErrors()) { - console.error(stats.toString({ colors: true })); - reject(new Error(`Encountered errors while building app`)); - } - - else { - fulfil(stats); - } - }); - }); -} +} \ No newline at end of file diff --git a/src/api/dev.ts b/src/api/dev.ts index 68711c3..1352744 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -9,6 +9,7 @@ import format_messages from 'webpack-format-messages'; import { locations } from '../config'; import { EventEmitter } from 'events'; import { create_routes, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core'; +import { Compiler, Compilers } from '../core/create_compilers'; import Deferred from './utils/Deferred'; import * as events from './interfaces'; @@ -155,7 +156,10 @@ class Watcher extends EventEmitter { }; // TODO watch the configs themselves? - const compilers = create_compilers({ webpack: this.dirs.webpack }); + const compilers: Compilers = create_compilers({ + webpack: this.dirs.webpack, + rollup: this.dirs.rollup + }); let log = ''; @@ -332,16 +336,14 @@ class Watcher extends EventEmitter { } } - watch(compiler: any, { name, invalid = noop, result }: { + watch(compiler: Compiler, { name, invalid = noop, result }: { name: string, invalid?: (filename: string) => void; result: (stats: any) => void; }) { - compiler.hooks.invalid.tap('sapper', (filename: string) => { - invalid(filename); - }); + compiler.oninvalid(invalid); - compiler.watch({}, (err: Error, stats: any) => { + compiler.watch((err: Error, stats: any) => { if (err) { this.emit('error', { type: name, diff --git a/src/cli/build.ts b/src/cli/build.ts index b7bf87b..53f3002 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -9,7 +9,8 @@ export function build() { dest: locations.dest(), app: locations.app(), routes: locations.routes(), - webpack: 'webpack' + webpack: 'webpack', + rollup: 'rollup' }); emitter.on('build', event => { diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index 51a866d..a6794b9 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -1,29 +1,98 @@ +import * as fs from 'fs'; import * as path from 'path'; import relative from 'require-relative'; -export default function create_compilers({ webpack }: { webpack: string }) { - const wp = relative('webpack', process.cwd()); +let r: any; +let wp: any; - const serviceworker_config = try_require(path.resolve(`${webpack}/service-worker.config.js`)); +export class WebpackCompiler { + _: any; - return { - client: wp( - require(path.resolve(`${webpack}/client.config.js`)) - ), + constructor(config: any) { + this._ = wp(require(path.resolve(config))); + } - server: wp( - require(path.resolve(`${webpack}/server.config.js`)) - ), + oninvalid(cb: (filename: string) => void) { + this._.hooks.invalid.tap('sapper', cb); + } - serviceworker: serviceworker_config && wp(serviceworker_config) - }; + compile() { + return new Promise((fulfil, reject) => { + this._.run((err: Error, stats: any) => { + if (err) { + reject(err); + process.exit(1); + } + + if (stats.hasErrors()) { + console.error(stats.toString({ colors: true })); + reject(new Error(`Encountered errors while building app`)); + } + + else { + fulfil(stats); + } + }); + }); + } + + watch(cb: (err: Error, stats: any) => void) { + this._.watch({}, cb); + } } -function try_require(specifier: string) { - try { - return require(specifier); - } catch (err) { - if (err.code === 'MODULE_NOT_FOUND') return null; - throw err; +export class RollupCompiler { + constructor(config: any) { + } + + oninvalid(cb: (filename: string) => void) { + + } + + compile() { + return new Promise((fulfil, reject) => { + + }); + } + + watch(cb: (err: Error, stats: any) => void) { + + } +} + +export type Compiler = RollupCompiler | WebpackCompiler; + +export type Compilers = { + client: Compiler; + server: Compiler; + serviceworker?: Compiler; +} + +export default function create_compilers({ webpack, rollup }: { webpack: string, rollup: string }): Compilers { + if (fs.existsSync(rollup)) { + if (!r) r = relative('rollup', process.cwd()); + + const sw = `${rollup}/service-worker.config.js`; + + return { + client: new RollupCompiler(`${rollup}/client.config.js`), + server: new RollupCompiler(`${rollup}/server.config.js`), + serviceworker: fs.existsSync(sw) && new RollupCompiler(sw) + }; + } + + if (fs.existsSync(webpack)) { + if (!wp) wp = relative('webpack', process.cwd()); + + const sw = `${webpack}/service-worker.config.js`; + + return { + client: new WebpackCompiler(`${webpack}/client.config.js`), + server: new WebpackCompiler(`${webpack}/server.config.js`), + serviceworker: fs.existsSync(sw) && new WebpackCompiler(sw) + }; + } + + throw new Error(`Could not find config files for rollup or webpack`); } \ No newline at end of file From 3d39836cfb9f7cfb29f262d8985e9bc518669d50 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 25 Aug 2018 09:12:06 -0400 Subject: [PATCH 005/178] prevent deprecation warnings --- src/middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index cd3bf94..3233061 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -217,7 +217,7 @@ function get_server_route_handler(routes: ServerRoute[]) { // intercept data so that it can be exported res.write = function(chunk: any) { - chunks.push(new Buffer(chunk)); + chunks.push(Buffer.from(chunk)); write.apply(res, arguments); }; @@ -227,7 +227,7 @@ function get_server_route_handler(routes: ServerRoute[]) { }; res.end = function(chunk?: any) { - if (chunk) chunks.push(new Buffer(chunk)); + if (chunk) chunks.push(Buffer.from(chunk)); end.apply(res, arguments); process.send({ From 9cbb8bdc33eb0edfaca5a1f37a99ecbca77619ae Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 25 Aug 2018 12:42:27 -0400 Subject: [PATCH 006/178] first stab at supporting Rollup (#130) --- rollup.config.js | 1 + rollup.js | 1 + sapper-dev-client.js | 2 + src/api/build.ts | 13 +- src/api/dev.ts | 83 +++---------- src/api/interfaces.ts | 3 +- src/cli/build.ts | 2 +- src/core/create_compilers.ts | 222 +++++++++++++++++++++++++++++++---- src/core/create_manifests.ts | 8 +- src/middleware.ts | 4 +- src/rollup.ts | 44 +++++++ 11 files changed, 276 insertions(+), 107 deletions(-) create mode 100644 rollup.js create mode 100644 src/rollup.ts diff --git a/rollup.config.js b/rollup.config.js index 51f1499..17c3bc7 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -32,6 +32,7 @@ export default [ `src/cli.ts`, `src/core.ts`, `src/middleware.ts`, + `src/rollup.ts`, `src/webpack.ts` ], output: { diff --git a/rollup.js b/rollup.js new file mode 100644 index 0000000..b37e9ab --- /dev/null +++ b/rollup.js @@ -0,0 +1 @@ +module.exports = require('./dist/rollup.ts.js'); \ No newline at end of file diff --git a/sapper-dev-client.js b/sapper-dev-client.js index b717a25..e5c0035 100644 --- a/sapper-dev-client.js +++ b/sapper-dev-client.js @@ -1,6 +1,8 @@ let source; function check() { + if (typeof module === 'undefined') return; + if (module.hot.status() === 'idle') { module.hot.check(true).then(modules => { console.log(`[SAPPER] applied HMR update`); diff --git a/src/api/build.ts b/src/api/build.ts index 0dce116..5f43d18 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -55,21 +55,20 @@ async function execute(emitter: EventEmitter, { const { client, server, serviceworker } = create_compilers({ webpack, rollup }); - const client_stats = await client.compile(); + const client_result = await client.compile(); emitter.emit('build', { type: 'client', // TODO duration/warnings - webpack_stats: client_stats + result: client_result }); - const client_info = client_stats.toJson(); - fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(client_info.assetsByChunkName)); + fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(client_result.assetsByChunkName)); const server_stats = await server.compile(); emitter.emit('build', { type: 'server', // TODO duration/warnings - webpack_stats: server_stats + result: server_stats }); let serviceworker_stats; @@ -77,7 +76,7 @@ async function execute(emitter: EventEmitter, { if (serviceworker) { create_serviceworker_manifest({ routes: route_objects, - client_files: client_stats.toJson().assets.map((chunk: { name: string }) => `client/${chunk.name}`) + client_files: client_result.assets.map((chunk: { name: string }) => `client/${chunk.name}`) }); serviceworker_stats = await serviceworker.compile(); @@ -85,7 +84,7 @@ async function execute(emitter: EventEmitter, { emitter.emit('build', { type: 'serviceworker', // TODO duration/warnings - webpack_stats: serviceworker_stats + result: serviceworker_stats }); } } \ No newline at end of file diff --git a/src/api/dev.ts b/src/api/dev.ts index 1352744..3b38521 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -5,11 +5,10 @@ import * as child_process from 'child_process'; import * as ports from 'port-authority'; import mkdirp from 'mkdirp'; import rimraf from 'rimraf'; -import format_messages from 'webpack-format-messages'; import { locations } from '../config'; import { EventEmitter } from 'events'; import { create_routes, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core'; -import { Compiler, Compilers } from '../core/create_compilers'; +import { Compiler, Compilers, CompileResult, CompileError } from '../core/create_compilers'; import Deferred from './utils/Deferred'; import * as events from './interfaces'; @@ -49,17 +48,19 @@ class Watcher extends EventEmitter { dest = locations.dest(), routes = locations.routes(), webpack = 'webpack', + rollup = 'rollup', port = +process.env.PORT }: { app: string, dest: string, routes: string, webpack: string, + rollup: string, port: number }) { super(); - this.dirs = { app, dest, routes, webpack }; + this.dirs = { app, dest, routes, webpack, rollup }; this.port = port; this.closed = false; @@ -181,7 +182,7 @@ class Watcher extends EventEmitter { this.deferreds.server = new Deferred(); }, - result: info => { + handle_result: (result: CompileResult) => { this.deferreds.client.promise.then(() => { const restart = () => { log = ''; @@ -263,11 +264,11 @@ class Watcher extends EventEmitter { // quite difficult }, - result: info => { - fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(info.assetsByChunkName, null, ' ')); + handle_result: (result: CompileResult) => { + fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(result.assetsByChunkName, null, ' ')); this.deferreds.client.fulfil(); - const client_files = info.assets.map((chunk: { name: string }) => `client/${chunk.name}`); + const client_files = result.assets.map((chunk: { name: string }) => `client/${chunk.name}`); create_serviceworker_manifest({ routes: create_routes(), @@ -285,11 +286,7 @@ class Watcher extends EventEmitter { watch_serviceworker = noop; this.watch(compilers.serviceworker, { - name: 'service worker', - - result: info => { - fs.writeFileSync(path.join(dest, 'serviceworker_info.json'), JSON.stringify(info, null, ' ')); - } + name: 'service worker' }); } : noop; @@ -336,80 +333,34 @@ class Watcher extends EventEmitter { } } - watch(compiler: Compiler, { name, invalid = noop, result }: { + watch(compiler: Compiler, { name, invalid = noop, handle_result = noop }: { name: string, invalid?: (filename: string) => void; - result: (stats: any) => void; + handle_result?: (result: CompileResult) => void; }) { compiler.oninvalid(invalid); - compiler.watch((err: Error, stats: any) => { + compiler.watch((err?: Error, result?: CompileResult) => { if (err) { this.emit('error', { type: name, message: err.message }); } else { - const messages = format_messages(stats); - const info = stats.toJson(); - this.emit('build', { type: name, - duration: info.time, - - errors: messages.errors.map((message: string) => { - const duplicate = this.current_build.unique_errors.has(message); - this.current_build.unique_errors.add(message); - - return mungeWebpackError(message, duplicate); - }), - - warnings: messages.warnings.map((message: string) => { - const duplicate = this.current_build.unique_warnings.has(message); - this.current_build.unique_warnings.add(message); - - return mungeWebpackError(message, duplicate); - }), + duration: result.duration, + errors: result.errors, + warnings: result.warnings }); - result(info); + handle_result(result); } }); } } -const locPattern = /\((\d+):(\d+)\)$/; - -function mungeWebpackError(message: string, duplicate: boolean) { - // TODO this is all a bit rube goldberg... - const lines = message.split('\n'); - - const file = lines.shift() - .replace('', '') // careful — there is a special character at the beginning of this string - .replace('', '') - .replace('./', ''); - - let line = null; - let column = null; - - const match = locPattern.exec(lines[0]); - if (match) { - lines[0] = lines[0].replace(locPattern, ''); - line = +match[1]; - column = +match[2]; - } - - return { - file, - line, - column, - message: lines.join('\n'), - originalMessage: message, - duplicate - }; -} - const INTERVAL = 10000; class DevServer { @@ -466,7 +417,7 @@ function noop() {} function watch_dir( dir: string, - filter: ({ path, stats }: { path: string, stats: fs.Stats }) => boolean, + filter: ({ path, stats }: { path: string, stats: fs.CompileResult }) => boolean, callback: () => void ) { let watch; diff --git a/src/api/interfaces.ts b/src/api/interfaces.ts index 381877a..0be020d 100644 --- a/src/api/interfaces.ts +++ b/src/api/interfaces.ts @@ -1,4 +1,5 @@ import * as child_process from 'child_process'; +import { CompileResult } from '../core/create_compilers'; export type ReadyEvent = { port: number; @@ -29,7 +30,7 @@ export type BuildEvent = { errors: Array<{ file: string, message: string, duplicate: boolean }>; warnings: Array<{ file: string, message: string, duplicate: boolean }>; duration: number; - webpack_stats: any; + result: CompileResult; } export type FileEvent = { diff --git a/src/cli/build.ts b/src/cli/build.ts index 53f3002..c5844f6 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -15,7 +15,7 @@ export function build() { emitter.on('build', event => { console.log(colors.inverse(`\nbuilt ${event.type}`)); - console.log(event.webpack_stats.toString({ colors: true })); + console.log(event.result.print()); }); emitter.on('error', event => { diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index a6794b9..1ee96c5 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -1,10 +1,167 @@ import * as fs from 'fs'; import * as path from 'path'; +import { locations } from '../config'; import relative from 'require-relative'; let r: any; let wp: any; +export class CompileError { + file: string; + line: number; + column: number; + message: string; +} + +export class CompileResult { + duration: number; + errors: CompileError[]; + warnings: CompileError[]; + assets: Array<{ name: string }>; + assetsByChunkName: Record; +} + +class RollupResult extends CompileResult { + constructor(duration: number, stats: any) { + super(); + + this.duration = duration; + + this.errors = []; + this.warnings = []; + + // TODO + this.assets = []; + + this.assetsByChunkName = { + // TODO need to hash these filenames and + // expose the info in the Rollup output + main: `client.js` + }; + } + + print() { + return 'TODO summarise build'; + } +} + +class WebpackResult extends CompileResult { + stats: any; + + constructor(stats: any) { + super(); + + this.stats = stats; + + const info = stats.toJson(); + + // TODO use import() + const format_messages = require('webpack-format-messages'); + const messages = format_messages(stats); + + this.errors = messages.errors.map(mungeWebpackError); + this.warnings = messages.warnings.map(mungeWebpackError); + + this.duration = info.time; + + this.assets = info.assets.map((chunk: { name: string }) => `client/${chunk.name}`); + this.assetsByChunkName = info.assetsByChunkName; + } + + print() { + return this.stats.toString({ colors: true }); + } +} + +export class RollupCompiler { + _: Promise; + _oninvalid: (filename: string) => void; + _start: number; + + constructor(config: any) { + this._ = this.get_config(path.resolve(config)); + } + + async get_config(input: string) { + const bundle = await r.rollup({ + input, + external: (id: string) => { + return (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5, id.length) === '.json'; + } + }); + + const { code } = await bundle.generate({ format: 'cjs' }); + + // temporarily override require + const defaultLoader = require.extensions['.js']; + require.extensions['.js'] = (module: any, filename: string) => { + if (filename === input) { + module._compile(code, filename); + } else { + defaultLoader(module, filename); + } + }; + + const mod: any = require(input); + delete require.cache[input]; + + return mod; + } + + oninvalid(cb: (filename: string) => void) { + this._oninvalid = cb; + } + + async compile(): Promise { + const config = await this._; + + const start = Date.now(); + + const bundle = await r.rollup(config); + await bundle.write(config.output); + + return new RollupResult(Date.now() - start, bundle); + } + + async watch(cb: (err?: Error, stats?: any) => void) { + const config = await this._; + + const watcher = r.watch(config); + + watcher.on('event', (event: any) => { + switch (event.code) { + case 'FATAL': + // TODO kill the process? + cb(event.error); + break; + + case 'ERROR': + // TODO print warnings as well? + cb(event.error); + break; + + case 'START': + case 'END': + // TODO is there anything to do with this info? + break; + + case 'BUNDLE_START': + this._start = Date.now(); + // TODO figure out which file changed + this._oninvalid('[TODO] unknown file'); + break; + + case 'BUNDLE_END': + cb(null, new RollupResult(Date.now() - this._start, event.result)); + break; + + default: + console.log(`Unexpected event ${event.code}`); + } + }); + } +} + export class WebpackCompiler { _: any; @@ -16,7 +173,7 @@ export class WebpackCompiler { this._.hooks.invalid.tap('sapper', cb); } - compile() { + compile(): Promise { return new Promise((fulfil, reject) => { this._.run((err: Error, stats: any) => { if (err) { @@ -24,41 +181,26 @@ export class WebpackCompiler { process.exit(1); } - if (stats.hasErrors()) { - console.error(stats.toString({ colors: true })); + const result = new WebpackResult(stats); + + if (result.errors.length) { + // TODO print errors + // console.error(stats.toString({ colors: true })); reject(new Error(`Encountered errors while building app`)); } else { - fulfil(stats); + fulfil(result); } }); }); } - watch(cb: (err: Error, stats: any) => void) { - this._.watch({}, cb); - } -} - -export class RollupCompiler { - constructor(config: any) { - - } - - oninvalid(cb: (filename: string) => void) { - - } - - compile() { - return new Promise((fulfil, reject) => { - + watch(cb: (err?: Error, stats?: any) => void) { + this._.watch({}, (err?: Error, stats?: any) => { + cb(err, stats && new WebpackResult(stats)); }); } - - watch(cb: (err: Error, stats: any) => void) { - - } } export type Compiler = RollupCompiler | WebpackCompiler; @@ -95,4 +237,34 @@ export default function create_compilers({ webpack, rollup }: { webpack: string, } throw new Error(`Could not find config files for rollup or webpack`); +} + +const locPattern = /\((\d+):(\d+)\)$/; + +function mungeWebpackError(message: string) { + // TODO this is all a bit rube goldberg... + const lines = message.split('\n'); + + const file = lines.shift() + .replace('', '') // careful — there is a special character at the beginning of this string + .replace('', '') + .replace('./', ''); + + let line = null; + let column = null; + + const match = locPattern.exec(lines[0]); + if (match) { + lines[0] = lines[0].replace(locPattern, ''); + line = +match[1]; + column = +match[2]; + } + + return { + file, + line, + column, + message: lines.join('\n'), + originalMessage: message + }; } \ No newline at end of file diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index ea950e3..da760a0 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -105,11 +105,9 @@ function generate_client( code += ` - if (module.hot) { - import('${sapper_dev_client}').then(client => { - client.connect(${dev_port}); - }); - }`.replace(/^\t{3}/gm, ''); + import('${sapper_dev_client}').then(client => { + client.connect(${dev_port}); + });`.replace(/^\t{3}/gm, ''); } return code; diff --git a/src/middleware.ts b/src/middleware.ts index cd3bf94..b031af0 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -320,7 +320,7 @@ function get_page_handler( } const link = preloaded_chunks - .filter(file => !file.match(/\.map$/)) + .filter(file => file && !file.match(/\.map$/)) .map(file => `<${req.baseUrl}/client/${file}>;rel="preload";as="script"`) .join(', '); @@ -484,7 +484,7 @@ function get_page_handler( let scripts = [] .concat(chunks.main) // chunks main might be an array. it might not! thanks, webpack .filter(file => !file.match(/\.map$/)) - .map(file => ``) + .map(file => ``) .join(''); let inline_script = `__SAPPER__={${[ diff --git a/src/rollup.ts b/src/rollup.ts new file mode 100644 index 0000000..77d41fd --- /dev/null +++ b/src/rollup.ts @@ -0,0 +1,44 @@ +import { locations, dev } from './config'; + +export default { + dev: dev(), + + client: { + input: () => { + return `${locations.app()}/client.js` + }, + + output: () => { + return { + dir: `${locations.dest()}/client`, + format: 'esm' + }; + } + }, + + server: { + input: () => { + return `${locations.app()}/server.js` + }, + + output: () => { + return { + dir: locations.dest(), + format: 'cjs' + }; + } + }, + + serviceworker: { + input: () => { + return `${locations.app()}/service-worker.js`; + }, + + output: () => { + return { + dir: locations.dest(), + format: 'iife' + } + } + } +}; \ No newline at end of file From 200c5fcbd26549ff0d7c9617d66ad65104e4ac25 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 25 Aug 2018 14:01:07 -0400 Subject: [PATCH 007/178] get tests passing again --- src/api/build.ts | 2 +- src/api/dev.ts | 2 +- src/core/create_compilers.ts | 4 ++-- src/core/create_manifests.ts | 2 -- src/middleware.ts | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/api/build.ts b/src/api/build.ts index 5f43d18..79289d1 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -76,7 +76,7 @@ async function execute(emitter: EventEmitter, { if (serviceworker) { create_serviceworker_manifest({ routes: route_objects, - client_files: client_result.assets.map((chunk: { name: string }) => `client/${chunk.name}`) + client_files: client_result.assets.map((file: string) => `client/${file}`) }); serviceworker_stats = await serviceworker.compile(); diff --git a/src/api/dev.ts b/src/api/dev.ts index 3b38521..44a9ef9 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -268,7 +268,7 @@ class Watcher extends EventEmitter { fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(result.assetsByChunkName, null, ' ')); this.deferreds.client.fulfil(); - const client_files = result.assets.map((chunk: { name: string }) => `client/${chunk.name}`); + const client_files = result.assets.map((file: string) => `client/${file}`); create_serviceworker_manifest({ routes: create_routes(), diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index 1ee96c5..210b71f 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -17,7 +17,7 @@ export class CompileResult { duration: number; errors: CompileError[]; warnings: CompileError[]; - assets: Array<{ name: string }>; + assets: string[]; assetsByChunkName: Record; } @@ -64,7 +64,7 @@ class WebpackResult extends CompileResult { this.duration = info.time; - this.assets = info.assets.map((chunk: { name: string }) => `client/${chunk.name}`); + this.assets = info.assets.map((chunk: { name: string }) => chunk.name); this.assetsByChunkName = info.assetsByChunkName; } diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index da760a0..8152312 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -56,8 +56,6 @@ function generate_client( const server_routes_to_ignore = routes.server_routes.filter(route => !page_ids.has(route.pattern.toString())); - const len = Math.max(...routes.components.map(c => c.name.length)); - let code = ` // This file is generated by Sapper — do not edit it! import root from '${get_file(path_to_routes, routes.root)}'; diff --git a/src/middleware.ts b/src/middleware.ts index b031af0..8d999cb 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -484,7 +484,7 @@ function get_page_handler( let scripts = [] .concat(chunks.main) // chunks main might be an array. it might not! thanks, webpack .filter(file => !file.match(/\.map$/)) - .map(file => ``) + .map(file => ``) .join(''); let inline_script = `__SAPPER__={${[ From 6e2383b66b9efc9d4fc9ee5ea54c09373a47616e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 28 Aug 2018 17:28:40 -0400 Subject: [PATCH 008/178] add shimport --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 7ecf9ae..617f0c0 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "html-minifier": "^3.5.16", + "shimport": "^0.0.4", "source-map-support": "^0.5.6", "tslib": "^1.9.1" }, From 85e25d6380ad996f41e34ee595ecb889586b5ac0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 28 Aug 2018 17:29:14 -0400 Subject: [PATCH 009/178] add a --bundler option, for forcing rollup or webpack --- src/api/build.ts | 4 +++- src/api/dev.ts | 8 +++++++- src/api/export.ts | 2 +- src/cli/build.ts | 8 ++++++-- src/cli/dev.ts | 2 +- src/cli/export.ts | 2 +- src/cli/utils/validate_bundler.ts | 21 +++++++++++++++++++++ src/core/create_compilers.ts | 9 +++++---- 8 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 src/cli/utils/validate_bundler.ts diff --git a/src/api/build.ts b/src/api/build.ts index 79289d1..152dc43 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -7,6 +7,7 @@ import minify_html from './utils/minify_html'; import { create_compilers, create_main_manifests, create_routes, create_serviceworker_manifest } from '../core'; import { Compilers, Compiler } from '../core/create_compilers'; import * as events from './interfaces'; +import validate_bundler from '../cli/utils/validate_bundler'; export function build(opts: {}) { const emitter = new EventEmitter(); @@ -28,6 +29,7 @@ export function build(opts: {}) { async function execute(emitter: EventEmitter, { dest = 'build', app = 'app', + bundler, webpack = 'webpack', rollup = 'rollup', routes = 'routes' @@ -53,7 +55,7 @@ async function execute(emitter: EventEmitter, { // create app/manifest/client.js and app/manifest/server.js create_main_manifests({ routes: route_objects }); - const { client, server, serviceworker } = create_compilers({ webpack, rollup }); + const { client, server, serviceworker } = create_compilers(validate_bundler(bundler), { webpack, rollup }); const client_result = await client.compile(); emitter.emit('build', { diff --git a/src/api/dev.ts b/src/api/dev.ts index 44a9ef9..49fbab2 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -11,16 +11,19 @@ import { create_routes, create_main_manifests, create_compilers, create_servicew import { Compiler, Compilers, CompileResult, CompileError } from '../core/create_compilers'; import Deferred from './utils/Deferred'; import * as events from './interfaces'; +import validate_bundler from '../cli/utils/validate_bundler'; export function dev(opts) { return new Watcher(opts); } class Watcher extends EventEmitter { + bundler: string; dirs: { app: string; dest: string; routes: string; + rollup: string; webpack: string; } port: number; @@ -47,6 +50,7 @@ class Watcher extends EventEmitter { app = locations.app(), dest = locations.dest(), routes = locations.routes(), + bundler, webpack = 'webpack', rollup = 'rollup', port = +process.env.PORT @@ -54,12 +58,14 @@ class Watcher extends EventEmitter { app: string, dest: string, routes: string, + bundler?: string, webpack: string, rollup: string, port: number }) { super(); + this.bundler = validate_bundler(bundler); this.dirs = { app, dest, routes, webpack, rollup }; this.port = port; this.closed = false; @@ -157,7 +163,7 @@ class Watcher extends EventEmitter { }; // TODO watch the configs themselves? - const compilers: Compilers = create_compilers({ + const compilers: Compilers = create_compilers(this.bundler, { webpack: this.dirs.webpack, rollup: this.dirs.rollup }); diff --git a/src/api/export.ts b/src/api/export.ts index 9e80ac5..0fc877b 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -13,7 +13,7 @@ import * as events from './interfaces'; type Opts = { build: string, dest: string, - basepath: string, + basepath?: string, timeout: number | false }; diff --git a/src/cli/build.ts b/src/cli/build.ts index c5844f6..e1a7e87 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -1,14 +1,18 @@ import { build as _build } from '../api/build'; -import colors from 'kleur'; +import * as colors from 'kleur'; import { locations } from '../config'; +import validate_bundler from './utils/validate_bundler'; + +export function build(opts: { bundler?: string }) { + const bundler = validate_bundler(opts.bundler); -export function build() { return new Promise((fulfil, reject) => { try { const emitter = _build({ dest: locations.dest(), app: locations.app(), routes: locations.routes(), + bundler, webpack: 'webpack', rollup: 'rollup' }); diff --git a/src/cli/dev.ts b/src/cli/dev.ts index afbbc2f..ecaa4a3 100644 --- a/src/cli/dev.ts +++ b/src/cli/dev.ts @@ -5,7 +5,7 @@ import prettyMs from 'pretty-ms'; import { dev as _dev } from '../api/dev'; import * as events from '../api/interfaces'; -export function dev(opts: { port: number, open: boolean }) { +export function dev(opts: { port: number, open: boolean, bundler?: string }) { try { const watcher = _dev(opts); diff --git a/src/cli/export.ts b/src/cli/export.ts index 04aa0f9..052e9b9 100644 --- a/src/cli/export.ts +++ b/src/cli/export.ts @@ -1,5 +1,5 @@ import { exporter as _exporter } from '../api/export'; -import colors from 'kleur'; +import * as colors from 'kleur'; import prettyBytes from 'pretty-bytes'; import { locations } from '../config'; diff --git a/src/cli/utils/validate_bundler.ts b/src/cli/utils/validate_bundler.ts new file mode 100644 index 0000000..38ea550 --- /dev/null +++ b/src/cli/utils/validate_bundler.ts @@ -0,0 +1,21 @@ +import * as fs from 'fs'; + +export default function validate_bundler(bundler?: string) { + if (!bundler) { + bundler = ( + fs.existsSync('rollup') ? 'rollup' : + fs.existsSync('webpack') ? 'webpack' : + null + ); + + if (!bundler) { + throw new Error(`Could not find a 'rollup' or 'webpack' directory`); + } + } + + if (bundler !== 'rollup' && bundler !== 'webpack') { + throw new Error(`'${bundler}' is not a valid option for --bundler — must be either 'rollup' or 'webpack'`); + } + + return bundler; +} \ No newline at end of file diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index 210b71f..8e25432 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -211,8 +211,8 @@ export type Compilers = { serviceworker?: Compiler; } -export default function create_compilers({ webpack, rollup }: { webpack: string, rollup: string }): Compilers { - if (fs.existsSync(rollup)) { +export default function create_compilers(bundler: string, { webpack, rollup }: { webpack: string, rollup: string }): Compilers { + if (bundler === 'rollup') { if (!r) r = relative('rollup', process.cwd()); const sw = `${rollup}/service-worker.config.js`; @@ -224,7 +224,7 @@ export default function create_compilers({ webpack, rollup }: { webpack: string, }; } - if (fs.existsSync(webpack)) { + if (bundler === 'webpack') { if (!wp) wp = relative('webpack', process.cwd()); const sw = `${webpack}/service-worker.config.js`; @@ -236,7 +236,8 @@ export default function create_compilers({ webpack, rollup }: { webpack: string, }; } - throw new Error(`Could not find config files for rollup or webpack`); + // this shouldn't be possible... + throw new Error(`Invalid bundler option '${bundler}'`); } const locPattern = /\((\d+):(\d+)\)$/; From 19a5dcad1d534929af0446dbce8e6685eca11317 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 28 Aug 2018 17:29:27 -0400 Subject: [PATCH 010/178] update CLI --- src/cli.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 289bffa..6b09f06 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,7 +11,8 @@ prog.command('dev') .describe('Start a development server') .option('-p, --port', 'Specify a port') .option('-o, --open', 'Open a browser window') - .action(async (opts: { port: number, open: boolean }) => { + .option('--bundler', 'Specify a bundler (rollup or webpack)') + .action(async (opts: { port: number, open: boolean, bundler?: string }) => { const { dev } = await import('./cli/dev'); dev(opts); }); @@ -19,8 +20,9 @@ prog.command('dev') prog.command('build [dest]') .describe('Create a production-ready version of your app') .option('-p, --port', 'Default of process.env.PORT', '3000') + .option('--bundler', 'Specify a bundler (rollup or webpack)', 'auto') .example(`build custom-dir -p 4567`) - .action(async (dest = 'build', opts: { port: string }) => { + .action(async (dest = 'build', opts: { port: string, bundler?: string }) => { console.log(`> Building...`); process.env.NODE_ENV = process.env.NODE_ENV || 'production'; @@ -30,7 +32,7 @@ prog.command('build [dest]') try { const { build } = await import('./cli/build'); - await build(); + await build(opts); const launcher = path.resolve(dest, 'index.js'); @@ -66,8 +68,10 @@ prog.command('export [dest]') .option('--build-dir', 'Specify a custom temporary build directory', '.sapper/prod') .option('--basepath', 'Specify a base path') .option('--timeout', 'Milliseconds to wait for a page (--no-timeout to disable)', 5000) + .option('--bundler', 'Specify a bundler (rollup or webpack)', 'auto') .action(async (dest = 'export', opts: { build: boolean, + bundler?: string, 'build-dir': string, basepath?: string, timeout: number | false @@ -81,7 +85,7 @@ prog.command('export [dest]') if (opts.build) { console.log(`> Building...`); const { build } = await import('./cli/build'); - await build(); + await build(opts); console.error(`\n> Built in ${elapsed(start)}`); } From 70b5cc86dc795fe4afee84936b593153d7ab6c6c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 28 Aug 2018 17:56:46 -0400 Subject: [PATCH 011/178] replace client_assets.json with build.json, include bundler name --- src/api/build.ts | 5 ++++- src/api/dev.ts | 5 ++++- src/cli.ts | 4 ++-- src/cli/build.ts | 2 +- src/cli/export.ts | 2 +- src/middleware.ts | 28 ++++++++++++++++------------ 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/api/build.ts b/src/api/build.ts index 152dc43..1f66bcf 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -64,7 +64,10 @@ async function execute(emitter: EventEmitter, { result: client_result }); - fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(client_result.assetsByChunkName)); + fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify({ + bundler, + assets: client_result.assetsByChunkName + })); const server_stats = await server.compile(); emitter.emit('build', { diff --git a/src/api/dev.ts b/src/api/dev.ts index 49fbab2..d7a4506 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -271,7 +271,10 @@ class Watcher extends EventEmitter { }, handle_result: (result: CompileResult) => { - fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(result.assetsByChunkName, null, ' ')); + fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify({ + bundler: this.bundler, + assets: result.assetsByChunkName + }, null, ' ')); this.deferreds.client.fulfil(); const client_files = result.assets.map((file: string) => `client/${file}`); diff --git a/src/cli.ts b/src/cli.ts index 6b09f06..302859a 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -20,7 +20,7 @@ prog.command('dev') prog.command('build [dest]') .describe('Create a production-ready version of your app') .option('-p, --port', 'Default of process.env.PORT', '3000') - .option('--bundler', 'Specify a bundler (rollup or webpack)', 'auto') + .option('--bundler', 'Specify a bundler (rollup or webpack, blank for auto)') .example(`build custom-dir -p 4567`) .action(async (dest = 'build', opts: { port: string, bundler?: string }) => { console.log(`> Building...`); @@ -68,7 +68,7 @@ prog.command('export [dest]') .option('--build-dir', 'Specify a custom temporary build directory', '.sapper/prod') .option('--basepath', 'Specify a base path') .option('--timeout', 'Milliseconds to wait for a page (--no-timeout to disable)', 5000) - .option('--bundler', 'Specify a bundler (rollup or webpack)', 'auto') + .option('--bundler', 'Specify a bundler (rollup or webpack, blank for auto)') .action(async (dest = 'export', opts: { build: boolean, bundler?: string, diff --git a/src/cli/build.ts b/src/cli/build.ts index e1a7e87..f00dafb 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -1,5 +1,5 @@ import { build as _build } from '../api/build'; -import * as colors from 'kleur'; +import colors from 'kleur'; import { locations } from '../config'; import validate_bundler from './utils/validate_bundler'; diff --git a/src/cli/export.ts b/src/cli/export.ts index 052e9b9..04aa0f9 100644 --- a/src/cli/export.ts +++ b/src/cli/export.ts @@ -1,5 +1,5 @@ import { exporter as _exporter } from '../api/export'; -import * as colors from 'kleur'; +import colors from 'kleur'; import prettyBytes from 'pretty-bytes'; import { locations } from '../config'; diff --git a/src/middleware.ts b/src/middleware.ts index 8d999cb..ea28340 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -282,9 +282,9 @@ function get_page_handler( ) { 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 get_build_info = dev() + ? () => JSON.parse(fs.readFileSync(path.join(output, 'build.json'), 'utf-8')) + : (assets => () => assets)(JSON.parse(fs.readFileSync(path.join(output, 'build.json'), 'utf-8'))); const template = dev() ? () => fs.readFileSync(`${locations.app()}/template.html`, 'utf-8') @@ -303,19 +303,22 @@ function get_page_handler( } function handle_page(page: Page, req: Req, res: ServerResponse, status = 200, error: Error | string = null) { - const chunks: Record = get_chunks(); + const build_info: { + bundler: 'rollup' | 'webpack', + assets: Record + } = get_build_info(); res.setHeader('Content-Type', 'text/html'); // preload main.js and current route // TODO detect other stuff we can preload? images, CSS, fonts? - let preloaded_chunks = Array.isArray(chunks.main) ? chunks.main : [chunks.main]; + let preloaded_chunks = Array.isArray(build_info.assets.main) ? build_info.assets.main : [build_info.assets.main]; if (!error) { page.parts.forEach(part => { if (!part) return; // using concat because it could be a string or an array. thanks webpack! - preloaded_chunks = preloaded_chunks.concat(chunks[part.name]); + preloaded_chunks = preloaded_chunks.concat(build_info.assets[part.name]); }); } @@ -481,11 +484,12 @@ function get_page_handler( store }); - let scripts = [] - .concat(chunks.main) // chunks main might be an array. it might not! thanks, webpack - .filter(file => !file.match(/\.map$/)) - .map(file => ``) - .join(''); + const file = [].concat(build_info.assets.main).filter(file => file && /\.js$/.test(file))[0]; + const main = `${req.baseUrl}/client/${file}`; + + const script = build_info.bundler === 'rollup' + ? `` + : ``; let inline_script = `__SAPPER__={${[ error && `error:1`, @@ -501,7 +505,7 @@ function get_page_handler( const body = template() .replace('%sapper.base%', () => ``) - .replace('%sapper.scripts%', () => `${scripts}`) + .replace('%sapper.scripts%', () => `${script}`) .replace('%sapper.html%', () => html) .replace('%sapper.head%', () => `${head}`) .replace('%sapper.styles%', () => (css && css.code ? `` : '')); From 7e698f1613cd8f861f11a90cb8156a246e356561 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 28 Aug 2018 18:06:26 -0400 Subject: [PATCH 012/178] use watchChange plugin hook to detect invalidations --- src/core/create_compilers.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index 8e25432..0013a9a 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -105,6 +105,13 @@ export class RollupCompiler { const mod: any = require(input); delete require.cache[input]; + (mod.plugins || (mod.plugins = [])).push({ + name: 'sapper-internal', + watchChange: (file: string) => { + this._oninvalid(file); + } + }); + return mod; } @@ -147,8 +154,6 @@ export class RollupCompiler { case 'BUNDLE_START': this._start = Date.now(); - // TODO figure out which file changed - this._oninvalid('[TODO] unknown file'); break; case 'BUNDLE_END': From f8d742bdd006c525d2dd0d5d35280bca394cbb19 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 10:55:52 -0400 Subject: [PATCH 013/178] hashing --- src/core/create_compilers.ts | 41 ++++++++++++++++++++++++++---------- src/rollup.ts | 2 ++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index 0013a9a..5b7fe38 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -22,7 +22,7 @@ export class CompileResult { } class RollupResult extends CompileResult { - constructor(duration: number, stats: any) { + constructor(duration: number, { input, chunks }: { input: string, chunks: any[] }) { super(); this.duration = duration; @@ -30,14 +30,17 @@ class RollupResult extends CompileResult { this.errors = []; this.warnings = []; - // TODO - this.assets = []; + this.assets = chunks.map(chunk => chunk.fileName); - this.assetsByChunkName = { - // TODO need to hash these filenames and - // expose the info in the Rollup output - main: `client.js` - }; + // TODO populate this properly. We don't have named chunks, as in + // webpack, but we can have a route -> [chunk] map or something + this.assetsByChunkName = {}; + + chunks.forEach(chunk => { + if (input in chunk.modules) { + this.assetsByChunkName.main = chunk.fileName; + } + }); } print() { @@ -77,9 +80,13 @@ export class RollupCompiler { _: Promise; _oninvalid: (filename: string) => void; _start: number; + input: string; + chunks: any[]; // TODO types constructor(config: any) { this._ = this.get_config(path.resolve(config)); + this.input = null; + this.chunks = []; } async get_config(input: string) { @@ -107,8 +114,13 @@ export class RollupCompiler { (mod.plugins || (mod.plugins = [])).push({ name: 'sapper-internal', - watchChange: (file: string) => { - this._oninvalid(file); + options: (opts: any) => { + this.input = opts.input; + }, + renderChunk: (code: string, chunk: any) => { + if (chunk.isEntry) { + this.chunks.push(chunk); + } } }); @@ -135,6 +147,10 @@ export class RollupCompiler { const watcher = r.watch(config); + watcher.on('change', (id: string) => { + this._oninvalid(id); + }); + watcher.on('event', (event: any) => { switch (event.code) { case 'FATAL': @@ -157,7 +173,10 @@ export class RollupCompiler { break; case 'BUNDLE_END': - cb(null, new RollupResult(Date.now() - this._start, event.result)); + cb(null, new RollupResult(Date.now() - this._start, { + input: this.input, + chunks: this.chunks + })); break; default: diff --git a/src/rollup.ts b/src/rollup.ts index 77d41fd..5ef280a 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -11,6 +11,8 @@ export default { output: () => { return { dir: `${locations.dest()}/client`, + entryFileNames: '[name].[hash].js', + chunkFileNames: '[name].[hash].js', format: 'esm' }; } From 458be49b351f1a92db68fb26f4dd65166175cbfc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 13:26:31 -0400 Subject: [PATCH 014/178] emit errors and warnings, albeit clumsily --- src/api/dev.ts | 4 +-- src/core/create_compilers.ts | 69 ++++++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/api/dev.ts b/src/api/dev.ts index d7a4506..92b07db 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -256,8 +256,6 @@ class Watcher extends EventEmitter { } }); - let first = true; - this.watch(compilers.client, { name: 'client', @@ -426,7 +424,7 @@ function noop() {} function watch_dir( dir: string, - filter: ({ path, stats }: { path: string, stats: fs.CompileResult }) => boolean, + filter: ({ path, stats }: { path: string, stats: fs.Stats }) => boolean, callback: () => void ) { let watch; diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index 5b7fe38..7909944 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -8,8 +8,6 @@ let wp: any; export class CompileError { file: string; - line: number; - column: number; message: string; } @@ -22,22 +20,22 @@ export class CompileResult { } class RollupResult extends CompileResult { - constructor(duration: number, { input, chunks }: { input: string, chunks: any[] }) { + constructor(duration: number, compiler: RollupCompiler) { super(); this.duration = duration; - this.errors = []; - this.warnings = []; + this.errors = compiler.errors.map(munge_rollup_warning_or_error); + this.warnings = compiler.warnings.map(munge_rollup_warning_or_error); // TODO emit this as they happen - this.assets = chunks.map(chunk => chunk.fileName); + this.assets = compiler.chunks.map(chunk => chunk.fileName); - // TODO populate this properly. We don't have named chunks, as in + // TODO populate this properly. We don't have namedcompiler. chunks, as in // webpack, but we can have a route -> [chunk] map or something this.assetsByChunkName = {}; - chunks.forEach(chunk => { - if (input in chunk.modules) { + compiler.chunks.forEach(chunk => { + if (compiler.input in chunk.modules) { this.assetsByChunkName.main = chunk.fileName; } }); @@ -62,8 +60,8 @@ class WebpackResult extends CompileResult { const format_messages = require('webpack-format-messages'); const messages = format_messages(stats); - this.errors = messages.errors.map(mungeWebpackError); - this.warnings = messages.warnings.map(mungeWebpackError); + this.errors = messages.errors.map(munge_webpack_warning_or_error); + this.warnings = messages.warnings.map(munge_webpack_warning_or_error); this.duration = info.time; @@ -81,11 +79,15 @@ export class RollupCompiler { _oninvalid: (filename: string) => void; _start: number; input: string; + warnings: any[]; + errors: any[]; chunks: any[]; // TODO types constructor(config: any) { this._ = this.get_config(path.resolve(config)); this.input = null; + this.warnings = []; + this.errors = []; this.chunks = []; } @@ -124,6 +126,16 @@ export class RollupCompiler { } }); + const onwarn = mod.onwarn || ((warning: any, handler: (warning: any) => void) => { + handler(warning); + }); + + mod.onwarn = (warning: any) => { + onwarn(warning, (warning: any) => { + this.warnings.push(warning); + }); + }; + return mod; } @@ -148,6 +160,9 @@ export class RollupCompiler { const watcher = r.watch(config); watcher.on('change', (id: string) => { + this.chunks = []; + this.warnings = []; + this.errors = []; this._oninvalid(id); }); @@ -155,12 +170,21 @@ export class RollupCompiler { switch (event.code) { case 'FATAL': // TODO kill the process? + if (event.error.filename) { + // TODO this is a bit messy. Also, can + // Rollup emit other kinds of error? + event.error.message = [ + `Failed to build — error in ${event.error.filename}: ${event.error.message}`, + event.error.frame + ].filter(Boolean).join('\n'); + } + cb(event.error); break; case 'ERROR': - // TODO print warnings as well? - cb(event.error); + this.errors.push(event.error); + cb(null, new RollupResult(Date.now() - this._start, this)); break; case 'START': @@ -173,10 +197,7 @@ export class RollupCompiler { break; case 'BUNDLE_END': - cb(null, new RollupResult(Date.now() - this._start, { - input: this.input, - chunks: this.chunks - })); + cb(null, new RollupResult(Date.now() - this._start, this)); break; default: @@ -266,7 +287,7 @@ export default function create_compilers(bundler: string, { webpack, rollup }: { const locPattern = /\((\d+):(\d+)\)$/; -function mungeWebpackError(message: string) { +function munge_webpack_warning_or_error(message: string) { // TODO this is all a bit rube goldberg... const lines = message.split('\n'); @@ -287,9 +308,13 @@ function mungeWebpackError(message: string) { return { file, - line, - column, - message: lines.join('\n'), - originalMessage: message + message: lines.join('\n') + }; +} + +function munge_rollup_warning_or_error(warning_or_error: any) { + return { + file: warning_or_error.filename, + message: [warning_or_error.message, warning_or_error.frame].filter(Boolean).join('\n') }; } \ No newline at end of file From 6393a30b13a0fafdc1c00aac131a32d615d175ce Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 15:03:10 -0400 Subject: [PATCH 015/178] print nice build summaries --- src/cli.ts | 2 +- src/cli/build.ts | 19 ++++++++-- src/cli/export.ts | 11 ++---- src/core/create_compilers.ts | 67 +++++++++++++++++++++++++++++++++--- src/utils.ts | 15 ++++++++ 5 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 src/utils.ts diff --git a/src/cli.ts b/src/cli.ts index 302859a..0568206 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -48,7 +48,7 @@ prog.command('build [dest]') console.error(`\n> Finished in ${elapsed(start)}. Type ${colors.bold.cyan(`node ${dest}`)} to run the app.`); } catch (err) { - console.error(err ? err.details || err.stack || err.message || err : 'Unknown error'); + console.log(`${colors.bold.red(`> ${err.message}`)}`); process.exit(1); } }); diff --git a/src/cli/build.ts b/src/cli/build.ts index f00dafb..6b291b6 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -2,6 +2,7 @@ import { build as _build } from '../api/build'; import colors from 'kleur'; import { locations } from '../config'; import validate_bundler from './utils/validate_bundler'; +import { repeat } from '../utils'; export function build(opts: { bundler?: string }) { const bundler = validate_bundler(opts.bundler); @@ -18,7 +19,20 @@ export function build(opts: { bundler?: string }) { }); emitter.on('build', event => { - console.log(colors.inverse(`\nbuilt ${event.type}`)); + let banner = `built ${event.type}`; + let c = colors.cyan; + + const { warnings } = event.result; + if (warnings.length > 0) { + banner += ` with ${warnings.length} ${warnings.length === 1 ? 'warning' : 'warnings'}`; + c = colors.yellow; + } + + console.log(); + console.log(c(`┌─${repeat('─', banner.length)}─┐`)); + console.log(c(`│ ${colors.bold(banner) } │`)); + console.log(c(`└─${repeat('─', banner.length)}─┘`)); + console.log(event.result.print()); }); @@ -30,8 +44,7 @@ export function build(opts: { bundler?: string }) { fulfil(); }); } catch (err) { - console.log(`${colors.bold.red(`> ${err.message}`)}`); - process.exit(1); + reject(err); } }); } \ No newline at end of file diff --git a/src/cli/export.ts b/src/cli/export.ts index 04aa0f9..00dd1d5 100644 --- a/src/cli/export.ts +++ b/src/cli/export.ts @@ -1,12 +1,8 @@ import { exporter as _exporter } from '../api/export'; import colors from 'kleur'; -import prettyBytes from 'pretty-bytes'; +import pb from 'pretty-bytes'; import { locations } from '../config'; - -function left_pad(str: string, len: number) { - while (str.length < len) str = ` ${str}`; - return str; -} +import { right_pad } from '../utils'; export function exporter(export_dir: string, { basepath = '', @@ -25,9 +21,8 @@ export function exporter(export_dir: string, { }); emitter.on('file', event => { - const pb = prettyBytes(event.size); const size_color = event.size > 150000 ? colors.bold.red : event.size > 50000 ? colors.bold.yellow : colors.bold.gray; - const size_label = size_color(left_pad(prettyBytes(event.size), 10)); + const size_label = size_color(right_pad(pb(event.size), 10)); const file_label = event.status === 200 ? event.file diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index 7909944..7d97b10 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -1,7 +1,10 @@ import * as fs from 'fs'; import * as path from 'path'; +import colors from 'kleur'; +import pb from 'pretty-bytes'; import { locations } from '../config'; import relative from 'require-relative'; +import { left_pad, right_pad } from '../utils'; let r: any; let wp: any; @@ -20,6 +23,8 @@ export class CompileResult { } class RollupResult extends CompileResult { + summary: string; + constructor(duration: number, compiler: RollupCompiler) { super(); @@ -39,10 +44,51 @@ class RollupResult extends CompileResult { this.assetsByChunkName.main = chunk.fileName; } }); + + this.summary = compiler.chunks.map(chunk => { + const size_color = chunk.code.length > 150000 ? colors.bold.red : chunk.code.length > 50000 ? colors.bold.yellow : colors.bold.white; + const size_label = left_pad(pb(chunk.code.length), 10); + + const lines = [size_color(`${size_label} ${chunk.fileName}`)]; + + const deps = Object.keys(chunk.modules) + .map(file => { + return { + file: path.relative(process.cwd(), file), + size: chunk.modules[file].renderedLength + }; + }) + .filter(dep => dep.size > 0) + .sort((a, b) => b.size - a.size); + + const total_unminified = deps.reduce((t, d) => t + d.size, 0); + + deps.forEach((dep, i) => { + const c = i === deps.length - 1 ? '└' : '│'; + let line = ` ${c} ${dep.file}`; + + if (deps.length > 1) { + const p = (100 * dep.size / total_unminified).toFixed(1); + line += ` (${p}%)`; + } + + lines.push(colors.gray(line)); + }); + + return lines.join('\n'); + }).join('\n'); } print() { - return 'TODO summarise build'; + const blocks: string[] = this.warnings.map(warning => { + return warning.file + ? `> ${colors.bold(warning.file)}\n${warning.message}` + : `> ${warning.message}`; + }); + + blocks.push(this.summary); + + return blocks.join('\n\n'); } } @@ -148,10 +194,23 @@ export class RollupCompiler { const start = Date.now(); - const bundle = await r.rollup(config); - await bundle.write(config.output); + try { + const bundle = await r.rollup(config); + await bundle.write(config.output); - return new RollupResult(Date.now() - start, bundle); + return new RollupResult(Date.now() - start, this); + } catch (err) { + if (err.filename) { + // TODO this is a bit messy. Also, can + // Rollup emit other kinds of error? + err.message = [ + `Failed to build — error in ${err.filename}: ${err.message}`, + err.frame + ].filter(Boolean).join('\n'); + } + + throw err; + } } async watch(cb: (err?: Error, stats?: any) => void) { diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..ea70779 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,15 @@ +export function left_pad(str: string, len: number) { + while (str.length < len) str = ` ${str}`; + return str; +} + +export function right_pad(str: string, len: number) { + while (str.length < len) str += ' '; + return str; +} + +export function repeat(str: string, i: number) { + let result = ''; + while (i--) result += str; + return result; +} \ No newline at end of file From 36fc8a947b0f9594f4e24eeb2e204786576b523a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 15:58:27 -0400 Subject: [PATCH 016/178] use shimport --- package.json | 2 +- src/api/build.ts | 5 ++++- src/api/dev.ts | 5 ++++- src/api/utils/copy_shimport.ts | 9 +++++++++ src/middleware.ts | 4 ++-- 5 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 src/api/utils/copy_shimport.ts diff --git a/package.json b/package.json index 617f0c0..54edf36 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "html-minifier": "^3.5.16", - "shimport": "^0.0.4", + "shimport": "^0.0.5", "source-map-support": "^0.5.6", "tslib": "^1.9.1" }, diff --git a/src/api/build.ts b/src/api/build.ts index 1f66bcf..c67071a 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -8,6 +8,7 @@ import { create_compilers, create_main_manifests, create_routes, create_servicew import { Compilers, Compiler } from '../core/create_compilers'; import * as events from './interfaces'; import validate_bundler from '../cli/utils/validate_bundler'; +import { copy_shimport } from './utils/copy_shimport'; export function build(opts: {}) { const emitter = new EventEmitter(); @@ -34,8 +35,9 @@ async function execute(emitter: EventEmitter, { rollup = 'rollup', routes = 'routes' } = {}) { - mkdirp.sync(dest); + mkdirp.sync(`${dest}/client`); rimraf.sync(path.join(dest, '**/*')); + copy_shimport(dest); // minify app/template.html // TODO compile this to a function? could be quicker than str.replace(...).replace(...).replace(...) @@ -66,6 +68,7 @@ async function execute(emitter: EventEmitter, { fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify({ bundler, + shimport: bundler === 'rollup' && require('shimport/package.json').version, assets: client_result.assetsByChunkName })); diff --git a/src/api/dev.ts b/src/api/dev.ts index 92b07db..1b7a719 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -12,6 +12,7 @@ import { Compiler, Compilers, CompileResult, CompileError } from '../core/create import Deferred from './utils/Deferred'; import * as events from './interfaces'; import validate_bundler from '../cli/utils/validate_bundler'; +import { copy_shimport } from './utils/copy_shimport'; export function dev(opts) { return new Watcher(opts); @@ -110,7 +111,8 @@ class Watcher extends EventEmitter { const { dest } = this.dirs; rimraf.sync(dest); - mkdirp.sync(dest); + mkdirp.sync(`${dest}/client`); + if (this.bundler === 'rollup') copy_shimport(dest); const dev_port = await ports.find(10000); @@ -271,6 +273,7 @@ class Watcher extends EventEmitter { handle_result: (result: CompileResult) => { fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify({ bundler: this.bundler, + shimport: this.bundler === 'rollup' && require('shimport/package.json').version, assets: result.assetsByChunkName }, null, ' ')); this.deferreds.client.fulfil(); diff --git a/src/api/utils/copy_shimport.ts b/src/api/utils/copy_shimport.ts new file mode 100644 index 0000000..27c247d --- /dev/null +++ b/src/api/utils/copy_shimport.ts @@ -0,0 +1,9 @@ +import * as fs from 'fs'; + +export function copy_shimport(dest: string) { + const shimport_version = require('shimport/package.json').version; + fs.writeFileSync( + `${dest}/client/shimport@${shimport_version}.js`, + fs.readFileSync(require.resolve('shimport/index.dev.js')) + ); +} \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts index ea28340..982e081 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -305,6 +305,7 @@ function get_page_handler( function handle_page(page: Page, req: Req, res: ServerResponse, status = 200, error: Error | string = null) { const build_info: { bundler: 'rollup' | 'webpack', + shimport: string | null, assets: Record } = get_build_info(); @@ -357,7 +358,6 @@ function get_page_handler( ); if (include_cookies) { - const cookies: Record = {}; if (!opts.headers) opts.headers = {}; const str = [] @@ -488,7 +488,7 @@ function get_page_handler( const main = `${req.baseUrl}/client/${file}`; const script = build_info.bundler === 'rollup' - ? `` + ? `` : ``; let inline_script = `__SAPPER__={${[ From 99b096a5c4b71935f3834cb215dd943d4aab5b2d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 16:05:41 -0400 Subject: [PATCH 017/178] rimraf before mkdirp --- src/api/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/build.ts b/src/api/build.ts index c67071a..941c126 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -35,8 +35,8 @@ async function execute(emitter: EventEmitter, { rollup = 'rollup', routes = 'routes' } = {}) { - mkdirp.sync(`${dest}/client`); rimraf.sync(path.join(dest, '**/*')); + mkdirp.sync(`${dest}/client`); copy_shimport(dest); // minify app/template.html From de308d5bb0f86802c0fa4d71f9995bde83433223 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 16:29:37 -0400 Subject: [PATCH 018/178] rebuild lockfile --- package-lock.json | 1515 +++++++++++++++++++++++++++++---------------- 1 file changed, 970 insertions(+), 545 deletions(-) diff --git a/package-lock.json b/package-lock.json index 09025f7..070086a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.17.0", + "version": "0.17.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -55,9 +55,9 @@ "dev": true }, "@types/node": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.7.1.tgz", - "integrity": "sha512-EGoI4ylB/lPOaqXqtzAyL8HcgOuCtH2hkEaLmkueOYufsTFWBn4VCvlCDC2HW8Q+9iF+QVC3sxjDKQYjHQeZ9w==", + "version": "10.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.9.3.tgz", + "integrity": "sha512-DOzWZKUnmFYG0KUOs+9HEBju2QhBU6oM2zeluunQNt0vnJvnkHvtDNlQPZDkTrkC5pZrNx1TPqeL137zciXZMQ==", "dev": true }, "@types/rimraf": { @@ -340,9 +340,9 @@ } }, "acorn": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", - "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.2.tgz", + "integrity": "sha512-cJrKCNcr2kv8dlDnbw+JPUGjHZzo4myaxOLmpOX8a+rgX94YeTcTMv/LFJUSByRpc+i4GgVnnhLxvMu/2Y+rqw==", "dev": true }, "acorn-dynamic-import": { @@ -415,6 +415,282 @@ "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } } }, "aproba": { @@ -433,10 +709,13 @@ } }, "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } }, "arr-flatten": { "version": "1.1.0", @@ -496,9 +775,9 @@ "dev": true }, "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", "dev": true }, "arrify": { @@ -608,7 +887,7 @@ "dependencies": { "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -688,6 +967,18 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, @@ -768,32 +1059,14 @@ } }, "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" } }, "brorand": { @@ -955,6 +1228,14 @@ "to-object-path": "^0.3.0", "union-value": "^1.0.0", "unset-value": "^1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "caller-path": { @@ -1024,9 +1305,9 @@ } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -1065,6 +1346,104 @@ "path-is-absolute": "^1.0.0", "readdirp": "^2.0.0", "upath": "^1.0.5" + }, + "dependencies": { + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "chownr": { @@ -1118,6 +1497,12 @@ "requires": { "is-descriptor": "^0.1.0" } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true } } }, @@ -1127,13 +1512,6 @@ "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", "requires": { "source-map": "~0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } } }, "cli-cursor": { @@ -1180,18 +1558,18 @@ } }, "color-convert": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", - "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "color-name": "1.1.1" + "color-name": "1.1.3" } }, "color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "combined-stream": { @@ -1468,7 +1846,7 @@ "dependencies": { "lodash": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/lodash/-/lodash-3.0.1.tgz", "integrity": "sha1-FNSQKKOLx0AkHRHi7NV+wG1zwZo=", "dev": true } @@ -1550,6 +1928,18 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, @@ -1670,9 +2060,9 @@ "dev": true }, "electron": { - "version": "1.8.7", - "resolved": "https://registry.npmjs.org/electron/-/electron-1.8.7.tgz", - "integrity": "sha512-q6dn8bspX8u8z6tNU4bEas6ZrdNavnrjJ6d/oz49Nb4zFIPrdh8p29AFjFlSAavypGwAVR/PhYOAGwzZSQSSVQ==", + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/electron/-/electron-1.8.8.tgz", + "integrity": "sha512-1f9zJehcTTGjrkb06o6ds+gsRq6SYhZJyxOk6zIWjRH8hVy03y/RzUDELzNas71f5vcvXmfGVvyjeEsadDI8tg==", "dev": true, "requires": { "@types/node": "^8.0.24", @@ -1681,9 +2071,9 @@ }, "dependencies": { "@types/node": { - "version": "8.10.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.26.tgz", - "integrity": "sha512-opk6bLLErLSwyVVJeSH5Ek7ZWOBSsN0JrvXTNVGLXLAXKB9xlTYajrplR44xVyMrmbut94H6uJ9jqzM/12jxkA==", + "version": "8.10.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.28.tgz", + "integrity": "sha512-iHsAzDg3OLH7JP+wipniUULHoDSWLgEDYOvsar6/mpAkTJd9/n23Ap8ikruMlvRTqMv/LXrflH9v/AfiEqaBGg==", "dev": true } } @@ -2056,38 +2446,12 @@ } }, "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "is-posix-bracket": "^0.1.0" } }, "expand-range": { @@ -2097,48 +2461,6 @@ "dev": true, "requires": { "fill-range": "^2.1.0" - }, - "dependencies": { - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } } }, "express": { @@ -2226,68 +2548,12 @@ } }, "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "is-extglob": "^1.0.0" } }, "extract-zip": { @@ -2361,26 +2627,16 @@ "dev": true }, "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" } }, "finalhandler": { @@ -3130,9 +3386,9 @@ } }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3151,53 +3407,15 @@ "requires": { "glob-parent": "^2.0.0", "is-glob": "^2.0.0" - }, - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } } }, "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } + "is-glob": "^2.0.0" } }, "globals": { @@ -3293,6 +3511,14 @@ "get-value": "^2.0.6", "has-values": "^1.0.0", "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "has-values": { @@ -3305,6 +3531,26 @@ "kind-of": "^4.0.0" }, "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", @@ -3408,9 +3654,9 @@ "dev": true }, "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -3512,17 +3758,6 @@ "dev": true, "requires": { "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } } }, "is-arrayish": { @@ -3568,17 +3803,6 @@ "dev": true, "requires": { "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } } }, "is-date-object": { @@ -3628,9 +3852,9 @@ "dev": true }, "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", "dev": true }, "is-finite": { @@ -3649,12 +3873,12 @@ "dev": true }, "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "^1.0.0" } }, "is-module": { @@ -3664,23 +3888,12 @@ "dev": true }, "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } } }, "is-path-cwd": { @@ -3714,6 +3927,14 @@ "dev": true, "requires": { "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "is-posix-bracket": { @@ -3786,10 +4007,13 @@ "dev": true }, "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } }, "isstream": { "version": "0.1.2", @@ -3896,10 +4120,13 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } }, "klaw": { "version": "1.3.1", @@ -3911,9 +4138,9 @@ } }, "kleur": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.1.tgz", - "integrity": "sha512-Zq/jyANIJ2uX8UZjWlqLwbyhcxSXJtT/Y89lClyeZd3l++3ztL1I5SSCYrbcbwSunTjC88N3WuMk0kRDQD6gzA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", + "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", "dev": true }, "levn": { @@ -4230,24 +4457,24 @@ "dev": true }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" } }, "miller-rabin": { @@ -4267,18 +4494,18 @@ "dev": true }, "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", "dev": true }, "mime-types": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", - "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", "dev": true, "requires": { - "mime-db": "~1.35.0" + "mime-db": "~1.36.0" } }, "mimic-fn": { @@ -4393,7 +4620,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -4434,6 +4661,20 @@ "ms": "2.0.0" } }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -4487,9 +4728,9 @@ "dev": true }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", "dev": true, "optional": true }, @@ -4510,6 +4751,26 @@ "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } } }, "natural-compare": { @@ -4531,9 +4792,9 @@ "dev": true }, "nice-try": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", - "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, "nightmare": { @@ -4783,15 +5044,6 @@ "requires": { "is-descriptor": "^0.1.0" } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } } } }, @@ -4808,6 +5060,14 @@ "dev": true, "requires": { "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "object.omit": { @@ -4827,6 +5087,14 @@ "dev": true, "requires": { "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "on-finished": { @@ -4969,23 +5237,6 @@ "is-dotfile": "^1.0.0", "is-extglob": "^1.0.0", "is-glob": "^2.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } } }, "parse-json": { @@ -5345,6 +5596,12 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, @@ -5688,9 +5945,9 @@ } }, "rollup-plugin-commonjs": { - "version": "9.1.5", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.1.5.tgz", - "integrity": "sha512-Hy7KbvsSMNu6aCO2xabp8gBcWrTiS+EzfHkzWwZwMjrcAYuYfCLU7fP1nM4xM0FMye/13r8mzTkfb9AmDaZ1hQ==", + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.1.6.tgz", + "integrity": "sha512-J7GOJm9uzEeLqkVxYSgjyoieh34hATWpa9G2M1ilGzWOLYGfQx5IDQ9ewG8QUj/Z2dzgV+d0/AyloAzElkABAA==", "dev": true, "requires": { "estree-walker": "^0.5.1", @@ -5785,7 +6042,7 @@ }, "typescript": { "version": "1.8.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-1.8.10.tgz", + "resolved": "http://registry.npmjs.org/typescript/-/typescript-1.8.10.tgz", "integrity": "sha1-tHXW4N/wv1DyluXKbvn7tccyDx4=", "dev": true } @@ -5799,97 +6056,6 @@ "requires": { "estree-walker": "^0.5.2", "micromatch": "^2.3.11" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - } } }, "run-async": { @@ -6132,6 +6298,11 @@ "jsonify": "~0.0.0" } }, + "shimport": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/shimport/-/shimport-0.0.5.tgz", + "integrity": "sha512-m1lX1XMFHTJ2Ix3tTHb1REkyrp0sTMtQ4OeibocI7fOErzQr28wf9DSDy2bEXl9KT54m3CDplxBNLrP6HrEVxA==" + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -6226,6 +6397,12 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, @@ -6277,6 +6454,18 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, @@ -6287,17 +6476,6 @@ "dev": true, "requires": { "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } } }, "source-list-map": { @@ -6307,10 +6485,9 @@ "dev": true }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { "version": "0.5.2", @@ -6326,19 +6503,12 @@ } }, "source-map-support": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.8.tgz", - "integrity": "sha512-WqAEWPdb78u25RfKzOF0swBpY0dKrNdjc4GvLwm7ScX/o9bj8Eh/YL8mcMhBHYDGl87UkkSXDOFnW4G7GhWhGg==", + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } } }, "source-map-url": { @@ -6630,9 +6800,9 @@ "dev": true }, "svelte": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-2.11.0.tgz", - "integrity": "sha512-lIgtxDkGzLNppVNRtn+t2GZzyumxQj6f/Ola+z7fT6bmisTUxKTFf3wUzOwNcYkQWNIOk2/NkzIa/UO4JQO/bg==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-2.13.2.tgz", + "integrity": "sha512-b27mBrxCR3CK06iiNdwynq34WV9I9VBBoeO+Rg9lTuRiaYmbN1eG1+6llxvn1VjPt6PGm3EskYOfR85piiCUfA==", "dev": true }, "svelte-dev-helper": { @@ -6642,13 +6812,14 @@ "dev": true }, "svelte-loader": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/svelte-loader/-/svelte-loader-2.10.1.tgz", - "integrity": "sha512-Kua8LHwjrHPEGgFUycPo4pg17vzcBu1ptm/2OR/mZfnR1tk1YRMhyoQGvS15Zr36VwRCPPgDYYVYT3peWVseAw==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/svelte-loader/-/svelte-loader-2.11.0.tgz", + "integrity": "sha512-+Q24lBhQ8KGB9flHNXjKqq9TJEIcT3VJlzsxhijqrJ9feK9bUG3TLDlosceoAXvOsjxuaHKN1ZveUbbX7J7zUQ==", "dev": true, "requires": { "loader-utils": "^1.1.0", - "svelte-dev-helper": "^1.1.7" + "require-relative": "^0.8.7", + "svelte-dev-helper": "^1.1.9" } }, "table": { @@ -6772,17 +6943,6 @@ "dev": true, "requires": { "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } } }, "to-regex": { @@ -6805,6 +6965,17 @@ "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } } }, "tough-cookie": { @@ -6915,24 +7086,12 @@ "dev": true }, "uglify-js": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.7.tgz", - "integrity": "sha512-J0M2i1mQA+ze3EdN9SBi751DNdAXmeFLfJrd/MDIkRc3G3Gbb9OPVSx7GIQvVwfWxQARcYV2DTxIkMyDAk3o9Q==", + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.8.tgz", + "integrity": "sha512-WatYTD84gP/867bELqI2F/2xC9PQBETn/L+7RGq9MQOA/7yFBNvY1UwXqvtILeE6n0ITwBXxp34M0/o70dzj6A==", "requires": { - "commander": "~2.16.0", + "commander": "~2.17.1", "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", - "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } } }, "uglifyjs-webpack-plugin": { @@ -6957,12 +7116,6 @@ "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "uglify-es": { "version": "3.3.9", "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", @@ -7071,6 +7224,12 @@ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true } } }, @@ -7233,9 +7392,9 @@ } }, "webpack": { - "version": "4.16.5", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.5.tgz", - "integrity": "sha512-i5cHYHonzSc1zBuwB5MSzW4v9cScZFbprkHK8ZgzPDCRkQXGGpYzPmJhbus5bOrZ0tXTcQp+xyImRSvKb0b+Kw==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.17.1.tgz", + "integrity": "sha512-vdPYogljzWPhFKDj3Gcp01Vqgu7K3IQlybc3XIdKSQHelK1C3eIQuysEUR7MxKJmdandZlQB/9BG2Jb1leJHaw==", "dev": true, "requires": { "@webassemblyjs/ast": "1.5.13", @@ -7283,6 +7442,47 @@ "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", "dev": true }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", @@ -7293,17 +7493,250 @@ "estraverse": "^4.1.1" } }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } } } }, @@ -7317,21 +7750,13 @@ } }, "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.2.0.tgz", + "integrity": "sha512-9BZwxR85dNsjWz3blyxdOhTgtnQvv3OEs5xofI0wPYTwu5kaWxS08UuD1oI7WLBLpRO+ylf0ofnXLXWmGb2WMw==", "dev": true, "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "which": { From ae19288797c109ef47a68a9425688d8e2a1628dc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 16:45:29 -0400 Subject: [PATCH 019/178] update rollup --- api.js | 2 +- package-lock.json | 6 +++--- package.json | 5 ++--- rollup.config.js | 3 +-- rollup.js | 2 +- sapper | 2 +- test/app/app/server.js | 2 +- webpack.js | 4 ++-- webpack/config.js | 2 +- 9 files changed, 13 insertions(+), 15 deletions(-) diff --git a/api.js b/api.js index 9746681..719e270 100644 --- a/api.js +++ b/api.js @@ -1 +1 @@ -module.exports = require('./dist/api.ts.js'); \ No newline at end of file +module.exports = require('./dist/api.js'); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 070086a..dfbec47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5935,9 +5935,9 @@ } }, "rollup": { - "version": "0.59.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.59.4.tgz", - "integrity": "sha512-ISiMqq/aJa+57QxX2MRcvLESHdJ7wSavmr6U1euMr+6UgFe6KM+3QANrYy8LQofwhTC1I7BcAdlLnDiaODs1BA==", + "version": "0.65.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.65.0.tgz", + "integrity": "sha512-en95i7zwW5IiWay6DR/6QV8TxO2LvWuCjHYDcgP96oVG/gPnWWzsxNViObhoJUs17bAj2RgB67WuBuGmysZZcw==", "dev": true, "requires": { "@types/estree": "0.0.39", diff --git a/package.json b/package.json index 54edf36..24fee69 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,12 @@ "name": "sapper", "version": "0.17.1", "description": "Military-grade apps, engineered by Svelte", - "main": "dist/middleware.ts.js", + "main": "dist/middleware.js", "bin": { "sapper": "./sapper" }, "files": [ "*.js", - "*.ts.js", "runtime", "webpack", "sapper", @@ -49,7 +48,7 @@ "pretty-ms": "^3.1.0", "require-relative": "^0.8.7", "rimraf": "^2.6.2", - "rollup": "^0.59.2", + "rollup": "^0.65.0", "rollup-plugin-commonjs": "^9.1.3", "rollup-plugin-json": "^3.0.0", "rollup-plugin-node-resolve": "^3.3.0", diff --git a/rollup.config.js b/rollup.config.js index 17c3bc7..c74d63d 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -52,7 +52,6 @@ export default [ typescript: require('typescript') }) ], - experimentalCodeSplitting: true, - experimentalDynamicImport: true + experimentalCodeSplitting: true } ]; \ No newline at end of file diff --git a/rollup.js b/rollup.js index b37e9ab..41b850d 100644 --- a/rollup.js +++ b/rollup.js @@ -1 +1 @@ -module.exports = require('./dist/rollup.ts.js'); \ No newline at end of file +module.exports = require('./dist/rollup.js'); \ No newline at end of file diff --git a/sapper b/sapper index 72c1eca..e6f3430 100755 --- a/sapper +++ b/sapper @@ -1,2 +1,2 @@ #!/usr/bin/env node -require('./dist/cli.ts.js'); \ No newline at end of file +require('./dist/cli.js'); \ No newline at end of file diff --git a/test/app/app/server.js b/test/app/app/server.js index f09d318..430853c 100644 --- a/test/app/app/server.js +++ b/test/app/app/server.js @@ -2,7 +2,7 @@ import fs from 'fs'; import { resolve } from 'url'; import express from 'express'; import serve from 'serve-static'; -import sapper from '../../../dist/middleware.ts.js'; +import sapper from '../../../dist/middleware.js'; import { Store } from 'svelte/store.js'; import { manifest } from './manifest/server.js'; diff --git a/webpack.js b/webpack.js index bb688ff..8e67b99 100644 --- a/webpack.js +++ b/webpack.js @@ -1,2 +1,2 @@ -// TODO write to this file, instead of webpack.ts.js -module.exports = require('./dist/webpack.ts.js'); \ No newline at end of file +// TODO write to this file, instead of webpack.js +module.exports = require('./dist/webpack.js'); \ No newline at end of file diff --git a/webpack/config.js b/webpack/config.js index 6483555..83dcd37 100644 --- a/webpack/config.js +++ b/webpack/config.js @@ -1,2 +1,2 @@ // TODO deprecate this file in favour of sapper/webpack.js -module.exports = require('../dist/webpack.ts.js'); \ No newline at end of file +module.exports = require('../dist/webpack.js'); \ No newline at end of file From c36df0d65091574178743b55430be274e78e0795 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 16:58:50 -0400 Subject: [PATCH 020/178] try to diagnose latest windows idiocy --- sapper | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sapper b/sapper index e6f3430..363b817 100755 --- a/sapper +++ b/sapper @@ -1,2 +1,6 @@ #!/usr/bin/env node +const fs = require('fs'); +console.log('dist files:'); +console.log(fs.readdirSync('dist')); + require('./dist/cli.js'); \ No newline at end of file From 9d904b39117301bca5d5aef295334a7c702060b2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 17:02:21 -0400 Subject: [PATCH 021/178] argh --- sapper | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sapper b/sapper index 363b817..b9c765e 100755 --- a/sapper +++ b/sapper @@ -1,6 +1,7 @@ #!/usr/bin/env node const fs = require('fs'); +const path = require('path'); console.log('dist files:'); -console.log(fs.readdirSync('dist')); +console.log(fs.readdirSync(path.join(__dirname, 'dist'))); require('./dist/cli.js'); \ No newline at end of file From ddec58ebd4039c08acd11ca1d9535e5b2158d3c5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 17:05:56 -0400 Subject: [PATCH 022/178] ffs --- rollup.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rollup.config.js b/rollup.config.js index c74d63d..2ab91c5 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,3 +1,4 @@ +import path from 'path'; import typescript from 'rollup-plugin-typescript'; import string from 'rollup-plugin-string'; import json from 'rollup-plugin-json'; @@ -36,7 +37,7 @@ export default [ `src/webpack.ts` ], output: { - dir: 'dist', + dir: path.join(__dirname, 'dist'), format: 'cjs', sourcemap: true }, From d6d0a150155660eb00afef02cedaf422e70a1a1a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 17:10:00 -0400 Subject: [PATCH 023/178] WHAT IS HAPPENING --- rollup.config.js | 2 +- sapper | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rollup.config.js b/rollup.config.js index 2ab91c5..b357313 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -16,7 +16,7 @@ export default [ { input: `src/runtime/index.ts`, output: { - file: `runtime.js`, + file: path.join(__dirname, `runtime.js`), format: 'es' }, plugins: [ diff --git a/sapper b/sapper index b9c765e..777f4b4 100755 --- a/sapper +++ b/sapper @@ -1,6 +1,9 @@ #!/usr/bin/env node const fs = require('fs'); const path = require('path'); +console.log('wtf:'); +console.log(process.cwd()); +console.log(fs.readdirSync(__dirname)); console.log('dist files:'); console.log(fs.readdirSync(path.join(__dirname, 'dist'))); From 8226e9bc1fbdca65855ee244e09b3331b72651c3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 17:17:36 -0400 Subject: [PATCH 024/178] starting to lose my sense of humour --- package.json | 2 +- rollup.config.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 24fee69..23425b7 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "scripts": { "cy:open": "cypress open", "test": "mocha --opts mocha.opts", - "pretest": "npm run build", + "pretest": "npm run build && echo \"seriously, wtf\" && ls . && ls dist", "build": "rm -rf dist && rollup -c", "dev": "rollup -cw", "prepublishOnly": "npm test", diff --git a/rollup.config.js b/rollup.config.js index b357313..be1abd3 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -16,7 +16,7 @@ export default [ { input: `src/runtime/index.ts`, output: { - file: path.join(__dirname, `runtime.js`), + file: `./runtime.js`, format: 'es' }, plugins: [ @@ -37,7 +37,7 @@ export default [ `src/webpack.ts` ], output: { - dir: path.join(__dirname, 'dist'), + dir: './dist', format: 'cjs', sourcemap: true }, From f87f0e3b80687d4aa63b26a8c3669c83e8fe5ba7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 17:24:48 -0400 Subject: [PATCH 025/178] stab in the dark --- rollup.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rollup.js b/rollup.js index 41b850d..6883e8d 100644 --- a/rollup.js +++ b/rollup.js @@ -1 +1,3 @@ +console.log(`please tell me windows isn't trying to run this file`); + module.exports = require('./dist/rollup.js'); \ No newline at end of file From 01a709e017563f85654ec7393159dc667eeaecf3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 17:27:34 -0400 Subject: [PATCH 026/178] ffffuuuuuuuuuuu --- rollup.config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rollup.config.js b/rollup.config.js index be1abd3..1251460 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -6,6 +6,8 @@ import resolve from 'rollup-plugin-node-resolve'; import commonjs from 'rollup-plugin-commonjs'; import pkg from './package.json'; +console.log('WE ARE INSIDE THE ROLLUP CONFIG'); + const external = [].concat( Object.keys(pkg.dependencies), Object.keys(process.binding('natives')), From 86dee1704023689ebd79469785015fb6917ef4f7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 17:29:17 -0400 Subject: [PATCH 027/178] come ON --- rollup.js => file-that-is-not-called-rollup.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename rollup.js => file-that-is-not-called-rollup.js (100%) diff --git a/rollup.js b/file-that-is-not-called-rollup.js similarity index 100% rename from rollup.js rename to file-that-is-not-called-rollup.js From bb737eeb3251ddefe413cbd62f23e9f9e9d74fdb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 17:37:20 -0400 Subject: [PATCH 028/178] success i think? --- config/rollup.js | 1 + config/webpack.js | 1 + file-that-is-not-called-rollup.js | 3 --- package.json | 2 +- rollup.config.js | 7 ++----- webpack.js | 3 +-- webpack/config.js | 3 +-- 7 files changed, 7 insertions(+), 13 deletions(-) create mode 100644 config/rollup.js create mode 100644 config/webpack.js delete mode 100644 file-that-is-not-called-rollup.js diff --git a/config/rollup.js b/config/rollup.js new file mode 100644 index 0000000..3b5c8be --- /dev/null +++ b/config/rollup.js @@ -0,0 +1 @@ +module.exports = require('../dist/rollup.js'); \ No newline at end of file diff --git a/config/webpack.js b/config/webpack.js new file mode 100644 index 0000000..15b21cf --- /dev/null +++ b/config/webpack.js @@ -0,0 +1 @@ +module.exports = require('../dist/webpack.js'); \ No newline at end of file diff --git a/file-that-is-not-called-rollup.js b/file-that-is-not-called-rollup.js deleted file mode 100644 index 6883e8d..0000000 --- a/file-that-is-not-called-rollup.js +++ /dev/null @@ -1,3 +0,0 @@ -console.log(`please tell me windows isn't trying to run this file`); - -module.exports = require('./dist/rollup.js'); \ No newline at end of file diff --git a/package.json b/package.json index 23425b7..24fee69 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "scripts": { "cy:open": "cypress open", "test": "mocha --opts mocha.opts", - "pretest": "npm run build && echo \"seriously, wtf\" && ls . && ls dist", + "pretest": "npm run build", "build": "rm -rf dist && rollup -c", "dev": "rollup -cw", "prepublishOnly": "npm test", diff --git a/rollup.config.js b/rollup.config.js index 1251460..c74d63d 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,4 +1,3 @@ -import path from 'path'; import typescript from 'rollup-plugin-typescript'; import string from 'rollup-plugin-string'; import json from 'rollup-plugin-json'; @@ -6,8 +5,6 @@ import resolve from 'rollup-plugin-node-resolve'; import commonjs from 'rollup-plugin-commonjs'; import pkg from './package.json'; -console.log('WE ARE INSIDE THE ROLLUP CONFIG'); - const external = [].concat( Object.keys(pkg.dependencies), Object.keys(process.binding('natives')), @@ -18,7 +15,7 @@ export default [ { input: `src/runtime/index.ts`, output: { - file: `./runtime.js`, + file: `runtime.js`, format: 'es' }, plugins: [ @@ -39,7 +36,7 @@ export default [ `src/webpack.ts` ], output: { - dir: './dist', + dir: 'dist', format: 'cjs', sourcemap: true }, diff --git a/webpack.js b/webpack.js index 8e67b99..98cf8c7 100644 --- a/webpack.js +++ b/webpack.js @@ -1,2 +1 @@ -// TODO write to this file, instead of webpack.js -module.exports = require('./dist/webpack.js'); \ No newline at end of file +throw new Error(`As of Sapper 0.18, webpack config must be loaded from sapper/config/webpack.js`); \ No newline at end of file diff --git a/webpack/config.js b/webpack/config.js index 83dcd37..98cf8c7 100644 --- a/webpack/config.js +++ b/webpack/config.js @@ -1,2 +1 @@ -// TODO deprecate this file in favour of sapper/webpack.js -module.exports = require('../dist/webpack.js'); \ No newline at end of file +throw new Error(`As of Sapper 0.18, webpack config must be loaded from sapper/config/webpack.js`); \ No newline at end of file From 5d0b7af47b738e5c4f011d0a1cf7d786e89d0d3f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 17:40:13 -0400 Subject: [PATCH 029/178] i feel good about this --- sapper | 8 -------- test/app/webpack/client.config.js | 2 +- test/app/webpack/server.config.js | 2 +- test/app/webpack/service-worker.config.js | 2 +- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/sapper b/sapper index 777f4b4..e6f3430 100755 --- a/sapper +++ b/sapper @@ -1,10 +1,2 @@ #!/usr/bin/env node -const fs = require('fs'); -const path = require('path'); -console.log('wtf:'); -console.log(process.cwd()); -console.log(fs.readdirSync(__dirname)); -console.log('dist files:'); -console.log(fs.readdirSync(path.join(__dirname, 'dist'))); - require('./dist/cli.js'); \ No newline at end of file diff --git a/test/app/webpack/client.config.js b/test/app/webpack/client.config.js index 9a17294..9acbe9b 100644 --- a/test/app/webpack/client.config.js +++ b/test/app/webpack/client.config.js @@ -1,4 +1,4 @@ -const config = require('../../../webpack/config.js'); +const config = require('../../../config/webpack.js'); const webpack = require('webpack'); const mode = process.env.NODE_ENV; diff --git a/test/app/webpack/server.config.js b/test/app/webpack/server.config.js index 4f76856..b88d1a6 100644 --- a/test/app/webpack/server.config.js +++ b/test/app/webpack/server.config.js @@ -1,4 +1,4 @@ -const config = require('../../../webpack/config.js'); +const config = require('../../../config/webpack.js'); const sapper_pkg = require('../../../package.json'); module.exports = { diff --git a/test/app/webpack/service-worker.config.js b/test/app/webpack/service-worker.config.js index f95b5f4..aacb312 100644 --- a/test/app/webpack/service-worker.config.js +++ b/test/app/webpack/service-worker.config.js @@ -1,4 +1,4 @@ -const config = require('../../../webpack/config.js'); +const config = require('../../../config/webpack.js'); module.exports = { entry: config.serviceworker.entry(), From 76cb6d97f3b2862714f46fef016199342b16ed83 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 18:01:17 -0400 Subject: [PATCH 030/178] oops --- src/api/utils/copy_shimport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/utils/copy_shimport.ts b/src/api/utils/copy_shimport.ts index 27c247d..dda43f1 100644 --- a/src/api/utils/copy_shimport.ts +++ b/src/api/utils/copy_shimport.ts @@ -4,6 +4,6 @@ export function copy_shimport(dest: string) { const shimport_version = require('shimport/package.json').version; fs.writeFileSync( `${dest}/client/shimport@${shimport_version}.js`, - fs.readFileSync(require.resolve('shimport/index.dev.js')) + fs.readFileSync(require.resolve('shimport/index.js')) ); } \ No newline at end of file From f05a8e52a004a4cb2a31998d36013c8f77303f24 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 18:01:27 -0400 Subject: [PATCH 031/178] deprecate, dont break --- webpack.js | 3 ++- webpack/config.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/webpack.js b/webpack.js index 98cf8c7..96fb4ce 100644 --- a/webpack.js +++ b/webpack.js @@ -1 +1,2 @@ -throw new Error(`As of Sapper 0.18, webpack config must be loaded from sapper/config/webpack.js`); \ No newline at end of file +console.error(`[DEPRECATION] As of Sapper 0.18, webpack config should be loaded from sapper/config/webpack.js`); +module.exports = require('./dist/webpack.js'); \ No newline at end of file diff --git a/webpack/config.js b/webpack/config.js index 98cf8c7..388f348 100644 --- a/webpack/config.js +++ b/webpack/config.js @@ -1 +1,2 @@ -throw new Error(`As of Sapper 0.18, webpack config must be loaded from sapper/config/webpack.js`); \ No newline at end of file +console.error(`[DEPRECATION] As of Sapper 0.18, webpack config should be loaded from sapper/config/webpack.js`); +module.exports = require('../dist/webpack.js'); \ No newline at end of file From 4259fc8e58a755d5b19dc363989eaae39945fc2a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 20:20:02 -0400 Subject: [PATCH 032/178] update shimport, minor tidy up --- package-lock.json | 6 +++--- package.json | 2 +- src/cli/export.ts | 4 ++-- src/core/create_compilers.ts | 3 +-- src/utils.ts | 5 ----- 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index dfbec47..a7e0b36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6299,9 +6299,9 @@ } }, "shimport": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/shimport/-/shimport-0.0.5.tgz", - "integrity": "sha512-m1lX1XMFHTJ2Ix3tTHb1REkyrp0sTMtQ4OeibocI7fOErzQr28wf9DSDy2bEXl9KT54m3CDplxBNLrP6HrEVxA==" + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/shimport/-/shimport-0.0.7.tgz", + "integrity": "sha512-A6aDOxg6fHjmFkc2HL4PUW9Cfb+huHZypApNVxCXoBFz6jO+iAmu2wQeXDjqyho3mF4AMpxZTAC+JbmFpKjwug==" }, "signal-exit": { "version": "3.0.2", diff --git a/package.json b/package.json index 24fee69..9911776 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "dependencies": { "html-minifier": "^3.5.16", - "shimport": "^0.0.5", + "shimport": "^0.0.8", "source-map-support": "^0.5.6", "tslib": "^1.9.1" }, diff --git a/src/cli/export.ts b/src/cli/export.ts index 00dd1d5..51a0246 100644 --- a/src/cli/export.ts +++ b/src/cli/export.ts @@ -2,7 +2,7 @@ import { exporter as _exporter } from '../api/export'; import colors from 'kleur'; import pb from 'pretty-bytes'; import { locations } from '../config'; -import { right_pad } from '../utils'; +import { left_pad } from '../utils'; export function exporter(export_dir: string, { basepath = '', @@ -22,7 +22,7 @@ export function exporter(export_dir: string, { emitter.on('file', event => { const size_color = event.size > 150000 ? colors.bold.red : event.size > 50000 ? colors.bold.yellow : colors.bold.gray; - const size_label = size_color(right_pad(pb(event.size), 10)); + const size_label = size_color(left_pad(pb(event.size), 10)); const file_label = event.status === 200 ? event.file diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index 7d97b10..b5ed879 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -2,9 +2,8 @@ import * as fs from 'fs'; import * as path from 'path'; import colors from 'kleur'; import pb from 'pretty-bytes'; -import { locations } from '../config'; import relative from 'require-relative'; -import { left_pad, right_pad } from '../utils'; +import { left_pad } from '../utils'; let r: any; let wp: any; diff --git a/src/utils.ts b/src/utils.ts index ea70779..7740a8f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,11 +3,6 @@ export function left_pad(str: string, len: number) { return str; } -export function right_pad(str: string, len: number) { - while (str.length < len) str += ' '; - return str; -} - export function repeat(str: string, i: number) { let result = ''; while (i--) result += str; From 708fe4c74b40a44bc1654c962bfaa57103f89d57 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 20:53:48 -0400 Subject: [PATCH 033/178] rebuild lockfile --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index a7e0b36..529a633 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6299,9 +6299,9 @@ } }, "shimport": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/shimport/-/shimport-0.0.7.tgz", - "integrity": "sha512-A6aDOxg6fHjmFkc2HL4PUW9Cfb+huHZypApNVxCXoBFz6jO+iAmu2wQeXDjqyho3mF4AMpxZTAC+JbmFpKjwug==" + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/shimport/-/shimport-0.0.9.tgz", + "integrity": "sha512-y0DHz5ffBuz+iXUQgkqjT3yJRuegeyhHeDdqVdDMVDCeuS0Ex6AFPLFNV228EfPQmkDumraLsN9HBcT1qyLxHw==" }, "signal-exit": { "version": "3.0.2", diff --git a/package.json b/package.json index 9911776..5ede3e2 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "dependencies": { "html-minifier": "^3.5.16", - "shimport": "^0.0.8", + "shimport": "^0.0.9", "source-map-support": "^0.5.6", "tslib": "^1.9.1" }, From d63b9437b551071665a36923320b007ffa5daff2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 21:08:15 -0400 Subject: [PATCH 034/178] -> v0.18.0 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5173907..f80cdf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # sapper changelog +## 0.18.0 + +* Rollup support ([#379](https://github.com/sveltejs/sapper/pull/379)) +* Fail `export` if a page times out (configurable with `--timeout`) ([#378](https://github.com/sveltejs/sapper/pull/378)) + ## 0.17.1 * Print which file is causing build errors/warnings ([#371](https://github.com/sveltejs/sapper/pull/371)) diff --git a/package.json b/package.json index 5ede3e2..de7e864 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.17.1", + "version": "0.18.0", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From 3b098caa6ee182df9efcc9142d3d3404cef9316c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 21:51:08 -0400 Subject: [PATCH 035/178] implement --live and --hot - fixes #385 --- src/api/dev.ts | 41 ++++++++++++++++++++++++----------------- src/cli.ts | 10 +++++++++- src/middleware.ts | 2 +- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/api/dev.ts b/src/api/dev.ts index 1b7a719..dddf7a1 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -29,14 +29,13 @@ class Watcher extends EventEmitter { } port: number; closed: boolean; + live: boolean; + hot: boolean; dev_server: DevServer; proc: child_process.ChildProcess; filewatchers: Array<{ close: () => void }>; - deferreds: { - client: Deferred; - server: Deferred; - }; + deferred: Deferred; crashed: boolean; restarting: boolean; @@ -51,6 +50,8 @@ class Watcher extends EventEmitter { app = locations.app(), dest = locations.dest(), routes = locations.routes(), + live, + hot, bundler, webpack = 'webpack', rollup = 'rollup', @@ -59,6 +60,8 @@ class Watcher extends EventEmitter { app: string, dest: string, routes: string, + live: boolean, + hot: boolean, bundler?: string, webpack: string, rollup: string, @@ -71,6 +74,9 @@ class Watcher extends EventEmitter { this.port = port; this.closed = false; + this.live = live; + this.hot = hot; + this.filewatchers = []; this.current_build = { @@ -159,10 +165,7 @@ class Watcher extends EventEmitter { }) ); - this.deferreds = { - server: new Deferred(), - client: new Deferred() - }; + let deferred = new Deferred(); // TODO watch the configs themselves? const compilers: Compilers = create_compilers(this.bundler, { @@ -187,11 +190,10 @@ class Watcher extends EventEmitter { invalid: filename => { this.restart(filename, 'server'); - this.deferreds.server = new Deferred(); }, handle_result: (result: CompileResult) => { - this.deferreds.client.promise.then(() => { + deferred.promise.then(() => { const restart = () => { log = ''; this.crashed = false; @@ -203,11 +205,15 @@ class Watcher extends EventEmitter { process: this.proc }); - this.deferreds.server.fulfil(); - - this.dev_server.send({ - status: 'completed' - }); + if (this.live) { + this.dev_server.send({ + action: 'reload' + }); + } else { + this.dev_server.send({ + status: 'completed' + }); + } })) .catch(err => { if (this.crashed) return; @@ -263,7 +269,7 @@ class Watcher extends EventEmitter { invalid: filename => { this.restart(filename, 'client'); - this.deferreds.client = new Deferred(); + deferred = new Deferred(); // TODO we should delete old assets. due to a webpack bug // i don't even begin to comprehend, this is apparently @@ -276,7 +282,6 @@ class Watcher extends EventEmitter { shimport: this.bundler === 'rollup' && require('shimport/package.json').version, assets: result.assetsByChunkName }, null, ' ')); - this.deferreds.client.fulfil(); const client_files = result.assets.map((file: string) => `client/${file}`); @@ -285,6 +290,8 @@ class Watcher extends EventEmitter { client_files }); + deferred.fulfil(); + // we need to wait a beat before watching the service // worker, because of some webpack nonsense setTimeout(watch_serviceworker, 100); diff --git a/src/cli.ts b/src/cli.ts index 0568206..74ff87d 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,8 +11,16 @@ prog.command('dev') .describe('Start a development server') .option('-p, --port', 'Specify a port') .option('-o, --open', 'Open a browser window') + .option('-l --live', 'Reload on changes', false) + .option('--hot', 'Use hot module replacement', true) .option('--bundler', 'Specify a bundler (rollup or webpack)') - .action(async (opts: { port: number, open: boolean, bundler?: string }) => { + .action(async (opts: { + port: number, + open: boolean, + live: boolean, + hot: boolean, + bundler?: string + }) => { const { dev } = await import('./cli/dev'); dev(opts); }); diff --git a/src/middleware.ts b/src/middleware.ts index 89dd7ee..9f66494 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -151,7 +151,7 @@ export default function middleware(opts: { serve({ prefix: '/client/', - cache_control: 'max-age=31536000' + cache_control: dev() ? 'no-cache' : 'max-age=31536000' }), get_server_route_handler(manifest.server_routes), From 36f930f48992316cef0738142672c69df0207fff Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 22:19:53 -0400 Subject: [PATCH 036/178] use --live by default, if using Rollup or --no-hot --- src/api/dev.ts | 6 +++--- src/cli.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/api/dev.ts b/src/api/dev.ts index dddf7a1..301a5e7 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -205,13 +205,13 @@ class Watcher extends EventEmitter { process: this.proc }); - if (this.live) { + if (this.hot && this.bundler === 'webpack') { this.dev_server.send({ - action: 'reload' + status: 'completed' }); } else { this.dev_server.send({ - status: 'completed' + action: 'reload' }); } })) diff --git a/src/cli.ts b/src/cli.ts index 74ff87d..f390f0d 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,8 +11,8 @@ prog.command('dev') .describe('Start a development server') .option('-p, --port', 'Specify a port') .option('-o, --open', 'Open a browser window') - .option('-l --live', 'Reload on changes', false) - .option('--hot', 'Use hot module replacement', true) + .option('--hot', 'Use hot module replacement (requires webpack)', true) + .option('-l --live', 'Reload on changes if not using --hot', true) .option('--bundler', 'Specify a bundler (rollup or webpack)') .action(async (opts: { port: number, From 553db81b7b4ceeb5ddba2374fad2e88fb84edf8a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 22:29:46 -0400 Subject: [PATCH 037/178] -> v0.18.1 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f80cdf6..a52adfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.18.1 + +* Add live reloading ([#385](https://github.com/sveltejs/sapper/issues/385)) + ## 0.18.0 * Rollup support ([#379](https://github.com/sveltejs/sapper/pull/379)) diff --git a/package.json b/package.json index de7e864..252dc05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.18.0", + "version": "0.18.1", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From 46bf8f2b78e4faebc098f5ccb3f28adcc547a5d0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 22:43:21 -0400 Subject: [PATCH 038/178] -> v0.18.2 --- CHANGELOG.md | 4 ++++ package.json | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a52adfc..44db5b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.18.2 + +* Update `pkg.files` + ## 0.18.1 * Add live reloading ([#385](https://github.com/sveltejs/sapper/issues/385)) diff --git a/package.json b/package.json index 252dc05..e57759a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.18.1", + "version": "0.18.2", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { @@ -10,6 +10,7 @@ "*.js", "runtime", "webpack", + "config", "sapper", "components", "dist" From 1e220317656ff9af8dce847abf8256df903a0c74 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 29 Aug 2018 22:52:01 -0400 Subject: [PATCH 039/178] -> v0.18.3 --- CHANGELOG.md | 4 ++++ package.json | 2 +- src/rollup.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44db5b1..75c1b68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.18.3 + +* Fix service worker Rollup build config + ## 0.18.2 * Update `pkg.files` diff --git a/package.json b/package.json index e57759a..6e6ba89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.18.2", + "version": "0.18.3", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { diff --git a/src/rollup.ts b/src/rollup.ts index 5ef280a..c23014a 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -38,7 +38,7 @@ export default { output: () => { return { - dir: locations.dest(), + file: `${locations.dest()}/service-worker.js`, format: 'iife' } } From 65d0172abec0e931a65c69780854e2c025c5800b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 18:22:40 -0400 Subject: [PATCH 040/178] handle non-Sapper responses when exporting - fixes #392 --- src/api/export.ts | 54 +++++++++++---------- src/middleware.ts | 24 --------- test/app/app/server.js | 7 +++ test/app/routes/index.html | 1 + test/app/routes/non-sapper-redirect-to.html | 1 + test/common/test.js | 7 ++- 6 files changed, 42 insertions(+), 52 deletions(-) create mode 100644 test/app/routes/non-sapper-redirect-to.html diff --git a/src/api/export.ts b/src/api/export.ts index 0fc877b..4f7e3b2 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -54,7 +54,9 @@ async function execute(emitter: EventEmitter, opts: Opts) { const port = await ports.find(3000); const origin = `http://localhost:${port}`; - const root = new URL(opts.basepath || '', origin); + let basepath = opts.basepath || '/'; + if (!basepath.endsWith('/')) basepath += '/'; + let root = new URL(basepath, origin); emitter.emit('info', { message: `Crawling ${root.href}` @@ -72,29 +74,15 @@ async function execute(emitter: EventEmitter, opts: Opts) { const seen = new Set(); const saved = new Set(); - const deferreds = new Map(); - function get_deferred(pathname: string) { - pathname = pathname.replace(root.pathname, ''); - - if (!deferreds.has(pathname)) { - deferreds.set(pathname, new Deferred()); - } - - return deferreds.get(pathname); - } - - proc.on('message', message => { - if (!message.__sapper__ || message.event !== 'file') return; - - const pathname = new URL(message.url, origin).pathname; + function save(url: string, status: number, type: string, body: string) { + const pathname = new URL(url, origin).pathname; let file = pathname.slice(1); - let { body } = message; if (saved.has(file)) return; saved.add(file); - const is_html = message.type === 'text/html'; + const is_html = type === 'text/html'; if (is_html) { file = file === '' ? 'index.html' : `${file}/index.html`; @@ -104,12 +92,15 @@ async function execute(emitter: EventEmitter, opts: Opts) { emitter.emit('file', { file, size: body.length, - status: message.status + status }); sander.writeFileSync(export_dir, file, body); + } - get_deferred(pathname).fulfil(); + proc.on('message', message => { + if (!message.__sapper__ || message.event !== 'file') return; + save(message.url, message.status, message.type, message.body); }); async function handle(url: URL) { @@ -118,25 +109,27 @@ async function execute(emitter: EventEmitter, opts: Opts) { if (seen.has(pathname)) return; seen.add(pathname); - const deferred = get_deferred(pathname); - 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), + fetch(url.href, { + redirect: 'manual' + }), timeout_deferred.promise ]); clearTimeout(timeout); // prevent it hanging at the end + let type = r.headers.get('Content-Type'); + let body = await r.text(); + const range = ~~(r.status / 100); if (range === 2) { - if (r.headers.get('Content-Type') === 'text/html') { - const body = await r.text(); + if (type === 'text/html') { const urls: URL[] = []; const cleaned = clean_html(body); @@ -162,7 +155,16 @@ async function execute(emitter: EventEmitter, opts: Opts) { } } - await deferred.promise; + if (range === 3) { + const location = r.headers.get('Location'); + + type = 'text/html'; + body = ``; + + await handle(new URL(location, root)); + } + + save(pathname, r.status, type, body); } return ports.wait(port) diff --git a/src/middleware.ts b/src/middleware.ts index 9f66494..7a95873 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -414,18 +414,6 @@ function get_page_handler( res.setHeader('Location', location); res.end(); - if (process.send) { - process.send({ - __sapper__: true, - event: 'file', - url: req.url, - method: req.method, - status: redirect.statusCode, - type: 'text/html', - body: `` - }); - } - return; } @@ -512,18 +500,6 @@ function get_page_handler( res.statusCode = status; res.end(body); - - if (process.send) { - process.send({ - __sapper__: true, - event: 'file', - url: req.url, - method: req.method, - status, - type: 'text/html', - body - }); - } }).catch(err => { if (error) { // we encountered an error while rendering the error page — oops diff --git a/test/app/app/server.js b/test/app/app/server.js index 430853c..0595bdf 100644 --- a/test/app/app/server.js +++ b/test/app/app/server.js @@ -108,6 +108,13 @@ const middlewares = [ }), ]; +app.get(`${BASEPATH}/non-sapper-redirect-from`, (req, res) => { + res.writeHead(301, { + Location: `${BASEPATH}/non-sapper-redirect-to` + }); + res.end(); +}); + if (BASEPATH) { app.use(BASEPATH, ...middlewares); } else { diff --git a/test/app/routes/index.html b/test/app/routes/index.html index b695afe..e5eecde 100644 --- a/test/app/routes/index.html +++ b/test/app/routes/index.html @@ -7,6 +7,7 @@ home about slow preload +redirect redirect redirect (root) broken link diff --git a/test/app/routes/non-sapper-redirect-to.html b/test/app/routes/non-sapper-redirect-to.html new file mode 100644 index 0000000..eeb0dfc --- /dev/null +++ b/test/app/routes/non-sapper-redirect-to.html @@ -0,0 +1 @@ +

redirected

\ No newline at end of file diff --git a/test/common/test.js b/test/common/test.js index 2db23ff..2ad9730 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -2,9 +2,7 @@ const fs = require('fs'); const path = require('path'); const assert = require('assert'); const Nightmare = require('nightmare'); -const serve = require('serve-static'); const walkSync = require('walk-sync'); -const fetch = require('node-fetch'); const rimraf = require('rimraf'); const ports = require('port-authority'); @@ -83,6 +81,11 @@ function testExport({ basepath = '' }) { 'about/index.html', 'slow-preload/index.html', + 'redirect-from/index.html', + 'redirect-to/index.html', + 'non-sapper-redirect-from/index.html', + 'non-sapper-redirect-to/index.html', + 'blog/index.html', 'blog/a-very-long-post/index.html', 'blog/how-can-i-get-involved/index.html', From 70fd7038b0d6a7038cca6fb1a144f54139e8a62f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 18:38:46 -0400 Subject: [PATCH 041/178] skip webpack annotations when using Rollup --- src/api/build.ts | 2 +- src/api/dev.ts | 6 +++--- src/core/create_manifests.ts | 19 +++++++++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/api/build.ts b/src/api/build.ts index 941c126..572685a 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -55,7 +55,7 @@ async function execute(emitter: EventEmitter, { const route_objects = create_routes(); // create app/manifest/client.js and app/manifest/server.js - create_main_manifests({ routes: route_objects }); + create_main_manifests({ bundler, routes: route_objects }); const { client, server, serviceworker } = create_compilers(validate_bundler(bundler), { webpack, rollup }); diff --git a/src/api/dev.ts b/src/api/dev.ts index 301a5e7..b15c377 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -124,7 +124,7 @@ class Watcher extends EventEmitter { try { const routes = create_routes(); - create_main_manifests({ routes, dev_port }); + create_main_manifests({ bundler: this.bundler, routes, dev_port }); } catch (err) { this.emit('fatal', { message: err.message @@ -145,11 +145,11 @@ class Watcher extends EventEmitter { }, () => { const routes = create_routes(); - create_main_manifests({ routes, dev_port }); + create_main_manifests({ bundler: this.bundler, routes, dev_port }); try { const routes = create_routes(); - create_main_manifests({ routes, dev_port }); + create_main_manifests({ bundler: this.bundler, routes, dev_port }); } catch (err) { this.emit('error', { message: err.message diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index 8152312..518f692 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -5,7 +5,8 @@ import { posixify, write_if_changed } from './utils'; import { dev, locations } from '../config'; import { Page, PageComponent, ServerRoute } from '../interfaces'; -export function create_main_manifests({ routes, dev_port }: { +export function create_main_manifests({ bundler, routes, dev_port }: { + bundler: string, routes: { components: PageComponent[], pages: Page[], server_routes: ServerRoute[] }; dev_port?: number; }) { @@ -14,7 +15,7 @@ export function create_main_manifests({ routes, dev_port }: { const path_to_routes = path.relative(manifest_dir, locations.routes()); - const client_manifest = generate_client(routes, path_to_routes, dev_port); + const client_manifest = generate_client(routes, path_to_routes, bundler, dev_port); const server_manifest = generate_server(routes, path_to_routes); write_if_changed( @@ -48,6 +49,7 @@ export function create_serviceworker_manifest({ routes, client_files }: { function generate_client( routes: { root: PageComponent, components: PageComponent[], pages: Page[], server_routes: ServerRoute[] }, path_to_routes: string, + bundler: string, dev_port?: number ) { const page_ids = new Set(routes.pages.map(page => @@ -61,10 +63,15 @@ function generate_client( import root from '${get_file(path_to_routes, routes.root)}'; import error from '${posixify(`${path_to_routes}/_error.html`)}'; - ${routes.components.map(component => - `const ${component.name} = () => - import(/* webpackChunkName: "${component.name}" */ '${get_file(path_to_routes, component)}');`) - .join('\n')} + ${routes.components.map(component => { + const annotation = bundler === 'webpack' + ? `/* webpackChunkName: "${component.name}" */ ` + : ''; + + const source = get_file(path_to_routes, component); + + return `const ${component.name} = () => import(${annotation}'${source}');`; + }).join('\n')} export const manifest = { ignore: [${server_routes_to_ignore.map(route => route.pattern).join(', ')}], From 7798f8f684d58b666e8d4c4c2f313606012bc0e3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 18:41:37 -0400 Subject: [PATCH 042/178] minor tidy up --- src/api/build.ts | 4 ++-- src/api/dev.ts | 2 +- src/core/create_compilers.ts | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/api/build.ts b/src/api/build.ts index 572685a..5fa9e6a 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -69,7 +69,7 @@ async function execute(emitter: EventEmitter, { fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify({ bundler, shimport: bundler === 'rollup' && require('shimport/package.json').version, - assets: client_result.assetsByChunkName + assets: client_result.assets })); const server_stats = await server.compile(); @@ -84,7 +84,7 @@ async function execute(emitter: EventEmitter, { if (serviceworker) { create_serviceworker_manifest({ routes: route_objects, - client_files: client_result.assets.map((file: string) => `client/${file}`) + client_files: client_result.chunks.map((file: string) => `client/${file}`) }); serviceworker_stats = await serviceworker.compile(); diff --git a/src/api/dev.ts b/src/api/dev.ts index b15c377..a904bf2 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -280,7 +280,7 @@ class Watcher extends EventEmitter { fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify({ bundler: this.bundler, shimport: this.bundler === 'rollup' && require('shimport/package.json').version, - assets: result.assetsByChunkName + assets: result.assets }, null, ' ')); const client_files = result.assets.map((file: string) => `client/${file}`); diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index b5ed879..40c99a1 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -17,8 +17,8 @@ export class CompileResult { duration: number; errors: CompileError[]; warnings: CompileError[]; - assets: string[]; - assetsByChunkName: Record; + chunks: string[]; + assets: Record; } class RollupResult extends CompileResult { @@ -32,15 +32,15 @@ class RollupResult extends CompileResult { this.errors = compiler.errors.map(munge_rollup_warning_or_error); this.warnings = compiler.warnings.map(munge_rollup_warning_or_error); // TODO emit this as they happen - this.assets = compiler.chunks.map(chunk => chunk.fileName); + this.chunks = compiler.chunks.map(chunk => chunk.fileName); // TODO populate this properly. We don't have namedcompiler. chunks, as in // webpack, but we can have a route -> [chunk] map or something - this.assetsByChunkName = {}; + this.assets = {}; compiler.chunks.forEach(chunk => { if (compiler.input in chunk.modules) { - this.assetsByChunkName.main = chunk.fileName; + this.assets.main = chunk.fileName; } }); @@ -110,8 +110,8 @@ class WebpackResult extends CompileResult { this.duration = info.time; - this.assets = info.assets.map((chunk: { name: string }) => chunk.name); - this.assetsByChunkName = info.assetsByChunkName; + this.chunks = info.assets.map((chunk: { name: string }) => chunk.name); + this.assets = info.assetsByChunkName; } print() { From bc31c73c33089f87945f7956630f70cbe59412fe Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 18:48:10 -0400 Subject: [PATCH 043/178] omit trailing slash from server route matchers - fixes #390 --- src/core/create_routes.ts | 10 +++++----- test/unit/create_routes/index.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/create_routes.ts b/src/core/create_routes.ts index c69cac1..96ac8c0 100644 --- a/src/core/create_routes.ts +++ b/src/core/create_routes.ts @@ -128,12 +128,12 @@ export default function create_routes(cwd = locations.routes()) { components.push(component); if (item.basename === 'index.html') { pages.push({ - pattern: get_pattern(parent_segments), + pattern: get_pattern(parent_segments, true), parts }); } else { pages.push({ - pattern: get_pattern(segments), + pattern: get_pattern(segments, true), parts }); } @@ -142,7 +142,7 @@ export default function create_routes(cwd = locations.routes()) { else { server_routes.push({ name: `route_${get_slug(item.file)}`, - pattern: get_pattern(segments), + pattern: get_pattern(segments, false), file: item.file, params: params }); @@ -276,7 +276,7 @@ function get_slug(file: string) { }); } -function get_pattern(segments: Part[][]) { +function get_pattern(segments: Part[][], add_trailing_slash: boolean) { return new RegExp( `^` + segments.map(segment => { @@ -290,6 +290,6 @@ function get_pattern(segments: Part[][]) { .replace(/%5D/g, ']'); }).join(''); }).join('') + - '\\\/?$' + (add_trailing_slash ? '\\\/?$' : '$') ); } \ No newline at end of file diff --git a/test/unit/create_routes/index.ts b/test/unit/create_routes/index.ts index 6c61bc3..02f8483 100644 --- a/test/unit/create_routes/index.ts +++ b/test/unit/create_routes/index.ts @@ -53,14 +53,14 @@ describe('create_routes', () => { assert.deepEqual(server_routes, [ { name: 'route_blog_json', - pattern: /^\/blog.json\/?$/, + pattern: /^\/blog.json$/, file: 'blog/index.json.js', params: [] }, { name: 'route_blog_$slug_json', - pattern: /^\/blog\/([^\/]+?).json\/?$/, + pattern: /^\/blog\/([^\/]+?).json$/, file: 'blog/[slug].json.js', params: ['slug'] } From e170e4af9b170559aebf0b9a8e546cbd6babe19a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 19:29:36 -0400 Subject: [PATCH 044/178] use builtin url module --- src/api/export.ts | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/api/export.ts b/src/api/export.ts index 4f7e3b2..408bb18 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -1,7 +1,7 @@ import * as child_process from 'child_process'; import * as path from 'path'; import * as sander from 'sander'; -import URL from 'url-parse'; +import * as url from 'url'; import fetch from 'node-fetch'; import * as ports from 'port-authority'; import { EventEmitter } from 'events'; @@ -34,6 +34,12 @@ export function exporter(opts: Opts) { return emitter; } +function resolve(from: string, to: string) { + return url.parse(url.resolve(from, to)); +} + +type URL = url.UrlWithStringQuery; + async function execute(emitter: EventEmitter, opts: Opts) { const export_dir = path.join(opts.dest, opts.basepath); @@ -53,10 +59,12 @@ async function execute(emitter: EventEmitter, opts: Opts) { const port = await ports.find(3000); - const origin = `http://localhost:${port}`; - let basepath = opts.basepath || '/'; - if (!basepath.endsWith('/')) basepath += '/'; - let root = new URL(basepath, origin); + const protocol = 'http:'; + const host = `localhost:${port}`; + const origin = `${protocol}//${host}`; + + const root = resolve(origin, opts.basepath || ''); + if (!root.href.endsWith('/')) root.href += '/'; emitter.emit('info', { message: `Crawling ${root.href}` @@ -75,8 +83,8 @@ async function execute(emitter: EventEmitter, opts: Opts) { const seen = new Set(); const saved = new Set(); - function save(url: string, status: number, type: string, body: string) { - const pathname = new URL(url, origin).pathname; + function save(path: string, status: number, type: string, body: string) { + const { pathname } = resolve(origin, path); let file = pathname.slice(1); if (saved.has(file)) return; @@ -136,7 +144,7 @@ async function execute(emitter: EventEmitter, opts: Opts) { const base_match = //m.exec(cleaned); const base_href = base_match && get_href(base_match[1]); - const base = new URL(base_href || '/', url.href); + const base = resolve(url.href, base_href); let match; let pattern = //gm; @@ -146,8 +154,11 @@ async function execute(emitter: EventEmitter, opts: Opts) { const href = get_href(attrs); if (href) { - const url = new URL(href, base.href); - if (url.origin === origin) urls.push(url); + const url = resolve(base.href, href); + + if (url.protocol === protocol && url.host === host) { + urls.push(url); + } } } @@ -161,7 +172,7 @@ async function execute(emitter: EventEmitter, opts: Opts) { type = 'text/html'; body = ``; - await handle(new URL(location, root)); + await handle(resolve(root.href, location)); } save(pathname, r.status, type, body); From 8c2688b1be7bc50a6331723241c9bfda58b14342 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 19:34:59 -0400 Subject: [PATCH 045/178] remove url-parse --- package-lock.json | 24 +----------------------- package.json | 1 - 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 529a633..858914d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.17.1", + "version": "0.18.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5574,12 +5574,6 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, - "querystringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", - "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==", - "dev": true - }, "randomatic": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", @@ -5872,12 +5866,6 @@ "resolve-from": "^1.0.0" } }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, "resolve": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", @@ -7285,16 +7273,6 @@ } } }, - "url-parse": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz", - "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", - "dev": true, - "requires": { - "querystringify": "^2.0.0", - "requires-port": "^1.0.0" - } - }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", diff --git a/package.json b/package.json index 6e6ba89..bc689dd 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "tiny-glob": "^0.2.2", "ts-node": "^7.0.1", "typescript": "^2.8.3", - "url-parse": "^1.2.0", "walk-sync": "^0.3.2", "webpack": "^4.8.3", "webpack-format-messages": "^2.0.1" From 54da5244672a322462c4e9e95c00a51f9f579db1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 19:46:14 -0400 Subject: [PATCH 046/178] implement --dev-port flag - fixes #381 --- src/api/dev.ts | 15 ++++++++++----- src/cli.ts | 4 +++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/api/dev.ts b/src/api/dev.ts index 301a5e7..3813fe8 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -29,6 +29,8 @@ class Watcher extends EventEmitter { } port: number; closed: boolean; + + dev_port: number; live: boolean; hot: boolean; @@ -50,6 +52,7 @@ class Watcher extends EventEmitter { app = locations.app(), dest = locations.dest(), routes = locations.routes(), + 'dev-port': dev_port, live, hot, bundler, @@ -60,6 +63,7 @@ class Watcher extends EventEmitter { app: string, dest: string, routes: string, + 'dev-port': number, live: boolean, hot: boolean, bundler?: string, @@ -74,6 +78,7 @@ class Watcher extends EventEmitter { this.port = port; this.closed = false; + this.dev_port = dev_port; this.live = live; this.hot = hot; @@ -120,11 +125,11 @@ class Watcher extends EventEmitter { mkdirp.sync(`${dest}/client`); if (this.bundler === 'rollup') copy_shimport(dest); - const dev_port = await ports.find(10000); + if (!this.dev_port) this.dev_port = await ports.find(10000); try { const routes = create_routes(); - create_main_manifests({ routes, dev_port }); + create_main_manifests({ routes, dev_port: this.dev_port }); } catch (err) { this.emit('fatal', { message: err.message @@ -132,7 +137,7 @@ class Watcher extends EventEmitter { return; } - this.dev_server = new DevServer(dev_port); + this.dev_server = new DevServer(this.dev_port); this.filewatchers.push( watch_dir( @@ -145,11 +150,11 @@ class Watcher extends EventEmitter { }, () => { const routes = create_routes(); - create_main_manifests({ routes, dev_port }); + create_main_manifests({ routes, dev_port: this.dev_port }); try { const routes = create_routes(); - create_main_manifests({ routes, dev_port }); + create_main_manifests({ routes, dev_port: this.dev_port }); } catch (err) { this.emit('error', { message: err.message diff --git a/src/cli.ts b/src/cli.ts index f390f0d..41a208e 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,12 +11,14 @@ prog.command('dev') .describe('Start a development server') .option('-p, --port', 'Specify a port') .option('-o, --open', 'Open a browser window') + .option('--dev-port', 'Specify a port for development server') .option('--hot', 'Use hot module replacement (requires webpack)', true) - .option('-l --live', 'Reload on changes if not using --hot', true) + .option('--live', 'Reload on changes if not using --hot', true) .option('--bundler', 'Specify a bundler (rollup or webpack)') .action(async (opts: { port: number, open: boolean, + 'dev-port': number, live: boolean, hot: boolean, bundler?: string From 002718b60923d4b87b3dbf0b78a99e91e1b10b57 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 20:00:58 -0400 Subject: [PATCH 047/178] -> v0.18.4 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c1b68..1cf8b81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # sapper changelog +## 0.18.4 + +* Handle non-Sapper responses when exporting ([#382](https://github.com/sveltejs/sapper/issues/392)) +* Add `--dev-port` flag to `sapper dev` ([#381](https://github.com/sveltejs/sapper/issues/381)) + ## 0.18.3 * Fix service worker Rollup build config diff --git a/package.json b/package.json index bc689dd..02f3c8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.18.3", + "version": "0.18.4", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From 7cc2a03aae9a35e94241e27f80b1004c30411364 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 20:38:40 -0400 Subject: [PATCH 048/178] oops --- src/api/dev.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/dev.ts b/src/api/dev.ts index e3c4f4b..8b3d30f 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -288,7 +288,7 @@ class Watcher extends EventEmitter { assets: result.assets }, null, ' ')); - const client_files = result.assets.map((file: string) => `client/${file}`); + const client_files = result.chunks.map((file: string) => `client/${file}`); create_serviceworker_manifest({ routes: create_routes(), From afcd643035f5d0cd6277ad6f255ba8a37fff4209 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 20:41:03 -0400 Subject: [PATCH 049/178] -> v0.18.5 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cf8b81..3dbb015 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.18.5 + +* Bugfix + ## 0.18.4 * Handle non-Sapper responses when exporting ([#382](https://github.com/sveltejs/sapper/issues/392)) diff --git a/package.json b/package.json index 02f3c8c..9f192ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.18.4", + "version": "0.18.5", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From e00b315decd53f0af1a2d5280fdf6d2d7176792a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 22:58:07 -0400 Subject: [PATCH 050/178] emit legacy build --- src/api/build.ts | 32 +++++++++++++++++++++++++++----- src/cli.ts | 9 ++++++++- src/cli/build.ts | 7 ++++++- src/rollup.ts | 5 ++++- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/api/build.ts b/src/api/build.ts index 5fa9e6a..06ed54a 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -5,9 +5,7 @@ import rimraf from 'rimraf'; import { EventEmitter } from 'events'; import minify_html from './utils/minify_html'; import { create_compilers, create_main_manifests, create_routes, create_serviceworker_manifest } from '../core'; -import { Compilers, Compiler } from '../core/create_compilers'; import * as events from './interfaces'; -import validate_bundler from '../cli/utils/validate_bundler'; import { copy_shimport } from './utils/copy_shimport'; export function build(opts: {}) { @@ -30,6 +28,7 @@ export function build(opts: {}) { async function execute(emitter: EventEmitter, { dest = 'build', app = 'app', + legacy, bundler, webpack = 'webpack', rollup = 'rollup', @@ -57,7 +56,7 @@ async function execute(emitter: EventEmitter, { // create app/manifest/client.js and app/manifest/server.js create_main_manifests({ bundler, routes: route_objects }); - const { client, server, serviceworker } = create_compilers(validate_bundler(bundler), { webpack, rollup }); + const { client, server, serviceworker } = create_compilers(bundler, { webpack, rollup }); const client_result = await client.compile(); emitter.emit('build', { @@ -66,11 +65,34 @@ async function execute(emitter: EventEmitter, { result: client_result }); - fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify({ + const build_info: { + bundler: string; + shimport: string; + assets: Record; + legacy_assets?: Record; + } = { bundler, shimport: bundler === 'rollup' && require('shimport/package.json').version, assets: client_result.assets - })); + }; + + if (legacy) { + process.env.SAPPER_LEGACY_BUILD = 'true'; + const { client } = create_compilers(bundler, { webpack, rollup }); + + const client_result = await client.compile(); + + emitter.emit('build', { + type: 'client (legacy)', + // TODO duration/warnings + result: client_result + }); + + build_info.legacy_assets = client_result.assets; + delete process.env.SAPPER_LEGACY_BUILD; + } + + fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify(build_info)); const server_stats = await server.compile(); emitter.emit('build', { diff --git a/src/cli.ts b/src/cli.ts index 41a208e..96cd811 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -31,8 +31,13 @@ prog.command('build [dest]') .describe('Create a production-ready version of your app') .option('-p, --port', 'Default of process.env.PORT', '3000') .option('--bundler', 'Specify a bundler (rollup or webpack, blank for auto)') + .option('--legacy', 'Create separate legacy build') .example(`build custom-dir -p 4567`) - .action(async (dest = 'build', opts: { port: string, bundler?: string }) => { + .action(async (dest = 'build', opts: { + port: string, + legacy: boolean, + bundler?: string + }) => { console.log(`> Building...`); process.env.NODE_ENV = process.env.NODE_ENV || 'production'; @@ -78,9 +83,11 @@ prog.command('export [dest]') .option('--build-dir', 'Specify a custom temporary build directory', '.sapper/prod') .option('--basepath', 'Specify a base path') .option('--timeout', 'Milliseconds to wait for a page (--no-timeout to disable)', 5000) + .option('--legacy', 'Create separate legacy build') .option('--bundler', 'Specify a bundler (rollup or webpack, blank for auto)') .action(async (dest = 'export', opts: { build: boolean, + legacy: boolean, bundler?: string, 'build-dir': string, basepath?: string, diff --git a/src/cli/build.ts b/src/cli/build.ts index 6b291b6..d13c00e 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -4,15 +4,20 @@ import { locations } from '../config'; import validate_bundler from './utils/validate_bundler'; import { repeat } from '../utils'; -export function build(opts: { bundler?: string }) { +export function build(opts: { bundler?: string, legacy?: boolean }) { const bundler = validate_bundler(opts.bundler); + if (opts.legacy && bundler === 'webpack') { + throw new Error(`Legacy builds are not supported for projects using webpack`); + } + return new Promise((fulfil, reject) => { try { const emitter = _build({ dest: locations.dest(), app: locations.app(), routes: locations.routes(), + legacy: opts.legacy, bundler, webpack: 'webpack', rollup: 'rollup' diff --git a/src/rollup.ts b/src/rollup.ts index c23014a..399c1a7 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -9,8 +9,11 @@ export default { }, output: () => { + let dir = `${locations.dest()}/client`; + if (process.env.SAPPER_LEGACY_BUILD) dir += `/legacy`; + return { - dir: `${locations.dest()}/client`, + dir, entryFileNames: '[name].[hash].js', chunkFileNames: '[name].[hash].js', format: 'esm' From c7cce985e3892c686554c90eb20bb9dfb9e0215a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Aug 2018 23:13:34 -0400 Subject: [PATCH 051/178] serve legacy assets if such there be --- src/middleware.ts | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index 7a95873..900bbf1 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -306,7 +306,8 @@ function get_page_handler( const build_info: { bundler: 'rollup' | 'webpack', shimport: string | null, - assets: Record + assets: Record, + legacy_assets?: Record } = get_build_info(); res.setHeader('Content-Type', 'text/html'); @@ -472,14 +473,7 @@ function get_page_handler( store }); - const file = [].concat(build_info.assets.main).filter(file => file && /\.js$/.test(file))[0]; - const main = `${req.baseUrl}/client/${file}`; - - const script = build_info.bundler === 'rollup' - ? `` - : ``; - - let inline_script = `__SAPPER__={${[ + let script = `__SAPPER__={${[ error && `error:1`, `baseUrl:"${req.baseUrl}"`, serialized.preloaded && `preloaded:${serialized.preloaded}`, @@ -488,12 +482,26 @@ function get_page_handler( const has_service_worker = fs.existsSync(path.join(locations.dest(), 'service-worker.js')); if (has_service_worker) { - inline_script += `if ('serviceWorker' in navigator) navigator.serviceWorker.register('${req.baseUrl}/service-worker.js');`; + script += `if('serviceWorker' in navigator)navigator.serviceWorker.register('${req.baseUrl}/service-worker.js');`; + } + + const file = [].concat(build_info.assets.main).filter(file => file && /\.js$/.test(file))[0]; + const main = `${req.baseUrl}/client/${file}`; + + if (build_info.bundler === 'rollup') { + if (build_info.legacy_assets) { + const legacy_main = `${req.baseUrl}/client/legacy/${build_info.legacy_assets.main}`; + script += `(function(){try{eval("async function x(){}");var main="${main}"}catch(e){main="${legacy_main}"};try{new Function("import('"+main+"')")();}catch(e){var s=document.createElement("script");s.src="${req.baseUrl}/client/shimport@${build_info.shimport}.js";s.setAttribute("data-main",main);document.head.appendChild(s);}}());`; + } else { + script += `try{new Function("import('${main}')")();}catch(e){var s=document.createElement("script");s.src="${req.baseUrl}/client/shimport@${build_info.shimport}.js";s.setAttribute("data-main","${main}");document.head.appendChild(s);}`; + } + } else { + script += `${script}`) + .replace('%sapper.scripts%', () => ``) .replace('%sapper.html%', () => html) .replace('%sapper.head%', () => `${head}`) .replace('%sapper.styles%', () => (css && css.code ? `` : '')); From 15cc4bf296ea37420c98b176e082e3ad127db010 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 31 Aug 2018 11:13:03 -0400 Subject: [PATCH 052/178] bundle webpack-format-messages --- src/core/create_compilers.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index 40c99a1..6c91caf 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -3,6 +3,7 @@ import * as path from 'path'; import colors from 'kleur'; import pb from 'pretty-bytes'; import relative from 'require-relative'; +import format_messages from 'webpack-format-messages'; import { left_pad } from '../utils'; let r: any; @@ -101,8 +102,6 @@ class WebpackResult extends CompileResult { const info = stats.toJson(); - // TODO use import() - const format_messages = require('webpack-format-messages'); const messages = format_messages(stats); this.errors = messages.errors.map(munge_webpack_warning_or_error); From 5be3809d9e18d48d52a3dd69f058f38203ec09ad Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 31 Aug 2018 11:13:24 -0400 Subject: [PATCH 053/178] -> v0.18.6 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dbb015..a7b39ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.18.6 + +* Bundle missing dependency + ## 0.18.5 * Bugfix diff --git a/package.json b/package.json index 9f192ff..dcf85fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.18.5", + "version": "0.18.6", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From 96fc19e939000ae5661429de5a3e2b95aa760af9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 31 Aug 2018 16:32:56 -0400 Subject: [PATCH 054/178] update shimport --- package-lock.json | 50 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 858914d..3a588fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.18.3", + "version": "0.18.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -55,9 +55,9 @@ "dev": true }, "@types/node": { - "version": "10.9.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.9.3.tgz", - "integrity": "sha512-DOzWZKUnmFYG0KUOs+9HEBju2QhBU6oM2zeluunQNt0vnJvnkHvtDNlQPZDkTrkC5pZrNx1TPqeL137zciXZMQ==", + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.9.4.tgz", + "integrity": "sha512-fCHV45gS+m3hH17zgkgADUSi2RR1Vht6wOZ0jyHP8rjiQra9f+mIcgwPQHllmDocYOstIEbKlxbFDYlgrTPYqw==", "dev": true }, "@types/rimraf": { @@ -1154,7 +1154,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -1594,7 +1594,7 @@ }, "compare-versions": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-2.0.1.tgz", + "resolved": "http://registry.npmjs.org/compare-versions/-/compare-versions-2.0.1.tgz", "integrity": "sha1-Htwfk2h/2XoyXFn1XkWgfbEGrKY=", "dev": true }, @@ -2071,9 +2071,9 @@ }, "dependencies": { "@types/node": { - "version": "8.10.28", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.28.tgz", - "integrity": "sha512-iHsAzDg3OLH7JP+wipniUULHoDSWLgEDYOvsar6/mpAkTJd9/n23Ap8ikruMlvRTqMv/LXrflH9v/AfiEqaBGg==", + "version": "8.10.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.29.tgz", + "integrity": "sha512-zbteaWZ2mdduacm0byELwtRyhYE40aK+pAanQk415gr1eRuu67x7QGOLmn8jz5zI8LDK7d0WI/oT6r5Trz4rzQ==", "dev": true } } @@ -2097,7 +2097,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -2538,7 +2538,7 @@ }, "external-editor": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { @@ -4270,9 +4270,9 @@ } }, "make-error": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", - "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", "dev": true }, "mamacro": { @@ -4397,7 +4397,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -4537,7 +4537,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -4991,7 +4991,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -5682,7 +5682,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -6287,9 +6287,9 @@ } }, "shimport": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/shimport/-/shimport-0.0.9.tgz", - "integrity": "sha512-y0DHz5ffBuz+iXUQgkqjT3yJRuegeyhHeDdqVdDMVDCeuS0Ex6AFPLFNV228EfPQmkDumraLsN9HBcT1qyLxHw==" + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/shimport/-/shimport-0.0.10.tgz", + "integrity": "sha512-3xPFDLmcLj87sx0OwA60qbloMQUsu6VGF97IG4RqxTf91sGeiaaXOPxM1PoQHbaTm4TOhH8zosokqLAZtuNGnA==" }, "signal-exit": { "version": "3.0.2", @@ -7009,7 +7009,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -7074,9 +7074,9 @@ "dev": true }, "uglify-js": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.8.tgz", - "integrity": "sha512-WatYTD84gP/867bELqI2F/2xC9PQBETn/L+7RGq9MQOA/7yFBNvY1UwXqvtILeE6n0ITwBXxp34M0/o70dzj6A==", + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", "requires": { "commander": "~2.17.1", "source-map": "~0.6.1" diff --git a/package.json b/package.json index 9f192ff..c922e35 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "html-minifier": "^3.5.16", - "shimport": "^0.0.9", + "shimport": "^0.0.10", "source-map-support": "^0.5.6", "tslib": "^1.9.1" }, From afba0491eda264a54aabdff0328608603e5cca3b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 31 Aug 2018 16:40:50 -0400 Subject: [PATCH 055/178] -> v0.18.7 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7b39ce..bb6152f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.18.7 + +* Support differential bundling for Rollup apps via a `--legacy` flag ([#280](https://github.com/sveltejs/sapper/issues/280)) + ## 0.18.6 * Bundle missing dependency diff --git a/package.json b/package.json index 7aa8a88..a14a964 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.18.6", + "version": "0.18.7", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From bebb0dd59519b59c67a427706885a4e1255c5ff7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 14:46:27 -0400 Subject: [PATCH 056/178] CSS extraction and code-splitting closes #388 --- package-lock.json | 14 +- package.json | 2 + src/api/build.ts | 62 ++- src/api/dev.ts | 41 +- src/api/find_page.ts | 4 +- src/api/interfaces.ts | 2 +- src/cli/build.ts | 5 +- src/core.ts | 4 +- src/core/create_compilers.ts | 377 ------------------ src/core/create_compilers/RollupCompiler.ts | 160 ++++++++ src/core/create_compilers/RollupResult.ts | 111 ++++++ src/core/create_compilers/WebpackCompiler.ts | 48 +++ src/core/create_compilers/WebpackResult.ts | 73 ++++ src/core/create_compilers/extract_css.ts | 230 +++++++++++ src/core/create_compilers/index.ts | 37 ++ src/core/create_compilers/interfaces.ts | 39 ++ ...eate_routes.ts => create_manifest_data.ts} | 4 +- src/core/create_manifests.ts | 46 ++- src/interfaces.ts | 15 + src/middleware.ts | 26 +- src/rollup.ts | 3 +- src/runtime/index.ts | 35 +- src/runtime/interfaces.ts | 7 +- .../index.ts | 24 +- .../samples/basic/about.html | 0 .../samples/basic/blog/[slug].html | 0 .../samples/basic/blog/[slug].json.js | 0 .../samples/basic/blog/_default.html | 0 .../samples/basic/blog/index.html | 0 .../samples/basic/blog/index.json.js | 0 .../samples/basic/index.html | 0 .../samples/clash-pages/[bar]/index.html | 0 .../samples/clash-pages/[foo].html | 0 .../samples/clash-pages/index.html | 0 .../samples/clash-routes/[bar]/index.js | 0 .../samples/clash-routes/[foo].js | 0 .../samples/clash-routes/index.html | 0 .../samples/encoding/#.html | 0 .../samples/hidden-dot/.unknown/foo.txt.js | 0 .../hidden-dot/.well-known/dnt-policy.txt.js | 0 .../samples/hidden-underscore/_foo.js | 0 .../samples/hidden-underscore/a/_b/c/d.js | 0 .../samples/hidden-underscore/e/f/g/h.js | 0 .../samples/hidden-underscore/i/_j.js | 0 .../samples/hidden-underscore/index.html | 0 .../samples/hidden-underscore/index.js | 0 .../samples/invalid-params/[foo][bar].js | 0 .../invalid-qualifier/[foo([a-z]([0-9]))].js | 0 .../qualifiers/[slug([0-9-a-z]{3,})].html | 0 .../samples/qualifiers/[slug([a-z]{2})].html | 0 .../samples/qualifiers/[slug].html | 0 .../samples/sorting/[wildcard].html | 0 .../samples/sorting/_layout.html | 0 .../samples/sorting/about.html | 0 .../samples/sorting/index.html | 0 .../sorting/post/[id([0-9-a-z]{3,})].html | 0 .../samples/sorting/post/[id].html | 0 .../samples/sorting/post/_default.html | 0 .../samples/sorting/post/bar.html | 0 .../samples/sorting/post/f[xx].html | 0 .../samples/sorting/post/foo.html | 0 .../samples/sorting/post/index.html | 0 62 files changed, 885 insertions(+), 484 deletions(-) delete mode 100644 src/core/create_compilers.ts create mode 100644 src/core/create_compilers/RollupCompiler.ts create mode 100644 src/core/create_compilers/RollupResult.ts create mode 100644 src/core/create_compilers/WebpackCompiler.ts create mode 100644 src/core/create_compilers/WebpackResult.ts create mode 100644 src/core/create_compilers/extract_css.ts create mode 100644 src/core/create_compilers/index.ts create mode 100644 src/core/create_compilers/interfaces.ts rename src/core/{create_routes.ts => create_manifest_data.ts} (97%) rename test/unit/{create_routes => create_manifest_data}/index.ts (81%) rename test/unit/{create_routes => create_manifest_data}/samples/basic/about.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/basic/blog/[slug].html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/basic/blog/[slug].json.js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/basic/blog/_default.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/basic/blog/index.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/basic/blog/index.json.js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/basic/index.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/clash-pages/[bar]/index.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/clash-pages/[foo].html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/clash-pages/index.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/clash-routes/[bar]/index.js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/clash-routes/[foo].js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/clash-routes/index.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/encoding/#.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/hidden-dot/.unknown/foo.txt.js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/hidden-dot/.well-known/dnt-policy.txt.js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/hidden-underscore/_foo.js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/hidden-underscore/a/_b/c/d.js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/hidden-underscore/e/f/g/h.js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/hidden-underscore/i/_j.js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/hidden-underscore/index.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/hidden-underscore/index.js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/invalid-params/[foo][bar].js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/invalid-qualifier/[foo([a-z]([0-9]))].js (100%) rename test/unit/{create_routes => create_manifest_data}/samples/qualifiers/[slug([0-9-a-z]{3,})].html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/qualifiers/[slug([a-z]{2})].html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/qualifiers/[slug].html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/sorting/[wildcard].html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/sorting/_layout.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/sorting/about.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/sorting/index.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/sorting/post/[id([0-9-a-z]{3,})].html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/sorting/post/[id].html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/sorting/post/_default.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/sorting/post/bar.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/sorting/post/f[xx].html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/sorting/post/foo.html (100%) rename test/unit/{create_routes => create_manifest_data}/samples/sorting/post/index.html (100%) diff --git a/package-lock.json b/package-lock.json index 3a588fe..bae5e68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.18.5", + "version": "0.18.7", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2235,7 +2235,7 @@ }, "eslint": { "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "resolved": "http://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", "dev": true, "requires": { @@ -6505,6 +6505,11 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "sourcemap-codec": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.1.tgz", + "integrity": "sha512-hX1eNBNuilj8yfFnECh0DzLgwKpBLMIvmhgEhixXNui8lMLBInTI8Kyxt++RwJnMNu7cAUo635L2+N1TxMJCzA==" + }, "spdx-correct": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", @@ -6703,6 +6708,11 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, + "string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=" + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", diff --git a/package.json b/package.json index a14a964..26da3ca 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "html-minifier": "^3.5.16", "shimport": "^0.0.10", "source-map-support": "^0.5.6", + "sourcemap-codec": "^1.4.1", + "string-hash": "^1.1.3", "tslib": "^1.9.1" }, "devDependencies": { diff --git a/src/api/build.ts b/src/api/build.ts index 06ed54a..e6090b3 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -3,15 +3,24 @@ import * as path from 'path'; import mkdirp from 'mkdirp'; import rimraf from 'rimraf'; import { EventEmitter } from 'events'; +import * as codec from 'sourcemap-codec'; +import hash from 'string-hash'; import minify_html from './utils/minify_html'; -import { create_compilers, create_main_manifests, create_routes, create_serviceworker_manifest } from '../core'; +import { create_compilers, create_main_manifests, create_manifest_data, create_serviceworker_manifest } from '../core'; import * as events from './interfaces'; import { copy_shimport } from './utils/copy_shimport'; +import { Dirs, PageComponent } from '../interfaces'; +import { CompileResult } from '../core/create_compilers/interfaces'; -export function build(opts: {}) { +type Opts = { + legacy: boolean; + bundler: string; +}; + +export function build(opts: Opts, dirs: Dirs) { const emitter = new EventEmitter(); - execute(emitter, opts).then( + execute(emitter, opts, dirs).then( () => { emitter.emit('done', {}); // TODO do we need to pass back any info? }, @@ -25,22 +34,14 @@ export function build(opts: {}) { return emitter; } -async function execute(emitter: EventEmitter, { - dest = 'build', - app = 'app', - legacy, - bundler, - webpack = 'webpack', - rollup = 'rollup', - routes = 'routes' -} = {}) { - rimraf.sync(path.join(dest, '**/*')); - mkdirp.sync(`${dest}/client`); - copy_shimport(dest); +async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) { + rimraf.sync(path.join(dirs.dest, '**/*')); + mkdirp.sync(`${dirs.dest}/client`); + copy_shimport(dirs.dest); // minify app/template.html // TODO compile this to a function? could be quicker than str.replace(...).replace(...).replace(...) - const template = fs.readFileSync(`${app}/template.html`, 'utf-8'); + const template = fs.readFileSync(`${dirs.app}/template.html`, 'utf-8'); // remove this in a future version if (template.indexOf('%sapper.base%') === -1) { @@ -49,14 +50,14 @@ async function execute(emitter: EventEmitter, { throw error; } - fs.writeFileSync(`${dest}/template.html`, minify_html(template)); + fs.writeFileSync(`${dirs.dest}/template.html`, minify_html(template)); - const route_objects = create_routes(); + const manifest_data = create_manifest_data(); // create app/manifest/client.js and app/manifest/server.js - create_main_manifests({ bundler, routes: route_objects }); + create_main_manifests({ bundler: opts.bundler, manifest_data }); - const { client, server, serviceworker } = create_compilers(bundler, { webpack, rollup }); + const { client, server, serviceworker } = create_compilers(opts.bundler, dirs); const client_result = await client.compile(); emitter.emit('build', { @@ -65,20 +66,11 @@ async function execute(emitter: EventEmitter, { result: client_result }); - const build_info: { - bundler: string; - shimport: string; - assets: Record; - legacy_assets?: Record; - } = { - bundler, - shimport: bundler === 'rollup' && require('shimport/package.json').version, - assets: client_result.assets - }; + const build_info = client_result.to_json(manifest_data, dirs); - if (legacy) { + if (opts.legacy) { process.env.SAPPER_LEGACY_BUILD = 'true'; - const { client } = create_compilers(bundler, { webpack, rollup }); + const { client } = create_compilers(opts.bundler, dirs); const client_result = await client.compile(); @@ -92,7 +84,7 @@ async function execute(emitter: EventEmitter, { delete process.env.SAPPER_LEGACY_BUILD; } - fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify(build_info)); + fs.writeFileSync(path.join(dirs.dest, 'build.json'), JSON.stringify(build_info)); const server_stats = await server.compile(); emitter.emit('build', { @@ -105,8 +97,8 @@ async function execute(emitter: EventEmitter, { if (serviceworker) { create_serviceworker_manifest({ - routes: route_objects, - client_files: client_result.chunks.map((file: string) => `client/${file}`) + manifest_data, + client_files: client_result.chunks.map(chunk => `client/${chunk.file}`) }); serviceworker_stats = await serviceworker.compile(); diff --git a/src/api/dev.ts b/src/api/dev.ts index 8b3d30f..c996b06 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -7,12 +7,14 @@ import mkdirp from 'mkdirp'; import rimraf from 'rimraf'; import { locations } from '../config'; import { EventEmitter } from 'events'; -import { create_routes, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core'; -import { Compiler, Compilers, CompileResult, CompileError } from '../core/create_compilers'; +import { create_manifest_data, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core'; +import { Compiler, Compilers } from '../core/create_compilers'; +import { CompileResult, CompileError } from '../core/create_compilers/interfaces'; import Deferred from './utils/Deferred'; import * as events from './interfaces'; import validate_bundler from '../cli/utils/validate_bundler'; import { copy_shimport } from './utils/copy_shimport'; +import { ManifestData } from '../interfaces'; export function dev(opts) { return new Watcher(opts); @@ -127,9 +129,11 @@ class Watcher extends EventEmitter { if (!this.dev_port) this.dev_port = await ports.find(10000); + let manifest_data: ManifestData; + try { - const routes = create_routes(); - create_main_manifests({ bundler: this.bundler, routes, dev_port: this.dev_port }); + manifest_data = create_manifest_data(); + create_main_manifests({ bundler: this.bundler, manifest_data, dev_port: this.dev_port }); } catch (err) { this.emit('fatal', { message: err.message @@ -149,12 +153,11 @@ class Watcher extends EventEmitter { return true; }, () => { - const routes = create_routes(); - create_main_manifests({ bundler: this.bundler, routes, dev_port: this.dev_port }); - try { - const routes = create_routes(); - create_main_manifests({ bundler: this.bundler, routes, dev_port: this.dev_port }); + const new_manifest_data = create_manifest_data(); + create_main_manifests({ bundler: this.bundler, manifest_data, dev_port: this.dev_port }); + + manifest_data = new_manifest_data; } catch (err) { this.emit('error', { message: err.message @@ -173,10 +176,7 @@ class Watcher extends EventEmitter { let deferred = new Deferred(); // TODO watch the configs themselves? - const compilers: Compilers = create_compilers(this.bundler, { - webpack: this.dirs.webpack, - rollup: this.dirs.rollup - }); + const compilers: Compilers = create_compilers(this.bundler, this.dirs); let log = ''; @@ -282,16 +282,17 @@ class Watcher extends EventEmitter { }, handle_result: (result: CompileResult) => { - fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify({ - bundler: this.bundler, - shimport: this.bundler === 'rollup' && require('shimport/package.json').version, - assets: result.assets - }, null, ' ')); + fs.writeFileSync( + path.join(dest, 'build.json'), - const client_files = result.chunks.map((file: string) => `client/${file}`); + // TODO should be more explicit that to_json has effects + JSON.stringify(result.to_json(manifest_data, this.dirs), null, ' ') + ); + + const client_files = result.chunks.map(chunk => `client/${chunk.file}`); create_serviceworker_manifest({ - routes: create_routes(), + manifest_data, client_files }); diff --git a/src/api/find_page.ts b/src/api/find_page.ts index bf843d0..e986176 100644 --- a/src/api/find_page.ts +++ b/src/api/find_page.ts @@ -1,8 +1,8 @@ import { locations } from '../config'; -import { create_routes } from '../core'; +import { create_manifest_data } from '../core'; export function find_page(pathname: string, cwd = locations.routes()) { - const { pages } = create_routes(cwd); + const { pages } = create_manifest_data(cwd); for (let i = 0; i < pages.length; i += 1) { const page = pages[i]; diff --git a/src/api/interfaces.ts b/src/api/interfaces.ts index 0be020d..1bd8e74 100644 --- a/src/api/interfaces.ts +++ b/src/api/interfaces.ts @@ -42,4 +42,4 @@ export type FailureEvent = { } -export type DoneEvent = {} \ No newline at end of file +export type DoneEvent = {}; \ No newline at end of file diff --git a/src/cli/build.ts b/src/cli/build.ts index d13c00e..362bf99 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -14,11 +14,12 @@ export function build(opts: { bundler?: string, legacy?: boolean }) { return new Promise((fulfil, reject) => { try { const emitter = _build({ + legacy: opts.legacy, + bundler + }, { dest: locations.dest(), app: locations.app(), routes: locations.routes(), - legacy: opts.legacy, - bundler, webpack: 'webpack', rollup: 'rollup' }); diff --git a/src/core.ts b/src/core.ts index c88a89d..7d813dc 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,3 +1,3 @@ export * from './core/create_manifests'; -export { default as create_compilers } from './core/create_compilers'; -export { default as create_routes } from './core/create_routes'; \ No newline at end of file +export { default as create_compilers } from './core/create_compilers/index'; +export { default as create_manifest_data } from './core/create_manifest_data'; \ No newline at end of file diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts deleted file mode 100644 index 6c91caf..0000000 --- a/src/core/create_compilers.ts +++ /dev/null @@ -1,377 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import colors from 'kleur'; -import pb from 'pretty-bytes'; -import relative from 'require-relative'; -import format_messages from 'webpack-format-messages'; -import { left_pad } from '../utils'; - -let r: any; -let wp: any; - -export class CompileError { - file: string; - message: string; -} - -export class CompileResult { - duration: number; - errors: CompileError[]; - warnings: CompileError[]; - chunks: string[]; - assets: Record; -} - -class RollupResult extends CompileResult { - summary: string; - - constructor(duration: number, compiler: RollupCompiler) { - super(); - - this.duration = duration; - - this.errors = compiler.errors.map(munge_rollup_warning_or_error); - this.warnings = compiler.warnings.map(munge_rollup_warning_or_error); // TODO emit this as they happen - - this.chunks = compiler.chunks.map(chunk => chunk.fileName); - - // TODO populate this properly. We don't have namedcompiler. chunks, as in - // webpack, but we can have a route -> [chunk] map or something - this.assets = {}; - - compiler.chunks.forEach(chunk => { - if (compiler.input in chunk.modules) { - this.assets.main = chunk.fileName; - } - }); - - this.summary = compiler.chunks.map(chunk => { - const size_color = chunk.code.length > 150000 ? colors.bold.red : chunk.code.length > 50000 ? colors.bold.yellow : colors.bold.white; - const size_label = left_pad(pb(chunk.code.length), 10); - - const lines = [size_color(`${size_label} ${chunk.fileName}`)]; - - const deps = Object.keys(chunk.modules) - .map(file => { - return { - file: path.relative(process.cwd(), file), - size: chunk.modules[file].renderedLength - }; - }) - .filter(dep => dep.size > 0) - .sort((a, b) => b.size - a.size); - - const total_unminified = deps.reduce((t, d) => t + d.size, 0); - - deps.forEach((dep, i) => { - const c = i === deps.length - 1 ? '└' : '│'; - let line = ` ${c} ${dep.file}`; - - if (deps.length > 1) { - const p = (100 * dep.size / total_unminified).toFixed(1); - line += ` (${p}%)`; - } - - lines.push(colors.gray(line)); - }); - - return lines.join('\n'); - }).join('\n'); - } - - print() { - const blocks: string[] = this.warnings.map(warning => { - return warning.file - ? `> ${colors.bold(warning.file)}\n${warning.message}` - : `> ${warning.message}`; - }); - - blocks.push(this.summary); - - return blocks.join('\n\n'); - } -} - -class WebpackResult extends CompileResult { - stats: any; - - constructor(stats: any) { - super(); - - this.stats = stats; - - const info = stats.toJson(); - - const messages = format_messages(stats); - - this.errors = messages.errors.map(munge_webpack_warning_or_error); - this.warnings = messages.warnings.map(munge_webpack_warning_or_error); - - this.duration = info.time; - - this.chunks = info.assets.map((chunk: { name: string }) => chunk.name); - this.assets = info.assetsByChunkName; - } - - print() { - return this.stats.toString({ colors: true }); - } -} - -export class RollupCompiler { - _: Promise; - _oninvalid: (filename: string) => void; - _start: number; - input: string; - warnings: any[]; - errors: any[]; - chunks: any[]; // TODO types - - constructor(config: any) { - this._ = this.get_config(path.resolve(config)); - this.input = null; - this.warnings = []; - this.errors = []; - this.chunks = []; - } - - async get_config(input: string) { - const bundle = await r.rollup({ - input, - external: (id: string) => { - return (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5, id.length) === '.json'; - } - }); - - const { code } = await bundle.generate({ format: 'cjs' }); - - // temporarily override require - const defaultLoader = require.extensions['.js']; - require.extensions['.js'] = (module: any, filename: string) => { - if (filename === input) { - module._compile(code, filename); - } else { - defaultLoader(module, filename); - } - }; - - const mod: any = require(input); - delete require.cache[input]; - - (mod.plugins || (mod.plugins = [])).push({ - name: 'sapper-internal', - options: (opts: any) => { - this.input = opts.input; - }, - renderChunk: (code: string, chunk: any) => { - if (chunk.isEntry) { - this.chunks.push(chunk); - } - } - }); - - const onwarn = mod.onwarn || ((warning: any, handler: (warning: any) => void) => { - handler(warning); - }); - - mod.onwarn = (warning: any) => { - onwarn(warning, (warning: any) => { - this.warnings.push(warning); - }); - }; - - return mod; - } - - oninvalid(cb: (filename: string) => void) { - this._oninvalid = cb; - } - - async compile(): Promise { - const config = await this._; - - const start = Date.now(); - - try { - const bundle = await r.rollup(config); - await bundle.write(config.output); - - return new RollupResult(Date.now() - start, this); - } catch (err) { - if (err.filename) { - // TODO this is a bit messy. Also, can - // Rollup emit other kinds of error? - err.message = [ - `Failed to build — error in ${err.filename}: ${err.message}`, - err.frame - ].filter(Boolean).join('\n'); - } - - throw err; - } - } - - async watch(cb: (err?: Error, stats?: any) => void) { - const config = await this._; - - const watcher = r.watch(config); - - watcher.on('change', (id: string) => { - this.chunks = []; - this.warnings = []; - this.errors = []; - this._oninvalid(id); - }); - - watcher.on('event', (event: any) => { - switch (event.code) { - case 'FATAL': - // TODO kill the process? - if (event.error.filename) { - // TODO this is a bit messy. Also, can - // Rollup emit other kinds of error? - event.error.message = [ - `Failed to build — error in ${event.error.filename}: ${event.error.message}`, - event.error.frame - ].filter(Boolean).join('\n'); - } - - cb(event.error); - break; - - case 'ERROR': - this.errors.push(event.error); - cb(null, new RollupResult(Date.now() - this._start, this)); - break; - - case 'START': - case 'END': - // TODO is there anything to do with this info? - break; - - case 'BUNDLE_START': - this._start = Date.now(); - break; - - case 'BUNDLE_END': - cb(null, new RollupResult(Date.now() - this._start, this)); - break; - - default: - console.log(`Unexpected event ${event.code}`); - } - }); - } -} - -export class WebpackCompiler { - _: any; - - constructor(config: any) { - this._ = wp(require(path.resolve(config))); - } - - oninvalid(cb: (filename: string) => void) { - this._.hooks.invalid.tap('sapper', cb); - } - - compile(): Promise { - return new Promise((fulfil, reject) => { - this._.run((err: Error, stats: any) => { - if (err) { - reject(err); - process.exit(1); - } - - const result = new WebpackResult(stats); - - if (result.errors.length) { - // TODO print errors - // console.error(stats.toString({ colors: true })); - reject(new Error(`Encountered errors while building app`)); - } - - else { - fulfil(result); - } - }); - }); - } - - watch(cb: (err?: Error, stats?: any) => void) { - this._.watch({}, (err?: Error, stats?: any) => { - cb(err, stats && new WebpackResult(stats)); - }); - } -} - -export type Compiler = RollupCompiler | WebpackCompiler; - -export type Compilers = { - client: Compiler; - server: Compiler; - serviceworker?: Compiler; -} - -export default function create_compilers(bundler: string, { webpack, rollup }: { webpack: string, rollup: string }): Compilers { - if (bundler === 'rollup') { - if (!r) r = relative('rollup', process.cwd()); - - const sw = `${rollup}/service-worker.config.js`; - - return { - client: new RollupCompiler(`${rollup}/client.config.js`), - server: new RollupCompiler(`${rollup}/server.config.js`), - serviceworker: fs.existsSync(sw) && new RollupCompiler(sw) - }; - } - - if (bundler === 'webpack') { - if (!wp) wp = relative('webpack', process.cwd()); - - const sw = `${webpack}/service-worker.config.js`; - - return { - client: new WebpackCompiler(`${webpack}/client.config.js`), - server: new WebpackCompiler(`${webpack}/server.config.js`), - serviceworker: fs.existsSync(sw) && new WebpackCompiler(sw) - }; - } - - // this shouldn't be possible... - throw new Error(`Invalid bundler option '${bundler}'`); -} - -const locPattern = /\((\d+):(\d+)\)$/; - -function munge_webpack_warning_or_error(message: string) { - // TODO this is all a bit rube goldberg... - const lines = message.split('\n'); - - const file = lines.shift() - .replace('', '') // careful — there is a special character at the beginning of this string - .replace('', '') - .replace('./', ''); - - let line = null; - let column = null; - - const match = locPattern.exec(lines[0]); - if (match) { - lines[0] = lines[0].replace(locPattern, ''); - line = +match[1]; - column = +match[2]; - } - - return { - file, - message: lines.join('\n') - }; -} - -function munge_rollup_warning_or_error(warning_or_error: any) { - return { - file: warning_or_error.filename, - message: [warning_or_error.message, warning_or_error.frame].filter(Boolean).join('\n') - }; -} \ No newline at end of file diff --git a/src/core/create_compilers/RollupCompiler.ts b/src/core/create_compilers/RollupCompiler.ts new file mode 100644 index 0000000..2a01cd0 --- /dev/null +++ b/src/core/create_compilers/RollupCompiler.ts @@ -0,0 +1,160 @@ +import * as path from 'path'; +import relative from 'require-relative'; +import { CompileResult } from './interfaces'; +import RollupResult from './RollupResult'; + +let rollup: any; + +export default class RollupCompiler { + _: Promise; + _oninvalid: (filename: string) => void; + _start: number; + input: string; + warnings: any[]; + errors: any[]; + chunks: any[]; + css_files: Array<{ id: string, code: string }>; + + constructor(config: string) { + this._ = this.get_config(path.resolve(config)); + this.input = null; + this.warnings = []; + this.errors = []; + this.chunks = []; + this.css_files = []; + } + + async get_config(input: string) { + if (!rollup) rollup = relative('rollup', process.cwd()); + + const bundle = await rollup.rollup({ + input, + external: (id: string) => { + return (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5, id.length) === '.json'; + } + }); + + const { code } = await bundle.generate({ format: 'cjs' }); + + // temporarily override require + const defaultLoader = require.extensions['.js']; + require.extensions['.js'] = (module: any, filename: string) => { + if (filename === input) { + module._compile(code, filename); + } else { + defaultLoader(module, filename); + } + }; + + const mod: any = require(input); + delete require.cache[input]; + + (mod.plugins || (mod.plugins = [])).push({ + name: 'sapper-internal', + options: (opts: any) => { + this.input = opts.input; + }, + renderChunk: (code: string, chunk: any) => { + this.chunks.push(chunk); + }, + transform: (code: string, id: string) => { + if (/\.css$/.test(id)) { + this.css_files.push({ id, code }); + return ``; + } + } + }); + + const onwarn = mod.onwarn || ((warning: any, handler: (warning: any) => void) => { + handler(warning); + }); + + mod.onwarn = (warning: any) => { + onwarn(warning, (warning: any) => { + this.warnings.push(warning); + }); + }; + + return mod; + } + + oninvalid(cb: (filename: string) => void) { + this._oninvalid = cb; + } + + async compile(): Promise { + const config = await this._; + + const start = Date.now(); + + try { + const bundle = await rollup.rollup(config); + await bundle.write(config.output); + + return new RollupResult(Date.now() - start, this); + } catch (err) { + if (err.filename) { + // TODO this is a bit messy. Also, can + // Rollup emit other kinds of error? + err.message = [ + `Failed to build — error in ${err.filename}: ${err.message}`, + err.frame + ].filter(Boolean).join('\n'); + } + + throw err; + } + } + + async watch(cb: (err?: Error, stats?: any) => void) { + const config = await this._; + + const watcher = rollup.watch(config); + + watcher.on('change', (id: string) => { + this.chunks = []; + this.warnings = []; + this.errors = []; + this._oninvalid(id); + }); + + watcher.on('event', (event: any) => { + switch (event.code) { + case 'FATAL': + // TODO kill the process? + if (event.error.filename) { + // TODO this is a bit messy. Also, can + // Rollup emit other kinds of error? + event.error.message = [ + `Failed to build — error in ${event.error.filename}: ${event.error.message}`, + event.error.frame + ].filter(Boolean).join('\n'); + } + + cb(event.error); + break; + + case 'ERROR': + this.errors.push(event.error); + cb(null, new RollupResult(Date.now() - this._start, this)); + break; + + case 'START': + case 'END': + // TODO is there anything to do with this info? + break; + + case 'BUNDLE_START': + this._start = Date.now(); + break; + + case 'BUNDLE_END': + cb(null, new RollupResult(Date.now() - this._start, this)); + break; + + default: + console.log(`Unexpected event ${event.code}`); + } + }); + } +} \ No newline at end of file diff --git a/src/core/create_compilers/RollupResult.ts b/src/core/create_compilers/RollupResult.ts new file mode 100644 index 0000000..87ea226 --- /dev/null +++ b/src/core/create_compilers/RollupResult.ts @@ -0,0 +1,111 @@ +import * as path from 'path'; +import colors from 'kleur'; +import pb from 'pretty-bytes'; +import RollupCompiler from './RollupCompiler'; +import extract_css from './extract_css'; +import { left_pad } from '../../utils'; +import { CompileResult, BuildInfo, CompileError, Chunk, CssFile } from './interfaces'; +import { ManifestData, Dirs, PageComponent } from '../../interfaces'; + +export default class RollupResult implements CompileResult { + duration: number; + errors: CompileError[]; + warnings: CompileError[]; + chunks: Chunk[]; + assets: Record; + css_files: CssFile[]; + css: { + main: string, + chunks: Record + }; + summary: string; + + constructor(duration: number, compiler: RollupCompiler) { + this.duration = duration; + + this.errors = compiler.errors.map(munge_warning_or_error); + this.warnings = compiler.warnings.map(munge_warning_or_error); // TODO emit this as they happen + + this.chunks = compiler.chunks.map(chunk => ({ + file: chunk.fileName, + imports: chunk.imports.filter(Boolean), + modules: Object.keys(chunk.modules) + })); + + this.css_files = compiler.css_files; + + // TODO populate this properly. We don't have named chunks, as in + // webpack, but we can have a route -> [chunk] map or something + this.assets = {}; + + compiler.chunks.forEach(chunk => { + if (compiler.input in chunk.modules) { + this.assets.main = chunk.fileName; + } + }); + + this.summary = compiler.chunks.map(chunk => { + const size_color = chunk.code.length > 150000 ? colors.bold.red : chunk.code.length > 50000 ? colors.bold.yellow : colors.bold.white; + const size_label = left_pad(pb(chunk.code.length), 10); + + const lines = [size_color(`${size_label} ${chunk.fileName}`)]; + + const deps = Object.keys(chunk.modules) + .map(file => { + return { + file: path.relative(process.cwd(), file), + size: chunk.modules[file].renderedLength + }; + }) + .filter(dep => dep.size > 0) + .sort((a, b) => b.size - a.size); + + const total_unminified = deps.reduce((t, d) => t + d.size, 0); + + deps.forEach((dep, i) => { + const c = i === deps.length - 1 ? '└' : '│'; + let line = ` ${c} ${dep.file}`; + + if (deps.length > 1) { + const p = (100 * dep.size / total_unminified).toFixed(1); + line += ` (${p}%)`; + } + + lines.push(colors.gray(line)); + }); + + return lines.join('\n'); + }).join('\n'); + } + + to_json(manifest_data: ManifestData, dirs: Dirs): BuildInfo { + // TODO extract_css has side-effects that don't belong + // in a method called to_json + return { + bundler: 'rollup', + shimport: require('shimport/package.json').version, + assets: this.assets, + css: extract_css(this, manifest_data.components, dirs) + }; + } + + print() { + const blocks: string[] = this.warnings.map(warning => { + return warning.file + ? `> ${colors.bold(warning.file)}\n${warning.message}` + : `> ${warning.message}`; + }); + + blocks.push(this.summary); + + return blocks.join('\n\n'); + } +} + +function munge_warning_or_error(warning_or_error: any) { + return { + file: warning_or_error.filename, + message: [warning_or_error.message, warning_or_error.frame].filter(Boolean).join('\n') + }; +} + diff --git a/src/core/create_compilers/WebpackCompiler.ts b/src/core/create_compilers/WebpackCompiler.ts new file mode 100644 index 0000000..7c8863f --- /dev/null +++ b/src/core/create_compilers/WebpackCompiler.ts @@ -0,0 +1,48 @@ +import * as path from 'path'; +import relative from 'require-relative'; +import { CompileResult } from './interfaces'; +import WebpackResult from './WebpackResult'; + +let webpack: any; + +export class WebpackCompiler { + _: any; + + constructor(config: string) { + if (!webpack) webpack = relative('webpack', process.cwd()); + this._ = webpack(require(path.resolve(config))); + } + + oninvalid(cb: (filename: string) => void) { + this._.hooks.invalid.tap('sapper', cb); + } + + compile(): Promise { + return new Promise((fulfil, reject) => { + this._.run((err: Error, stats: any) => { + if (err) { + reject(err); + process.exit(1); + } + + const result = new WebpackResult(stats); + + if (result.errors.length) { + // TODO print errors + // console.error(stats.toString({ colors: true })); + reject(new Error(`Encountered errors while building app`)); + } + + else { + fulfil(result); + } + }); + }); + } + + watch(cb: (err?: Error, stats?: any) => void) { + this._.watch({}, (err?: Error, stats?: any) => { + cb(err, stats && new WebpackResult(stats)); + }); + } +} \ No newline at end of file diff --git a/src/core/create_compilers/WebpackResult.ts b/src/core/create_compilers/WebpackResult.ts new file mode 100644 index 0000000..4b015a6 --- /dev/null +++ b/src/core/create_compilers/WebpackResult.ts @@ -0,0 +1,73 @@ +import format_messages from 'webpack-format-messages'; +import { CompileResult, BuildInfo, CompileError, Chunk, CssFile } from './interfaces'; +import { ManifestData, Dirs } from '../../interfaces'; + +const locPattern = /\((\d+):(\d+)\)$/; + +function munge_warning_or_error(message: string) { + // TODO this is all a bit rube goldberg... + const lines = message.split('\n'); + + const file = lines.shift() + .replace('', '') // careful — there is a special character at the beginning of this string + .replace('', '') + .replace('./', ''); + + let line = null; + let column = null; + + const match = locPattern.exec(lines[0]); + if (match) { + lines[0] = lines[0].replace(locPattern, ''); + line = +match[1]; + column = +match[2]; + } + + return { + file, + message: lines.join('\n') + }; +} + +export default class WebpackResult implements CompileResult { + duration: number; + errors: CompileError[]; + warnings: CompileError[]; + chunks: Chunk[]; + assets: Record; + css_files: CssFile[]; + stats: any; + + constructor(stats: any) { + this.stats = stats; + + const info = stats.toJson(); + + const messages = format_messages(stats); + + this.errors = messages.errors.map(munge_warning_or_error); + this.warnings = messages.warnings.map(munge_warning_or_error); + + this.duration = info.time; + + this.chunks = info.assets.map((chunk: { name: string }) => ({ file: chunk.name })); + this.assets = info.assetsByChunkName; + } + + to_json(manifest_data: ManifestData, dirs: Dirs): BuildInfo { + return { + bundler: 'webpack', + shimport: null, // webpack has its own loader + assets: this.assets, + css: { + // TODO + main: null, + chunks: {} + } + }; + } + + print() { + return this.stats.toString({ colors: true }); + } +} \ No newline at end of file diff --git a/src/core/create_compilers/extract_css.ts b/src/core/create_compilers/extract_css.ts new file mode 100644 index 0000000..6791ab6 --- /dev/null +++ b/src/core/create_compilers/extract_css.ts @@ -0,0 +1,230 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import hash from 'string-hash'; +import * as codec from 'sourcemap-codec'; +import { PageComponent, Dirs } from '../../interfaces'; +import { CompileResult } from './interfaces'; + +const inline_sourcemap_header = 'data:application/json;charset=utf-8;base64,'; + +function extract_sourcemap(raw: string, id: string) { + let raw_map: string; + let map = null; + + const code = raw.replace(/\/\*#\s+sourceMappingURL=(.+)\s+\*\//g, (m, url) => { + if (raw_map) { + // TODO should not happen! + throw new Error(`Found multiple sourcemaps in single CSS file (${id})`); + } + + raw_map = url; + return ''; + }).trim(); + + if (raw_map) { + if (raw_map.startsWith(inline_sourcemap_header)) { + const json = Buffer.from(raw_map.slice(inline_sourcemap_header.length), 'base64').toString(); + map = JSON.parse(json); + } else { + // TODO do we want to handle non-inline sourcemaps? could be a rabbit hole + } + } + + return { + code, + map + }; +} + +type SourceMap = { + version: 3; + file: string; + sources: string[]; + sourcesContent: string[]; + names: string[]; + mappings: string; +}; + +export default function extract_css(client_result: CompileResult, components: PageComponent[], dirs: Dirs) { + const result: { + main: string | null; + chunks: Record + } = { + main: null, + chunks: {} + }; + + if (!client_result.css_files) return; // Rollup-only for now + + const unaccounted_for = new Set(); + + const css_map = new Map(); + client_result.css_files.forEach(css => { + unaccounted_for.add(css.id); + css_map.set(css.id, css.code); + }); + + const chunk_map = new Map(); + client_result.chunks.forEach(chunk => { + chunk_map.set(chunk.file, chunk); + }); + + const chunks_with_css = new Set(); + + // figure out which chunks belong to which components... + const component_owners = new Map(); + client_result.chunks.forEach(chunk => { + chunk.modules.forEach(module => { + const component = path.relative(dirs.routes, module); + component_owners.set(component, chunk); + }); + }); + + const chunks_depended_upon_by_component = new Map(); + + // ...so we can figure out which chunks don't belong + components.forEach(component => { + const chunk = component_owners.get(component.file); + if (!chunk) { + // this should never happen! + throw new Error(`Could not find chunk that owns ${component.file}`); + } + + const chunks = new Set([chunk]); + chunks.forEach(chunk => { + chunk.imports.forEach((file: string) => { + const chunk = chunk_map.get(file); + if (chunk) chunks.add(chunk); + }); + }); + + chunks.forEach(chunk => { + chunk.modules.forEach((module: string) => { + unaccounted_for.delete(module); + }); + }); + + chunks_depended_upon_by_component.set( + component, + chunks + ); + }); + + function get_css_from_modules(modules: string[]) { + const parts: string[] = []; + const mappings: number[][][] = []; + + const combined_map: SourceMap = { + version: 3, + file: null, + sources: [], + sourcesContent: [], + names: [], + mappings: null + }; + + modules.forEach(module => { + if (!/\.css$/.test(module)) return; + + const css = css_map.get(module); + + const { code, map } = extract_sourcemap(css, module); + + parts.push(code); + + if (map) { + const lines = codec.decode(map.mappings); + + if (combined_map.sources.length > 0 || combined_map.names.length > 0) { + lines.forEach(line => { + line.forEach(segment => { + // adjust source index + segment[1] += combined_map.sources.length; + + // adjust name index + if (segment[4]) segment[4] += combined_map.names.length; + }); + }); + } + + combined_map.sources.push(...map.sources); + combined_map.sourcesContent.push(...map.sourcesContent); + combined_map.names.push(...map.names); + + mappings.push(...lines); + } + }); + + if (parts.length > 0) { + combined_map.mappings = codec.encode(mappings); + + combined_map.sources = combined_map.sources.map(source => path.relative(`${dirs.dest}/client`, source)); + + return { + code: parts.join('\n'), + map: combined_map + }; + } + + return null; + } + + const main = client_result.assets.main; + const entry = fs.readFileSync(`${dirs.dest}/client/${main}`, 'utf-8'); + + const replacements = new Map(); + + chunks_depended_upon_by_component.forEach((chunks, component) => { + const chunks_with_css = Array.from(chunks).filter(chunk => { + const css = get_css_from_modules(chunk.modules); + + if (css) { + const { code, map } = css; + + const output_file_name = chunk.file.replace(/\.js$/, '.css'); + + map.file = output_file_name; + map.sources = map.sources.map(source => path.relative(`${dirs.dest}/client`, source)); + + fs.writeFileSync(`${dirs.dest}/client/${output_file_name}`, `${code}\n/* sourceMappingURL=client/${output_file_name}.map */`); + fs.writeFileSync(`${dirs.dest}/client/${output_file_name}.map`, JSON.stringify(map, null, ' ')); + + return true; + } + }); + + const files = chunks_with_css.map(chunk => chunk.file.replace(/\.js$/, '.css')); + + replacements.set( + component.file, + files + ); + + result.chunks[component.file] = files; + }); + + const replaced = entry.replace(/["']__SAPPER_CSS_PLACEHOLDER:(.+?)__["']/g, (m, route) => { + return JSON.stringify(replacements.get(route)); + }); + + fs.writeFileSync(`${dirs.dest}/client/${main}`, replaced); + + const leftover = get_css_from_modules(Array.from(unaccounted_for)); + if (leftover) { + const { code, map } = leftover; + + const main_hash = hash(code); + + const output_file_name = `main.${main_hash}.css`; + + map.file = output_file_name; + map.sources = map.sources.map(source => path.relative(`${dirs.dest}/client`, source)); + + fs.writeFileSync(`${dirs.dest}/client/${output_file_name}`, `${code}\n/* sourceMappingURL=client/${output_file_name}.map */`); + fs.writeFileSync(`${dirs.dest}/client/${output_file_name}.map`, JSON.stringify(map, null, ' ')); + + result.main = output_file_name; + } + + return result; +} \ No newline at end of file diff --git a/src/core/create_compilers/index.ts b/src/core/create_compilers/index.ts new file mode 100644 index 0000000..cf9c13a --- /dev/null +++ b/src/core/create_compilers/index.ts @@ -0,0 +1,37 @@ +import * as fs from 'fs'; +import { Dirs } from '../../interfaces'; +import RollupCompiler from './RollupCompiler'; +import { WebpackCompiler } from './WebpackCompiler'; + +export type Compiler = RollupCompiler | WebpackCompiler; + +export type Compilers = { + client: Compiler; + server: Compiler; + serviceworker?: Compiler; +} + +export default function create_compilers(bundler: string, dirs: Dirs): Compilers { + if (bundler === 'rollup') { + const sw = `${dirs.rollup}/service-worker.config.js`; + + return { + client: new RollupCompiler(`${dirs.rollup}/client.config.js`), + server: new RollupCompiler(`${dirs.rollup}/server.config.js`), + serviceworker: fs.existsSync(sw) && new RollupCompiler(sw) + }; + } + + if (bundler === 'webpack') { + const sw = `${dirs.webpack}/service-worker.config.js`; + + return { + client: new WebpackCompiler(`${dirs.webpack}/client.config.js`), + server: new WebpackCompiler(`${dirs.webpack}/server.config.js`), + serviceworker: fs.existsSync(sw) && new WebpackCompiler(sw) + }; + } + + // this shouldn't be possible... + throw new Error(`Invalid bundler option '${bundler}'`); +} \ No newline at end of file diff --git a/src/core/create_compilers/interfaces.ts b/src/core/create_compilers/interfaces.ts new file mode 100644 index 0000000..11b8b0e --- /dev/null +++ b/src/core/create_compilers/interfaces.ts @@ -0,0 +1,39 @@ +import { ManifestData, Dirs } from '../../interfaces'; + +export type Chunk = { + file: string; + imports: string[]; + modules: string[]; +} + +export type CssFile = { + id: string; + code: string; +}; + +export class CompileError { + file: string; + message: string; +} + +export interface CompileResult { + duration: number; + errors: CompileError[]; + warnings: CompileError[]; + chunks: Chunk[]; + assets: Record; + css_files: CssFile[]; + + to_json: (manifest_data: ManifestData, dirs: Dirs) => BuildInfo +} + +export type BuildInfo = { + bundler: string; + shimport: string; + assets: Record; + legacy_assets?: Record; + css: { + main: string | null, + chunks: Record + } +} \ No newline at end of file diff --git a/src/core/create_routes.ts b/src/core/create_manifest_data.ts similarity index 97% rename from src/core/create_routes.ts rename to src/core/create_manifest_data.ts index 96ac8c0..83788c2 100644 --- a/src/core/create_routes.ts +++ b/src/core/create_manifest_data.ts @@ -1,10 +1,10 @@ import * as fs from 'fs'; import * as path from 'path'; import { locations } from '../config'; -import { Page, PageComponent, ServerRoute } from '../interfaces'; +import { Page, PageComponent, ServerRoute, ManifestData } from '../interfaces'; import { posixify } from './utils'; -export default function create_routes(cwd = locations.routes()) { +export default function create_manifest_data(cwd = locations.routes()): ManifestData { const components: PageComponent[] = []; const pages: Page[] = []; const server_routes: ServerRoute[] = []; diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index 518f692..3c904c9 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -3,11 +3,11 @@ import * as path from 'path'; import glob from 'tiny-glob/sync.js'; import { posixify, write_if_changed } from './utils'; import { dev, locations } from '../config'; -import { Page, PageComponent, ServerRoute } from '../interfaces'; +import { Page, PageComponent, ServerRoute, ManifestData } from '../interfaces'; -export function create_main_manifests({ bundler, routes, dev_port }: { +export function create_main_manifests({ bundler, manifest_data, dev_port }: { bundler: string, - routes: { components: PageComponent[], pages: Page[], server_routes: ServerRoute[] }; + manifest_data: ManifestData; dev_port?: number; }) { const manifest_dir = path.join(locations.app(), 'manifest'); @@ -15,8 +15,8 @@ export function create_main_manifests({ bundler, routes, dev_port }: { const path_to_routes = path.relative(manifest_dir, locations.routes()); - const client_manifest = generate_client(routes, path_to_routes, bundler, dev_port); - const server_manifest = generate_server(routes, path_to_routes); + const client_manifest = generate_client(manifest_data, path_to_routes, bundler, dev_port); + const server_manifest = generate_server(manifest_data, path_to_routes); write_if_changed( `${manifest_dir}/default-layout.html`, @@ -26,8 +26,8 @@ export function create_main_manifests({ bundler, routes, dev_port }: { write_if_changed(`${manifest_dir}/server.js`, server_manifest); } -export function create_serviceworker_manifest({ routes, client_files }: { - routes: { components: PageComponent[], pages: Page[], server_routes: ServerRoute[] }; +export function create_serviceworker_manifest({ manifest_data, client_files }: { + manifest_data: ManifestData; client_files: string[]; }) { const assets = glob('**', { cwd: 'assets', filesOnly: true }); @@ -40,44 +40,47 @@ export function create_serviceworker_manifest({ routes, client_files }: { export const shell = [\n\t${client_files.map((x: string) => `"${x}"`).join(',\n\t')}\n]; - export const routes = [\n\t${routes.pages.map((r: Page) => `{ pattern: ${r.pattern} }`).join(',\n\t')}\n]; + export const routes = [\n\t${manifest_data.pages.map((r: Page) => `{ pattern: ${r.pattern} }`).join(',\n\t')}\n]; `.replace(/^\t\t/gm, '').trim(); write_if_changed(`${locations.app()}/manifest/service-worker.js`, code); } function generate_client( - routes: { root: PageComponent, components: PageComponent[], pages: Page[], server_routes: ServerRoute[] }, + manifest_data: ManifestData, path_to_routes: string, bundler: string, dev_port?: number ) { - const page_ids = new Set(routes.pages.map(page => + const page_ids = new Set(manifest_data.pages.map(page => page.pattern.toString())); - const server_routes_to_ignore = routes.server_routes.filter(route => + const server_routes_to_ignore = manifest_data.server_routes.filter(route => !page_ids.has(route.pattern.toString())); let code = ` // This file is generated by Sapper — do not edit it! - import root from '${get_file(path_to_routes, routes.root)}'; + import root from '${get_file(path_to_routes, manifest_data.root)}'; import error from '${posixify(`${path_to_routes}/_error.html`)}'; - ${routes.components.map(component => { + ${manifest_data.components.map(component => { const annotation = bundler === 'webpack' ? `/* webpackChunkName: "${component.name}" */ ` : ''; const source = get_file(path_to_routes, component); - return `const ${component.name} = () => import(${annotation}'${source}');`; + return `const ${component.name} = { + js: () => import(${annotation}'${source}'), + css: "__SAPPER_CSS_PLACEHOLDER:${component.file}__" + };`; }).join('\n')} export const manifest = { ignore: [${server_routes_to_ignore.map(route => route.pattern).join(', ')}], pages: [ - ${routes.pages.map(page => `{ + ${manifest_data.pages.map(page => `{ // ${page.parts[page.parts.length - 1].component.file} pattern: ${page.pattern}, parts: [ @@ -119,15 +122,15 @@ function generate_client( } function generate_server( - routes: { root: PageComponent, components: PageComponent[], pages: Page[], server_routes: ServerRoute[] }, + manifest_data: ManifestData, path_to_routes: string ) { const imports = [].concat( - routes.server_routes.map(route => + manifest_data.server_routes.map(route => `import * as ${route.name} from '${posixify(`${path_to_routes}/${route.file}`)}';`), - routes.components.map(component => + manifest_data.components.map(component => `import ${component.name} from '${get_file(path_to_routes, component)}';`), - `import root from '${get_file(path_to_routes, routes.root)}';`, + `import root from '${get_file(path_to_routes, manifest_data.root)}';`, `import error from '${posixify(`${path_to_routes}/_error.html`)}';` ); @@ -137,7 +140,7 @@ function generate_server( export const manifest = { server_routes: [ - ${routes.server_routes.map(route => `{ + ${manifest_data.server_routes.map(route => `{ // ${route.file} pattern: ${route.pattern}, handlers: ${route.name}, @@ -148,7 +151,7 @@ function generate_server( ], pages: [ - ${routes.pages.map(page => `{ + ${manifest_data.pages.map(page => `{ // ${page.parts[page.parts.length - 1].component.file} pattern: ${page.pattern}, parts: [ @@ -157,6 +160,7 @@ function generate_server( const props = [ `name: "${part.component.name}"`, + `file: "${part.component.file}"`, `component: ${part.component.name}` ]; diff --git a/src/interfaces.ts b/src/interfaces.ts index e63c086..313da87 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -39,4 +39,19 @@ export type ServerRoute = { pattern: RegExp; file: string; params: string[]; +}; + +export type Dirs = { + dest: string, + app: string, + routes: string, + webpack: string, + rollup: string +}; + +export type ManifestData = { + root: PageComponent; + components: PageComponent[]; + pages: Page[]; + server_routes: ServerRoute[]; }; \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts index 900bbf1..171481d 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -499,12 +499,36 @@ function get_page_handler( script += ``) .replace('%sapper.html%', () => html) .replace('%sapper.head%', () => `${head}`) - .replace('%sapper.styles%', () => (css && css.code ? `` : '')); + .replace('%sapper.styles%', () => styles); res.statusCode = status; res.end(body); diff --git a/src/rollup.ts b/src/rollup.ts index 399c1a7..2941021 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -16,7 +16,8 @@ export default { dir, entryFileNames: '[name].[hash].js', chunkFileNames: '[name].[hash].js', - format: 'esm' + format: 'esm', + sourcemap: dev() }; } }, diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 8e74336..1d7210c 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -1,5 +1,5 @@ import { detach, findAnchor, scroll_state, which } from './utils'; -import { Component, ComponentConstructor, Params, Query, Redirect, Manifest, RouteData, ScrollPosition, Store, Target } from './interfaces'; +import { Component, ComponentConstructor, Params, Query, Redirect, Manifest, RouteData, ScrollPosition, Store, Target, ComponentLoader } from './interfaces'; const initial_data = typeof window !== 'undefined' && window.__SAPPER__; @@ -131,6 +131,30 @@ function changed(a: Record, b: Record; let root_data: any; +function load_css(chunk: string) { + const href = `${initial_data.baseUrl}client/${chunk}`; + if (document.querySelector(`link[href="${href}"]`)) return; + + return new Promise((fulfil, reject) => { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = href; + + link.onload = () => fulfil(); + link.onerror = reject; + + document.head.appendChild(link); + }); +} + +function load_component(component: ComponentLoader): Promise { + // TODO this is temporary — once placeholders are + // always rewritten, scratch the ternary + const promises: Array> = (typeof component.css === 'string' ? [] : component.css.map(load_css)); + promises.unshift(component.js()); + return Promise.all(promises).then(values => values[0].default); +} + function prepare_page(target: Target): Promise<{ redirect?: Redirect; data?: any; @@ -177,7 +201,8 @@ function prepare_page(target: Target): Promise<{ if (i < changed_from) return null; if (!part) return null; - const { default: Component } = await part.component(); + const Component = await load_component(part.component); + const req = { path, query, @@ -468,9 +493,9 @@ export function prefetchRoutes(pathnames: string[]) { if (!pathnames) return true; return pathnames.some(pathname => route.pattern.test(pathname)); }) - .reduce((promise: Promise, route) => { - return promise.then(route.load); - }, Promise.resolve()); + .reduce((promise: Promise, route) => promise.then(() => { + return Promise.all(route.parts.map(part => part && load_component(part.component))); + }), Promise.resolve()); } // remove this in 0.9 diff --git a/src/runtime/interfaces.ts b/src/runtime/interfaces.ts index 5fbc381..457d667 100644 --- a/src/runtime/interfaces.ts +++ b/src/runtime/interfaces.ts @@ -15,10 +15,15 @@ export interface Component { destroy: () => void; } +export type ComponentLoader = { + js: () => Promise<{ default: ComponentConstructor }>, + css: string[] +}; + export type Page = { pattern: RegExp; parts: Array<{ - component: () => Promise<{ default: ComponentConstructor }>; + component: ComponentLoader; params?: (match: RegExpExecArray) => Record; }>; }; diff --git a/test/unit/create_routes/index.ts b/test/unit/create_manifest_data/index.ts similarity index 81% rename from test/unit/create_routes/index.ts rename to test/unit/create_manifest_data/index.ts index 02f8483..e847b8a 100644 --- a/test/unit/create_routes/index.ts +++ b/test/unit/create_manifest_data/index.ts @@ -1,10 +1,10 @@ import * as path from 'path'; import * as assert from 'assert'; -import create_routes from '../../../src/core/create_routes'; +import manifest_data from '../../../src/core/create_manifest_data'; -describe('create_routes', () => { +describe('manifest_data', () => { it('creates routes', () => { - const { components, pages, server_routes } = create_routes(path.join(__dirname, 'samples/basic')); + const { components, pages, server_routes } = manifest_data(path.join(__dirname, 'samples/basic')); const index = { name: 'index', file: 'index.html' }; const about = { name: 'about', file: 'about.html' }; @@ -68,7 +68,7 @@ describe('create_routes', () => { }); it('encodes invalid characters', () => { - const { components, pages } = create_routes(path.join(__dirname, 'samples/encoding')); + const { components, pages } = manifest_data(path.join(__dirname, 'samples/encoding')); // had to remove ? and " because windows @@ -90,7 +90,7 @@ describe('create_routes', () => { }); it('allows regex qualifiers', () => { - const { pages } = create_routes(path.join(__dirname, 'samples/qualifiers')); + const { pages } = manifest_data(path.join(__dirname, 'samples/qualifiers')); assert.deepEqual(pages.map(p => p.pattern), [ /^\/([0-9-a-z]{3,})\/?$/, @@ -100,7 +100,7 @@ describe('create_routes', () => { }); it('sorts routes correctly', () => { - const { pages } = create_routes(path.join(__dirname, 'samples/sorting')); + const { pages } = manifest_data(path.join(__dirname, 'samples/sorting')); assert.deepEqual(pages.map(p => p.parts.map(part => part && part.component.file)), [ ['index.html'], @@ -116,7 +116,7 @@ describe('create_routes', () => { }); it('ignores files and directories with leading underscores', () => { - const { server_routes } = create_routes(path.join(__dirname, 'samples/hidden-underscore')); + const { server_routes } = manifest_data(path.join(__dirname, 'samples/hidden-underscore')); assert.deepEqual(server_routes.map(r => r.file), [ 'index.js', @@ -125,7 +125,7 @@ describe('create_routes', () => { }); it('ignores files and directories with leading dots except .well-known', () => { - const { server_routes } = create_routes(path.join(__dirname, 'samples/hidden-dot')); + const { server_routes } = manifest_data(path.join(__dirname, 'samples/hidden-dot')); assert.deepEqual(server_routes.map(r => r.file), [ '.well-known/dnt-policy.txt.js' @@ -134,24 +134,24 @@ describe('create_routes', () => { it('fails on clashes', () => { assert.throws(() => { - const { pages } = create_routes(path.join(__dirname, 'samples/clash-pages')); + const { pages } = manifest_data(path.join(__dirname, 'samples/clash-pages')); }, /The \[bar\]\/index\.html and \[foo\]\.html pages clash/); assert.throws(() => { - const { server_routes } = create_routes(path.join(__dirname, 'samples/clash-routes')); + const { server_routes } = manifest_data(path.join(__dirname, 'samples/clash-routes')); console.log(server_routes); }, /The \[bar\]\/index\.js and \[foo\]\.js routes clash/); }); it('fails if dynamic params are not separated', () => { assert.throws(() => { - create_routes(path.join(__dirname, 'samples/invalid-params')); + manifest_data(path.join(__dirname, 'samples/invalid-params')); }, /Invalid route \[foo\]\[bar\]\.js — parameters must be separated/); }); it('errors when trying to use reserved characters in route regexp', () => { assert.throws(() => { - create_routes(path.join(__dirname, 'samples/invalid-qualifier')); + manifest_data(path.join(__dirname, 'samples/invalid-qualifier')); }, /Invalid route \[foo\(\[a-z\]\(\[0-9\]\)\)\].js — cannot use \(, \), \? or \: in route qualifiers/); }); }); \ No newline at end of file diff --git a/test/unit/create_routes/samples/basic/about.html b/test/unit/create_manifest_data/samples/basic/about.html similarity index 100% rename from test/unit/create_routes/samples/basic/about.html rename to test/unit/create_manifest_data/samples/basic/about.html diff --git a/test/unit/create_routes/samples/basic/blog/[slug].html b/test/unit/create_manifest_data/samples/basic/blog/[slug].html similarity index 100% rename from test/unit/create_routes/samples/basic/blog/[slug].html rename to test/unit/create_manifest_data/samples/basic/blog/[slug].html diff --git a/test/unit/create_routes/samples/basic/blog/[slug].json.js b/test/unit/create_manifest_data/samples/basic/blog/[slug].json.js similarity index 100% rename from test/unit/create_routes/samples/basic/blog/[slug].json.js rename to test/unit/create_manifest_data/samples/basic/blog/[slug].json.js diff --git a/test/unit/create_routes/samples/basic/blog/_default.html b/test/unit/create_manifest_data/samples/basic/blog/_default.html similarity index 100% rename from test/unit/create_routes/samples/basic/blog/_default.html rename to test/unit/create_manifest_data/samples/basic/blog/_default.html diff --git a/test/unit/create_routes/samples/basic/blog/index.html b/test/unit/create_manifest_data/samples/basic/blog/index.html similarity index 100% rename from test/unit/create_routes/samples/basic/blog/index.html rename to test/unit/create_manifest_data/samples/basic/blog/index.html diff --git a/test/unit/create_routes/samples/basic/blog/index.json.js b/test/unit/create_manifest_data/samples/basic/blog/index.json.js similarity index 100% rename from test/unit/create_routes/samples/basic/blog/index.json.js rename to test/unit/create_manifest_data/samples/basic/blog/index.json.js diff --git a/test/unit/create_routes/samples/basic/index.html b/test/unit/create_manifest_data/samples/basic/index.html similarity index 100% rename from test/unit/create_routes/samples/basic/index.html rename to test/unit/create_manifest_data/samples/basic/index.html diff --git a/test/unit/create_routes/samples/clash-pages/[bar]/index.html b/test/unit/create_manifest_data/samples/clash-pages/[bar]/index.html similarity index 100% rename from test/unit/create_routes/samples/clash-pages/[bar]/index.html rename to test/unit/create_manifest_data/samples/clash-pages/[bar]/index.html diff --git a/test/unit/create_routes/samples/clash-pages/[foo].html b/test/unit/create_manifest_data/samples/clash-pages/[foo].html similarity index 100% rename from test/unit/create_routes/samples/clash-pages/[foo].html rename to test/unit/create_manifest_data/samples/clash-pages/[foo].html diff --git a/test/unit/create_routes/samples/clash-pages/index.html b/test/unit/create_manifest_data/samples/clash-pages/index.html similarity index 100% rename from test/unit/create_routes/samples/clash-pages/index.html rename to test/unit/create_manifest_data/samples/clash-pages/index.html diff --git a/test/unit/create_routes/samples/clash-routes/[bar]/index.js b/test/unit/create_manifest_data/samples/clash-routes/[bar]/index.js similarity index 100% rename from test/unit/create_routes/samples/clash-routes/[bar]/index.js rename to test/unit/create_manifest_data/samples/clash-routes/[bar]/index.js diff --git a/test/unit/create_routes/samples/clash-routes/[foo].js b/test/unit/create_manifest_data/samples/clash-routes/[foo].js similarity index 100% rename from test/unit/create_routes/samples/clash-routes/[foo].js rename to test/unit/create_manifest_data/samples/clash-routes/[foo].js diff --git a/test/unit/create_routes/samples/clash-routes/index.html b/test/unit/create_manifest_data/samples/clash-routes/index.html similarity index 100% rename from test/unit/create_routes/samples/clash-routes/index.html rename to test/unit/create_manifest_data/samples/clash-routes/index.html diff --git a/test/unit/create_routes/samples/encoding/#.html b/test/unit/create_manifest_data/samples/encoding/#.html similarity index 100% rename from test/unit/create_routes/samples/encoding/#.html rename to test/unit/create_manifest_data/samples/encoding/#.html diff --git a/test/unit/create_routes/samples/hidden-dot/.unknown/foo.txt.js b/test/unit/create_manifest_data/samples/hidden-dot/.unknown/foo.txt.js similarity index 100% rename from test/unit/create_routes/samples/hidden-dot/.unknown/foo.txt.js rename to test/unit/create_manifest_data/samples/hidden-dot/.unknown/foo.txt.js diff --git a/test/unit/create_routes/samples/hidden-dot/.well-known/dnt-policy.txt.js b/test/unit/create_manifest_data/samples/hidden-dot/.well-known/dnt-policy.txt.js similarity index 100% rename from test/unit/create_routes/samples/hidden-dot/.well-known/dnt-policy.txt.js rename to test/unit/create_manifest_data/samples/hidden-dot/.well-known/dnt-policy.txt.js diff --git a/test/unit/create_routes/samples/hidden-underscore/_foo.js b/test/unit/create_manifest_data/samples/hidden-underscore/_foo.js similarity index 100% rename from test/unit/create_routes/samples/hidden-underscore/_foo.js rename to test/unit/create_manifest_data/samples/hidden-underscore/_foo.js diff --git a/test/unit/create_routes/samples/hidden-underscore/a/_b/c/d.js b/test/unit/create_manifest_data/samples/hidden-underscore/a/_b/c/d.js similarity index 100% rename from test/unit/create_routes/samples/hidden-underscore/a/_b/c/d.js rename to test/unit/create_manifest_data/samples/hidden-underscore/a/_b/c/d.js diff --git a/test/unit/create_routes/samples/hidden-underscore/e/f/g/h.js b/test/unit/create_manifest_data/samples/hidden-underscore/e/f/g/h.js similarity index 100% rename from test/unit/create_routes/samples/hidden-underscore/e/f/g/h.js rename to test/unit/create_manifest_data/samples/hidden-underscore/e/f/g/h.js diff --git a/test/unit/create_routes/samples/hidden-underscore/i/_j.js b/test/unit/create_manifest_data/samples/hidden-underscore/i/_j.js similarity index 100% rename from test/unit/create_routes/samples/hidden-underscore/i/_j.js rename to test/unit/create_manifest_data/samples/hidden-underscore/i/_j.js diff --git a/test/unit/create_routes/samples/hidden-underscore/index.html b/test/unit/create_manifest_data/samples/hidden-underscore/index.html similarity index 100% rename from test/unit/create_routes/samples/hidden-underscore/index.html rename to test/unit/create_manifest_data/samples/hidden-underscore/index.html diff --git a/test/unit/create_routes/samples/hidden-underscore/index.js b/test/unit/create_manifest_data/samples/hidden-underscore/index.js similarity index 100% rename from test/unit/create_routes/samples/hidden-underscore/index.js rename to test/unit/create_manifest_data/samples/hidden-underscore/index.js diff --git a/test/unit/create_routes/samples/invalid-params/[foo][bar].js b/test/unit/create_manifest_data/samples/invalid-params/[foo][bar].js similarity index 100% rename from test/unit/create_routes/samples/invalid-params/[foo][bar].js rename to test/unit/create_manifest_data/samples/invalid-params/[foo][bar].js diff --git a/test/unit/create_routes/samples/invalid-qualifier/[foo([a-z]([0-9]))].js b/test/unit/create_manifest_data/samples/invalid-qualifier/[foo([a-z]([0-9]))].js similarity index 100% rename from test/unit/create_routes/samples/invalid-qualifier/[foo([a-z]([0-9]))].js rename to test/unit/create_manifest_data/samples/invalid-qualifier/[foo([a-z]([0-9]))].js diff --git a/test/unit/create_routes/samples/qualifiers/[slug([0-9-a-z]{3,})].html b/test/unit/create_manifest_data/samples/qualifiers/[slug([0-9-a-z]{3,})].html similarity index 100% rename from test/unit/create_routes/samples/qualifiers/[slug([0-9-a-z]{3,})].html rename to test/unit/create_manifest_data/samples/qualifiers/[slug([0-9-a-z]{3,})].html diff --git a/test/unit/create_routes/samples/qualifiers/[slug([a-z]{2})].html b/test/unit/create_manifest_data/samples/qualifiers/[slug([a-z]{2})].html similarity index 100% rename from test/unit/create_routes/samples/qualifiers/[slug([a-z]{2})].html rename to test/unit/create_manifest_data/samples/qualifiers/[slug([a-z]{2})].html diff --git a/test/unit/create_routes/samples/qualifiers/[slug].html b/test/unit/create_manifest_data/samples/qualifiers/[slug].html similarity index 100% rename from test/unit/create_routes/samples/qualifiers/[slug].html rename to test/unit/create_manifest_data/samples/qualifiers/[slug].html diff --git a/test/unit/create_routes/samples/sorting/[wildcard].html b/test/unit/create_manifest_data/samples/sorting/[wildcard].html similarity index 100% rename from test/unit/create_routes/samples/sorting/[wildcard].html rename to test/unit/create_manifest_data/samples/sorting/[wildcard].html diff --git a/test/unit/create_routes/samples/sorting/_layout.html b/test/unit/create_manifest_data/samples/sorting/_layout.html similarity index 100% rename from test/unit/create_routes/samples/sorting/_layout.html rename to test/unit/create_manifest_data/samples/sorting/_layout.html diff --git a/test/unit/create_routes/samples/sorting/about.html b/test/unit/create_manifest_data/samples/sorting/about.html similarity index 100% rename from test/unit/create_routes/samples/sorting/about.html rename to test/unit/create_manifest_data/samples/sorting/about.html diff --git a/test/unit/create_routes/samples/sorting/index.html b/test/unit/create_manifest_data/samples/sorting/index.html similarity index 100% rename from test/unit/create_routes/samples/sorting/index.html rename to test/unit/create_manifest_data/samples/sorting/index.html diff --git a/test/unit/create_routes/samples/sorting/post/[id([0-9-a-z]{3,})].html b/test/unit/create_manifest_data/samples/sorting/post/[id([0-9-a-z]{3,})].html similarity index 100% rename from test/unit/create_routes/samples/sorting/post/[id([0-9-a-z]{3,})].html rename to test/unit/create_manifest_data/samples/sorting/post/[id([0-9-a-z]{3,})].html diff --git a/test/unit/create_routes/samples/sorting/post/[id].html b/test/unit/create_manifest_data/samples/sorting/post/[id].html similarity index 100% rename from test/unit/create_routes/samples/sorting/post/[id].html rename to test/unit/create_manifest_data/samples/sorting/post/[id].html diff --git a/test/unit/create_routes/samples/sorting/post/_default.html b/test/unit/create_manifest_data/samples/sorting/post/_default.html similarity index 100% rename from test/unit/create_routes/samples/sorting/post/_default.html rename to test/unit/create_manifest_data/samples/sorting/post/_default.html diff --git a/test/unit/create_routes/samples/sorting/post/bar.html b/test/unit/create_manifest_data/samples/sorting/post/bar.html similarity index 100% rename from test/unit/create_routes/samples/sorting/post/bar.html rename to test/unit/create_manifest_data/samples/sorting/post/bar.html diff --git a/test/unit/create_routes/samples/sorting/post/f[xx].html b/test/unit/create_manifest_data/samples/sorting/post/f[xx].html similarity index 100% rename from test/unit/create_routes/samples/sorting/post/f[xx].html rename to test/unit/create_manifest_data/samples/sorting/post/f[xx].html diff --git a/test/unit/create_routes/samples/sorting/post/foo.html b/test/unit/create_manifest_data/samples/sorting/post/foo.html similarity index 100% rename from test/unit/create_routes/samples/sorting/post/foo.html rename to test/unit/create_manifest_data/samples/sorting/post/foo.html diff --git a/test/unit/create_routes/samples/sorting/post/index.html b/test/unit/create_manifest_data/samples/sorting/post/index.html similarity index 100% rename from test/unit/create_routes/samples/sorting/post/index.html rename to test/unit/create_manifest_data/samples/sorting/post/index.html From 57a26e3511353bf6c7f62c8fa95a2347a2d17b42 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 14:50:01 -0400 Subject: [PATCH 057/178] -> v0.19.0 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb6152f..b60b657 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # sapper changelog +## 0.19.0 + +* Extract styles out of JS into .css files, for Rollup apps ([#388](https://github.com/sveltejs/sapper/issues/388)) +* Fix `prefetchRoutes` ([#380](https://github.com/sveltejs/sapper/issues/380)) + ## 0.18.7 * Support differential bundling for Rollup apps via a `--legacy` flag ([#280](https://github.com/sveltejs/sapper/issues/280)) diff --git a/package.json b/package.json index 26da3ca..988bf09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.18.7", + "version": "0.19.0", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From fe5a8fb1e7aa4a8415e2f493c500d2626a901b39 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 14:53:06 -0400 Subject: [PATCH 058/178] dont include .map files in package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 988bf09..9d6c154 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "config", "sapper", "components", - "dist" + "dist/*.js" ], "directories": { "test": "test" From a18af2a4732670d530a89eec3368c257b0554ea6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 17:01:29 -0400 Subject: [PATCH 059/178] dont include origin in export redirects --- src/api/export.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/export.ts b/src/api/export.ts index 408bb18..11d1ad9 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -170,7 +170,7 @@ async function execute(emitter: EventEmitter, opts: Opts) { const location = r.headers.get('Location'); type = 'text/html'; - body = ``; + body = ``; await handle(resolve(root.href, location)); } From a56ee6bdb7697062d6670b6a8565873e20d40015 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 17:01:43 -0400 Subject: [PATCH 060/178] regenerate lockfile --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index bae5e68..55ca862 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.18.7", + "version": "0.19.0", "lockfileVersion": 1, "requires": true, "dependencies": { From 45f4c47a3e42c154184f92e6a53f8d17b7258b02 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 18:41:17 -0400 Subject: [PATCH 061/178] oops that wasnt quite right --- src/api/export.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/export.ts b/src/api/export.ts index 11d1ad9..d2bbd40 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -170,7 +170,7 @@ async function execute(emitter: EventEmitter, opts: Opts) { const location = r.headers.get('Location'); type = 'text/html'; - body = ``; + body = ``; await handle(resolve(root.href, location)); } From c0b833862a66bd643a3588736191b196030e4018 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 18:41:32 -0400 Subject: [PATCH 062/178] -> v0.19.1 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b60b657..ced4ede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.19.1 + +* Don't include local origin in export redirects ([#409](https://github.com/sveltejs/sapper/pull/409)) + ## 0.19.0 * Extract styles out of JS into .css files, for Rollup apps ([#388](https://github.com/sveltejs/sapper/issues/388)) diff --git a/package.json b/package.json index 9d6c154..36d956f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.19.0", + "version": "0.19.1", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From 1b6dfd3580c88cbaf88f13a9e7b962392c643a57 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 20:33:00 -0400 Subject: [PATCH 063/178] ignore things that look like temp files when generating manifest data - fixes #220 --- src/core/create_manifest_data.ts | 6 +++- test/unit/create_manifest_data/index.ts | 33 ++++++++++++------- .../samples/lockfiles/foo.js | 0 .../samples/lockfiles/foo.js_tmp | 0 4 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 test/unit/create_manifest_data/samples/lockfiles/foo.js create mode 100644 test/unit/create_manifest_data/samples/lockfiles/foo.js_tmp diff --git a/src/core/create_manifest_data.ts b/src/core/create_manifest_data.ts index 83788c2..367317b 100644 --- a/src/core/create_manifest_data.ts +++ b/src/core/create_manifest_data.ts @@ -30,13 +30,16 @@ export default function create_manifest_data(cwd = locations.routes()): Manifest const file = path.relative(cwd, resolved); const is_dir = fs.statSync(resolved).isDirectory(); + const ext = path.extname(basename); + if (!is_dir && !/^\.[a-z]+$/i.test(ext)) return null; // filter out tmp files etc + const segment = is_dir ? basename : basename.slice(0, -path.extname(basename).length); const parts = get_parts(segment); const is_index = is_dir ? false : basename.startsWith('index.'); - const is_page = path.extname(basename) === '.html'; + const is_page = ext === '.html'; parts.forEach(part => { if (/\]\[/.test(part.content)) { @@ -57,6 +60,7 @@ export default function create_manifest_data(cwd = locations.routes()): Manifest is_page }; }) + .filter(Boolean) .sort(comparator); items.forEach(item => { diff --git a/test/unit/create_manifest_data/index.ts b/test/unit/create_manifest_data/index.ts index e847b8a..d21a537 100644 --- a/test/unit/create_manifest_data/index.ts +++ b/test/unit/create_manifest_data/index.ts @@ -1,10 +1,10 @@ import * as path from 'path'; import * as assert from 'assert'; -import manifest_data from '../../../src/core/create_manifest_data'; +import create_manifest_data from '../../../src/core/create_manifest_data'; describe('manifest_data', () => { it('creates routes', () => { - const { components, pages, server_routes } = manifest_data(path.join(__dirname, 'samples/basic')); + const { components, pages, server_routes } = create_manifest_data(path.join(__dirname, 'samples/basic')); const index = { name: 'index', file: 'index.html' }; const about = { name: 'about', file: 'about.html' }; @@ -68,7 +68,7 @@ describe('manifest_data', () => { }); it('encodes invalid characters', () => { - const { components, pages } = manifest_data(path.join(__dirname, 'samples/encoding')); + const { components, pages } = create_manifest_data(path.join(__dirname, 'samples/encoding')); // had to remove ? and " because windows @@ -90,7 +90,7 @@ describe('manifest_data', () => { }); it('allows regex qualifiers', () => { - const { pages } = manifest_data(path.join(__dirname, 'samples/qualifiers')); + const { pages } = create_manifest_data(path.join(__dirname, 'samples/qualifiers')); assert.deepEqual(pages.map(p => p.pattern), [ /^\/([0-9-a-z]{3,})\/?$/, @@ -100,7 +100,7 @@ describe('manifest_data', () => { }); it('sorts routes correctly', () => { - const { pages } = manifest_data(path.join(__dirname, 'samples/sorting')); + const { pages } = create_manifest_data(path.join(__dirname, 'samples/sorting')); assert.deepEqual(pages.map(p => p.parts.map(part => part && part.component.file)), [ ['index.html'], @@ -116,7 +116,7 @@ describe('manifest_data', () => { }); it('ignores files and directories with leading underscores', () => { - const { server_routes } = manifest_data(path.join(__dirname, 'samples/hidden-underscore')); + const { server_routes } = create_manifest_data(path.join(__dirname, 'samples/hidden-underscore')); assert.deepEqual(server_routes.map(r => r.file), [ 'index.js', @@ -125,7 +125,7 @@ describe('manifest_data', () => { }); it('ignores files and directories with leading dots except .well-known', () => { - const { server_routes } = manifest_data(path.join(__dirname, 'samples/hidden-dot')); + const { server_routes } = create_manifest_data(path.join(__dirname, 'samples/hidden-dot')); assert.deepEqual(server_routes.map(r => r.file), [ '.well-known/dnt-policy.txt.js' @@ -134,24 +134,35 @@ describe('manifest_data', () => { it('fails on clashes', () => { assert.throws(() => { - const { pages } = manifest_data(path.join(__dirname, 'samples/clash-pages')); + const { pages } = create_manifest_data(path.join(__dirname, 'samples/clash-pages')); }, /The \[bar\]\/index\.html and \[foo\]\.html pages clash/); assert.throws(() => { - const { server_routes } = manifest_data(path.join(__dirname, 'samples/clash-routes')); + const { server_routes } = create_manifest_data(path.join(__dirname, 'samples/clash-routes')); console.log(server_routes); }, /The \[bar\]\/index\.js and \[foo\]\.js routes clash/); }); it('fails if dynamic params are not separated', () => { assert.throws(() => { - manifest_data(path.join(__dirname, 'samples/invalid-params')); + create_manifest_data(path.join(__dirname, 'samples/invalid-params')); }, /Invalid route \[foo\]\[bar\]\.js — parameters must be separated/); }); it('errors when trying to use reserved characters in route regexp', () => { assert.throws(() => { - manifest_data(path.join(__dirname, 'samples/invalid-qualifier')); + create_manifest_data(path.join(__dirname, 'samples/invalid-qualifier')); }, /Invalid route \[foo\(\[a-z\]\(\[0-9\]\)\)\].js — cannot use \(, \), \? or \: in route qualifiers/); }); + + it('ignores things that look like lockfiles' , () => { + const { server_routes } = create_manifest_data(path.join(__dirname, 'samples/lockfiles')); + + assert.deepEqual(server_routes, [{ + file: 'foo.js', + name: 'route_foo', + params: [], + pattern: /^\/foo$/ + }]); + }); }); \ No newline at end of file diff --git a/test/unit/create_manifest_data/samples/lockfiles/foo.js b/test/unit/create_manifest_data/samples/lockfiles/foo.js new file mode 100644 index 0000000..e69de29 diff --git a/test/unit/create_manifest_data/samples/lockfiles/foo.js_tmp b/test/unit/create_manifest_data/samples/lockfiles/foo.js_tmp new file mode 100644 index 0000000..e69de29 From 64223b572bf838cc16f2cb12ab01002a65783cfa Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 20:56:50 -0400 Subject: [PATCH 064/178] ignore clicks on elements without hrefs - fixes #235 --- src/runtime/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 1d7210c..83c0a76 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -336,6 +336,8 @@ function handle_click(event: MouseEvent) { const a: HTMLAnchorElement | SVGAElement = findAnchor(event.target); if (!a) return; + if (!a.href) return; + // check if link is inside an svg // in this case, both href and target are always inside an object const svg = typeof a.href === 'object' && a.href.constructor.name === 'SVGAnimatedString'; From 2c507b5a2e3111c41b54487dbf40025fa9a87834 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 21:46:25 -0400 Subject: [PATCH 065/178] allow reserved words as route names - fixes #315 --- src/core/create_manifest_data.ts | 7 +++-- src/core/utils.ts | 53 +++++++++++++++++++++++++++++++- test/app/routes/const.html | 1 + test/app/routes/index.html | 1 + test/common/test.js | 8 +++++ 5 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 test/app/routes/const.html diff --git a/src/core/create_manifest_data.ts b/src/core/create_manifest_data.ts index 367317b..fd29a9b 100644 --- a/src/core/create_manifest_data.ts +++ b/src/core/create_manifest_data.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { locations } from '../config'; import { Page, PageComponent, ServerRoute, ManifestData } from '../interfaces'; -import { posixify } from './utils'; +import { posixify, reserved_words } from './utils'; export default function create_manifest_data(cwd = locations.routes()): ManifestData { const components: PageComponent[] = []; @@ -269,7 +269,7 @@ function get_parts(part: string): Part[] { } function get_slug(file: string) { - return file + let name = file .replace(/[\\\/]index/, '') .replace(/_default([\/\\index])?\.html$/, 'index') .replace(/[\/\\]/g, '_') @@ -278,6 +278,9 @@ function get_slug(file: string) { .replace(/[^a-zA-Z0-9_$]/g, c => { return c === '.' ? '_' : `$${c.charCodeAt(0)}` }); + + if (reserved_words.has(name)) name += '_'; + return name; } function get_pattern(segments: Part[][], add_trailing_slash: boolean) { diff --git a/src/core/utils.ts b/src/core/utils.ts index fb963ae..2668e68 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -22,4 +22,55 @@ export function fudge_mtime(file: string) { new Date(atime.getTime() - 999999), new Date(mtime.getTime() - 999999) ); -} \ No newline at end of file +} + +export const reserved_words = new Set([ + 'arguments', + 'await', + 'break', + 'case', + 'catch', + 'class', + 'const', + 'continue', + 'debugger', + 'default', + 'delete', + 'do', + 'else', + 'enum', + 'eval', + 'export', + 'extends', + 'false', + 'finally', + 'for', + 'function', + 'if', + 'implements', + 'import', + 'in', + 'instanceof', + 'interface', + 'let', + 'new', + 'null', + 'package', + 'private', + 'protected', + 'public', + 'return', + 'static', + 'super', + 'switch', + 'this', + 'throw', + 'true', + 'try', + 'typeof', + 'var', + 'void', + 'while', + 'with', + 'yield', +]); \ No newline at end of file diff --git a/test/app/routes/const.html b/test/app/routes/const.html new file mode 100644 index 0000000..6975aae --- /dev/null +++ b/test/app/routes/const.html @@ -0,0 +1 @@ +

reserved words are okay as routes

\ No newline at end of file diff --git a/test/app/routes/index.html b/test/app/routes/index.html index e5eecde..4678fbd 100644 --- a/test/app/routes/index.html +++ b/test/app/routes/index.html @@ -14,6 +14,7 @@
error link credentials blog +const
diff --git a/test/common/test.js b/test/common/test.js index 2ad9730..abf3b27 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -743,6 +743,14 @@ function run({ mode, basepath = '' }) { assert.equal(title, 'root preload function ran: true'); }); }); + + it('allows reserved words as route names', () => { + return nightmare.goto(`${base}/const`).init() + .then(() => nightmare.page.title()) + .then(title => { + assert.equal(title, 'reserved words are okay as routes'); + }); + }); }); describe('headers', () => { From bf9cbe2f3b2efed6d3eee9352198eee18e58adcc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 21:53:46 -0400 Subject: [PATCH 066/178] print details of webpack errors - fixes #403 --- src/core/create_compilers/WebpackCompiler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/create_compilers/WebpackCompiler.ts b/src/core/create_compilers/WebpackCompiler.ts index 7c8863f..f4d58bc 100644 --- a/src/core/create_compilers/WebpackCompiler.ts +++ b/src/core/create_compilers/WebpackCompiler.ts @@ -28,8 +28,7 @@ export class WebpackCompiler { const result = new WebpackResult(stats); if (result.errors.length) { - // TODO print errors - // console.error(stats.toString({ colors: true })); + console.error(stats.toString({ colors: true })); reject(new Error(`Encountered errors while building app`)); } From 8b60d568dc239acf94d7905f34566e09fe5500ca Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 21:56:45 -0400 Subject: [PATCH 067/178] -> v0.19.2 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ced4ede..14a9818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # sapper changelog +## 0.19.2 + +* Ignore editor tmp files ([#220](https://github.com/sveltejs/sapper/issues/220)) +* Ignore clicks an `` element without `href` ([#235](https://github.com/sveltejs/sapper/issues/235)) +* Allow routes that are reserved JavaScript words ([#315](https://github.com/sveltejs/sapper/issues/315)) +* Print out webpack errors ([#403](https://github.com/sveltejs/sapper/issues/403)) + ## 0.19.1 * Don't include local origin in export redirects ([#409](https://github.com/sveltejs/sapper/pull/409)) diff --git a/package.json b/package.json index 36d956f..da67c88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.19.1", + "version": "0.19.2", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From 6ccae0cd3313e135bd9f6e6634f56821ed2c2dfa Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Sep 2018 23:00:39 -0400 Subject: [PATCH 068/178] better unicode handling - fixes #347, i think --- src/middleware.ts | 3 ++- test/app/routes/fünke.html | 12 +++++++++++- test/app/routes/fünke.json.js | 9 +++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 test/app/routes/fünke.json.js diff --git a/src/middleware.ts b/src/middleware.ts index 171481d..15b4bbf 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -185,7 +185,8 @@ function serve({ prefix, pathname, cache_control }: { const type = lookup(req.path); try { - const data = read(req.path.slice(1)); + const file = decodeURIComponent(req.path.slice(1)); + const data = read(file); res.setHeader('Content-Type', type); res.setHeader('Cache-Control', cache_control); diff --git a/test/app/routes/fünke.html b/test/app/routes/fünke.html index 088a7aa..e1fba6c 100644 --- a/test/app/routes/fünke.html +++ b/test/app/routes/fünke.html @@ -1 +1,11 @@ -

I'm afraid I just blue myself

\ No newline at end of file +

{phrase}

+ + \ No newline at end of file diff --git a/test/app/routes/fünke.json.js b/test/app/routes/fünke.json.js new file mode 100644 index 0000000..a9ccd6e --- /dev/null +++ b/test/app/routes/fünke.json.js @@ -0,0 +1,9 @@ +export function get(req, res) { + res.writeHead(200, { + 'Content-Type': 'application/json' + }); + + res.end(JSON.stringify( + "I'm afraid I just blue myself" + )); +} \ No newline at end of file From 0cc5ff95d6a475c9a651f36e9bcfb4ccaf74abc0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Sep 2018 07:54:39 -0400 Subject: [PATCH 069/178] minor tidy up --- test/common/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/test.js b/test/common/test.js index abf3b27..d192fea 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -746,7 +746,7 @@ function run({ mode, basepath = '' }) { it('allows reserved words as route names', () => { return nightmare.goto(`${base}/const`).init() - .then(() => nightmare.page.title()) + .page.title() .then(title => { assert.equal(title, 'reserved words are okay as routes'); }); From 499b377bfd71ab0eddf9ef2a3cc0c9083859962b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Sep 2018 07:55:30 -0400 Subject: [PATCH 070/178] -> v0.19.3 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14a9818..1e895e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.19.3 + +* Better unicode route handling ([#347](https://github.com/sveltejs/sapper/issues/347)) + ## 0.19.2 * Ignore editor tmp files ([#220](https://github.com/sveltejs/sapper/issues/220)) diff --git a/package.json b/package.json index da67c88..0337bef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.19.2", + "version": "0.19.3", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From 0706b5f50ab8f10b5c1d884c14b28a71a02804e3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Sep 2018 09:02:13 -0400 Subject: [PATCH 071/178] decode req.path during export --- src/api/export.ts | 2 +- test/app/routes/blog/_posts.js | 8 ++++++++ test/common/test.js | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/api/export.ts b/src/api/export.ts index d2bbd40..d4deaca 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -85,7 +85,7 @@ async function execute(emitter: EventEmitter, opts: Opts) { function save(path: string, status: number, type: string, body: string) { const { pathname } = resolve(origin, path); - let file = pathname.slice(1); + let file = decodeURIComponent(pathname.slice(1)); if (saved.has(file)) return; saved.add(file); diff --git a/test/app/routes/blog/_posts.js b/test/app/routes/blog/_posts.js index d5e0dc2..fa73826 100644 --- a/test/app/routes/blog/_posts.js +++ b/test/app/routes/blog/_posts.js @@ -106,6 +106,14 @@ const posts = [

If you didn't have adult onset diabetes, I wouldn't mind giving you a little sugar. Everybody dance NOW. And the soup of the day is bread. Great, now I'm gonna smell to high heaven like a tuna melt!

That's how Tony Wonder lost a nut. She calls it a Mayonegg. Go ahead, touch the Cornballer. There's a new daddy in town. A discipline daddy.

` + }, + + { + title: 'Encödïng test', + slug: 'encödïng-test', + html: ` +

It works

+ ` } ]; diff --git a/test/common/test.js b/test/common/test.js index d192fea..4db2d30 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -93,6 +93,7 @@ function testExport({ basepath = '' }) { 'blog/how-to-use-sapper/index.html', 'blog/what-is-sapper/index.html', 'blog/why-the-name/index.html', + 'blog/encödïng-test/index.html', 'blog.json', 'blog/a-very-long-post.json', @@ -101,6 +102,7 @@ function testExport({ basepath = '' }) { 'blog/how-to-use-sapper.json', 'blog/what-is-sapper.json', 'blog/why-the-name.json', + 'blog/encödïng-test.json', 'favicon.png', 'global.css', From ae90f774e1ef826d90469c4a6f455e7d0015d4b9 Mon Sep 17 00:00:00 2001 From: Nikolai Sivertsen Date: Tue, 4 Sep 2018 00:26:18 +0200 Subject: [PATCH 072/178] Enable source maps by default in dev mode when using rollup --- src/rollup.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rollup.ts b/src/rollup.ts index 2941021..1240e21 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -30,7 +30,8 @@ export default { output: () => { return { dir: locations.dest(), - format: 'cjs' + format: 'cjs', + sourcemap: dev() }; } }, From 30966ee7f21e0f51cba6191c025a95ab9ce8839f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Sep 2018 18:36:07 -0400 Subject: [PATCH 073/178] decode req.params - fixes #417 --- src/core/create_manifests.ts | 10 +++++--- test/app/app/client.js | 5 ++-- test/app/routes/echo/page/[slug].html | 11 +++++++++ test/app/routes/echo/server-route/[slug].js | 15 ++++++++++++ test/app/routes/index.html | 1 + test/common/test.js | 26 +++++++++++++++++++++ 6 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 test/app/routes/echo/page/[slug].html create mode 100644 test/app/routes/echo/server-route/[slug].js diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index 3c904c9..3d260e6 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -63,6 +63,8 @@ function generate_client( import root from '${get_file(path_to_routes, manifest_data.root)}'; import error from '${posixify(`${path_to_routes}/_error.html`)}'; + const d = decodeURIComponent; + ${manifest_data.components.map(component => { const annotation = bundler === 'webpack' ? `/* webpackChunkName: "${component.name}" */ ` @@ -88,7 +90,7 @@ function generate_client( if (part === null) return 'null'; if (part.params.length > 0) { - const props = part.params.map((param, i) => `${param}: match[${i + 1}]`); + const props = part.params.map((param, i) => `${param}: d(match[${i + 1}])`); return `{ component: ${part.component.name}, params: match => ({ ${props.join(', ')} }) }`; } @@ -138,6 +140,8 @@ function generate_server( // This file is generated by Sapper — do not edit it! ${imports.join('\n')} + const d = decodeURIComponent; + export const manifest = { server_routes: [ ${manifest_data.server_routes.map(route => `{ @@ -145,7 +149,7 @@ function generate_server( pattern: ${route.pattern}, handlers: ${route.name}, params: ${route.params.length > 0 - ? `match => ({ ${route.params.map((param, i) => `${param}: match[${i + 1}]`).join(', ')} })` + ? `match => ({ ${route.params.map((param, i) => `${param}: d(match[${i + 1}])`).join(', ')} })` : `() => ({})`} }`).join(',\n\n\t\t\t\t')} ], @@ -165,7 +169,7 @@ function generate_server( ]; if (part.params.length > 0) { - const params = part.params.map((param, i) => `${param}: match[${i + 1}]`); + const params = part.params.map((param, i) => `${param}: d(match[${i + 1}])`); props.push(`params: match => ({ ${params.join(', ')} })`); } diff --git a/test/app/app/client.js b/test/app/app/client.js index 2ca0ebe..d1c2e96 100644 --- a/test/app/app/client.js +++ b/test/app/app/client.js @@ -1,4 +1,4 @@ -import { init, prefetchRoutes } from '../../../runtime.js'; +import { init, goto, prefetchRoutes } from '../../../runtime.js'; import { Store } from 'svelte/store.js'; import { manifest } from './manifest/client.js'; @@ -10,4 +10,5 @@ window.init = () => { }); }; -window.prefetchRoutes = prefetchRoutes; \ No newline at end of file +window.prefetchRoutes = prefetchRoutes; +window.goto = goto; \ No newline at end of file diff --git a/test/app/routes/echo/page/[slug].html b/test/app/routes/echo/page/[slug].html new file mode 100644 index 0000000..f393c6a --- /dev/null +++ b/test/app/routes/echo/page/[slug].html @@ -0,0 +1,11 @@ +

{slug}

+ + \ No newline at end of file diff --git a/test/app/routes/echo/server-route/[slug].js b/test/app/routes/echo/server-route/[slug].js new file mode 100644 index 0000000..e0e9ef7 --- /dev/null +++ b/test/app/routes/echo/server-route/[slug].js @@ -0,0 +1,15 @@ +export function get(req, res) { + res.writeHead(200, { + 'Content-Type': 'text/html' + }); + + res.end(` + + + + +

${req.params.slug}

+ + + `); +} \ No newline at end of file diff --git a/test/app/routes/index.html b/test/app/routes/index.html index 4678fbd..9ecc6a7 100644 --- a/test/app/routes/index.html +++ b/test/app/routes/index.html @@ -15,6 +15,7 @@
credentials blog const +echo/page/encöding
diff --git a/test/common/test.js b/test/common/test.js index d192fea..91240c3 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -751,6 +751,32 @@ function run({ mode, basepath = '' }) { assert.equal(title, 'reserved words are okay as routes'); }); }); + + it('encodes req.params for server-rendered pages', () => { + return nightmare.goto(`${base}/echo/page/encöded`) + .page.title() + .then(title => { + assert.equal(title, 'encöded'); + }); + }); + + it('encodes req.params for client-rendered pages', () => { + return nightmare.goto(base).init() + .click('a[href="echo/page/encöding"]') + .wait(100) + .page.title() + .then(title => { + assert.equal(title, 'encöding'); + }); + }); + + it('encodes req.params for server routes', () => { + return nightmare.goto(`${base}/echo/server-route/encöded`) + .page.title() + .then(title => { + assert.equal(title, 'encöded'); + }); + }); }); describe('headers', () => { From 9ef4f33e3853d25a3627703a987fc73605e787df Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Sep 2018 20:09:25 -0400 Subject: [PATCH 074/178] decode query params --- src/runtime/index.ts | 2 +- test/app/routes/echo/page/[slug].html | 7 ++++--- test/app/routes/index.html | 2 +- test/common/test.js | 12 ++++++------ 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 83c0a76..9530604 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -67,7 +67,7 @@ function select_route(url: URL): Target { if (url.search.length > 0) { url.search.slice(1).split('&').forEach(searchParam => { const [, key, value] = /([^=]+)=(.*)/.exec(searchParam); - query[key] = value || true; + query[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : true; }); } return { url, path, page, match, query }; diff --git a/test/app/routes/echo/page/[slug].html b/test/app/routes/echo/page/[slug].html index f393c6a..7b2f0a8 100644 --- a/test/app/routes/echo/page/[slug].html +++ b/test/app/routes/echo/page/[slug].html @@ -1,10 +1,11 @@ -

{slug}

+

{slug} ({message})

`) + .replace('%sapper.scripts%', () => `${script}`) .replace('%sapper.html%', () => html) .replace('%sapper.head%', () => `${head}`) .replace('%sapper.styles%', () => styles); From 68e93a8fa06998fe14ba4159dc9f3cef7e5529d0 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 7 Sep 2018 11:07:50 -0700 Subject: [PATCH 078/178] add cache-control:immutable for immutable assets --- src/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware.ts b/src/middleware.ts index 15b4bbf..bab6536 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -151,7 +151,7 @@ export default function middleware(opts: { serve({ prefix: '/client/', - cache_control: dev() ? 'no-cache' : 'max-age=31536000' + cache_control: dev() ? 'no-cache' : 'max-age=31536000, immutable' }), get_server_route_handler(manifest.server_routes), From 68b78f56d65d7891520ad3b9e04f2f577924d7ad Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 7 Sep 2018 16:42:56 -0400 Subject: [PATCH 079/178] remove unused file --- components/default-layout.html | 1 - 1 file changed, 1 deletion(-) delete mode 100644 components/default-layout.html diff --git a/components/default-layout.html b/components/default-layout.html deleted file mode 100644 index 8c0dcbb..0000000 --- a/components/default-layout.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 64b16715cdc468bc38a7e8efb038ead10f64988d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 7 Sep 2018 18:02:58 -0400 Subject: [PATCH 080/178] handle value-less query string parameters - fixes #426 --- src/runtime/index.ts | 4 ++-- test/app/routes/index.html | 1 + test/common/test.js | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 9530604..9e07bad 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -66,8 +66,8 @@ function select_route(url: URL): Target { const query: Record = {}; if (url.search.length > 0) { url.search.slice(1).split('&').forEach(searchParam => { - const [, key, value] = /([^=]+)=(.*)/.exec(searchParam); - query[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : true; + const [, key, value] = /([^=]+)(?:=(.*))?/.exec(searchParam); + query[key] = decodeURIComponent((value || '').replace(/\+/g, ' ')); }); } return { url, path, page, match, query }; diff --git a/test/app/routes/index.html b/test/app/routes/index.html index 3b889ef..65c7e59 100644 --- a/test/app/routes/index.html +++ b/test/app/routes/index.html @@ -16,6 +16,7 @@ blog const echo/page/encöded?message=hëllö+wörld +echo/page/empty?message
diff --git a/test/common/test.js b/test/common/test.js index 8a61b59..f149911 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -772,6 +772,24 @@ function run({ mode, basepath = '' }) { }); }); + it('accepts value-less query string parameter on server', () => { + return nightmare.goto(`${base}/echo/page/empty?message`) + .page.title() + .then(title => { + assert.equal(title, 'empty ()'); + }); + }); + + it('accepts value-less query string parameter on client', () => { + return nightmare.goto(base).init() + .click('a[href="echo/page/empty?message"]') + .wait(100) + .page.title() + .then(title => { + assert.equal(title, 'empty ()'); + }); + }); + it('encodes req.params for server routes', () => { return nightmare.goto(`${base}/echo/server-route/encöded`) .page.title() From 0e8ed6612c1c9a3084e3639787aba6481bb2a03f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 7 Sep 2018 18:19:39 -0400 Subject: [PATCH 081/178] -> v0.20.2 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2c9e80..84b6897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # sapper changelog +## 0.20.2 + +* Add `immutable` cache control header for hashed assets ([#425](https://github.com/sveltejs/sapper/pull/425)) +* Handle value-less query string params ([#426](https://github.com/sveltejs/sapper/issues/426)) + ## 0.20.1 * Update shimport diff --git a/package.json b/package.json index 3e5fcc8..10059af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.20.1", + "version": "0.20.2", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From 5ee53a98c6c3c09f9069be3851c92a590dfd4961 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 7 Sep 2018 16:28:38 -0700 Subject: [PATCH 082/178] set service-worker max-age to 0 --- src/middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index bab6536..20102ac 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -141,12 +141,12 @@ export default function middleware(opts: { fs.existsSync(path.join(output, 'service-worker.js')) && serve({ pathname: '/service-worker.js', - cache_control: 'max-age=600' + cache_control: 'max-age=0' }), fs.existsSync(path.join(output, 'service-worker.js.map')) && serve({ pathname: '/service-worker.js.map', - cache_control: 'max-age=600' + cache_control: 'max-age=0' }), serve({ From 0a7be736c0e27717782c6897ee5608b0d7fc2316 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 7 Sep 2018 19:45:40 -0400 Subject: [PATCH 083/178] snake_case --- src/middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index 489d85a..7b0103b 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -525,11 +525,11 @@ function get_page_handler( } // users can set a CSP nonce using res.locals.nonce - const nonceAttr = (res.locals && res.locals.nonce) ? ` nonce="${res.locals.nonce}"` : ''; + const nonce_attr = (res.locals && res.locals.nonce) ? ` nonce="${res.locals.nonce}"` : ''; const body = template() .replace('%sapper.base%', () => ``) - .replace('%sapper.scripts%', () => `${script}`) + .replace('%sapper.scripts%', () => `${script}`) .replace('%sapper.html%', () => html) .replace('%sapper.head%', () => `${head}`) .replace('%sapper.styles%', () => styles); From 2d1f5353143cd30e88e079a8e10b97b71bf4e4a0 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 7 Sep 2018 16:46:40 -0700 Subject: [PATCH 084/178] use consistent cache-control:max-age=600 for HTML pages --- src/middleware.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/middleware.ts b/src/middleware.ts index bab6536..d86a77a 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -136,7 +136,7 @@ export default function middleware(opts: { fs.existsSync(path.join(output, 'index.html')) && serve({ pathname: '/index.html', - cache_control: 'max-age=600' + cache_control: dev() ? 'no-cache' : 'max-age=600' }), fs.existsSync(path.join(output, 'service-worker.js')) && serve({ @@ -532,6 +532,7 @@ function get_page_handler( .replace('%sapper.styles%', () => styles); res.statusCode = status; + res.setHeader('Cache-Control', dev() ? 'no-cache' : 'max-age=600'); res.end(body); }).catch(err => { if (error) { From 87ff9c2aebe26063fe873ff182fccad5b566f8d5 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 7 Sep 2018 17:25:17 -0700 Subject: [PATCH 085/178] fix test --- src/middleware.ts | 2 +- test/common/test.js | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index d86a77a..eef75ca 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -312,6 +312,7 @@ function get_page_handler( } = get_build_info(); res.setHeader('Content-Type', 'text/html'); + res.setHeader('Cache-Control', dev() ? 'no-cache' : 'max-age=600'); // preload main.js and current route // TODO detect other stuff we can preload? images, CSS, fonts? @@ -532,7 +533,6 @@ function get_page_handler( .replace('%sapper.styles%', () => styles); res.statusCode = status; - res.setHeader('Cache-Control', dev() ? 'no-cache' : 'max-age=600'); res.end(body); }).catch(err => { if (error) { diff --git a/test/common/test.js b/test/common/test.js index f149911..065e22e 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -5,6 +5,7 @@ const Nightmare = require('nightmare'); const walkSync = require('walk-sync'); const rimraf = require('rimraf'); const ports = require('port-authority'); +const fetch = require('node-fetch'); Nightmare.action('page', { title(done) { @@ -800,15 +801,20 @@ function run({ mode, basepath = '' }) { }); describe('headers', () => { - it('sets Content-Type and Link...preload headers', () => { - return capture(() => nightmare.goto(base)).then(requests => { - const { headers } = requests[0]; + it('sets Content-Type, Link...preload, and Cache-Control headers', () => { + return capture(() => fetch(base)).then(responses => { + const { headers } = responses[0]; assert.equal( headers['content-type'], 'text/html' ); + assert.equal( + headers['cache-control'], + 'max-age=600' + ); + const str = ['main', '.+?\\.\\d+'] .map(file => { return `<${basepath}/client/[^/]+/${file}\\.js>;rel="preload";as="script"`; From cbb5e8755ba8955061b289afdca9c278ee4be694 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 8 Sep 2018 10:12:55 -0400 Subject: [PATCH 086/178] Use MDN recommendation for preventing SW caching --- src/middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index 20102ac..755efb5 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -141,12 +141,12 @@ export default function middleware(opts: { fs.existsSync(path.join(output, 'service-worker.js')) && serve({ pathname: '/service-worker.js', - cache_control: 'max-age=0' + cache_control: 'no-cache, no-store, must-revalidate' }), fs.existsSync(path.join(output, 'service-worker.js.map')) && serve({ pathname: '/service-worker.js.map', - cache_control: 'max-age=0' + cache_control: 'no-cache, no-store, must-revalidate' }), serve({ From 8c07d9d2ac400a7bc03227626dad60fd99a92063 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 8 Sep 2018 10:24:21 -0400 Subject: [PATCH 087/178] -> v0.20.3 --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84b6897..d74c912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # sapper changelog +## 0.20.3 + +* Inject `nonce` attribute if `res.locals.nonce` is present ([#424](https://github.com/sveltejs/sapper/pull/424)) +* Prevent service worker caching ([#428](https://github.com/sveltejs/sapper/pull/428)) +* Consistent caching for HTML responses ([#429](https://github.com/sveltejs/sapper/pull/429)) + ## 0.20.2 * Add `immutable` cache control header for hashed assets ([#425](https://github.com/sveltejs/sapper/pull/425)) diff --git a/package.json b/package.json index 10059af..7748922 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.20.2", + "version": "0.20.3", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From 520949c5e140da423cba30363834caba81d42a14 Mon Sep 17 00:00:00 2001 From: Nikolai Sivertsen Date: Wed, 12 Sep 2018 16:55:45 +0200 Subject: [PATCH 088/178] Enable debugging in Chrome and VS Code - fixes 435 --- src/api/dev.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/api/dev.ts b/src/api/dev.ts index c996b06..b3f9e45 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -36,6 +36,8 @@ class Watcher extends EventEmitter { live: boolean; hot: boolean; + devtools_port: number; + dev_server: DevServer; proc: child_process.ChildProcess; filewatchers: Array<{ close: () => void }>; @@ -57,6 +59,7 @@ class Watcher extends EventEmitter { 'dev-port': dev_port, live, hot, + 'devtools-port': devtools_port, bundler, webpack = 'webpack', rollup = 'rollup', @@ -68,6 +71,7 @@ class Watcher extends EventEmitter { 'dev-port': number, live: boolean, hot: boolean, + 'devtools-port': number, bundler?: string, webpack: string, rollup: string, @@ -84,6 +88,8 @@ class Watcher extends EventEmitter { this.live = live; this.hot = hot; + this.devtools_port = devtools_port; + this.filewatchers = []; this.current_build = { @@ -129,6 +135,9 @@ class Watcher extends EventEmitter { if (!this.dev_port) this.dev_port = await ports.find(10000); + // Chrome looks for debugging targets on ports 9222 and 9229 by default + if (!this.devtools_port) this.devtools_port = await ports.find(9222); + let manifest_data: ManifestData; try { @@ -238,12 +247,21 @@ class Watcher extends EventEmitter { restart(); } + // we need to give the child process its own DevTools port, + // otherwise Node will try to use the parent's (and fail) + const debugArgRegex = /--inspect(?:-brk|-port)?|--debug-port/; + const execArgv = process.execArgv.slice(); + if (execArgv.some((arg: string) => !!arg.match(debugArgRegex))) { + execArgv.push(`--inspect-port=${this.devtools_port}`); + } + this.proc = child_process.fork(`${dest}/server.js`, [], { cwd: process.cwd(), env: Object.assign({ PORT: this.port }, process.env), - stdio: ['ipc'] + stdio: ['ipc'], + execArgv }); this.proc.stdout.on('data', chunk => { From 467041a3cd3183a3c06ccdae88f44cc8677e0ae6 Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Thu, 13 Sep 2018 19:45:30 +0100 Subject: [PATCH 089/178] Fix for legacy manifest file --- package.json | 1 + src/api/build.ts | 3 ++- src/core/create_compilers/extract_css.ts | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 7748922..c5be7ee 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "test": "mocha --opts mocha.opts", "pretest": "npm run build", "build": "rm -rf dist && rollup -c", + "prepare": "npm run build", "dev": "rollup -cw", "prepublishOnly": "npm test", "update_mime_types": "curl http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types | grep -e \"^[^#]\" > src/middleware/mime-types.md" diff --git a/src/api/build.ts b/src/api/build.ts index e6090b3..4315640 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -79,7 +79,8 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) { // TODO duration/warnings result: client_result }); - + + client_result.to_json(manifest_data, dirs); build_info.legacy_assets = client_result.assets; delete process.env.SAPPER_LEGACY_BUILD; } diff --git a/src/core/create_compilers/extract_css.ts b/src/core/create_compilers/extract_css.ts index 6791ab6..634da73 100644 --- a/src/core/create_compilers/extract_css.ts +++ b/src/core/create_compilers/extract_css.ts @@ -170,6 +170,7 @@ export default function extract_css(client_result: CompileResult, components: Pa } const main = client_result.assets.main; + if (process.env.SAPPER_LEGACY_BUILD) main = `legacy/${main}`; const entry = fs.readFileSync(`${dirs.dest}/client/${main}`, 'utf-8'); const replacements = new Map(); From e66e3cd7eb9289dfecab75444704774dbbf99c57 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Sep 2018 11:11:42 -0400 Subject: [PATCH 090/178] -> v0.20.4 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d74c912..a8ff04b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # sapper changelog +## 0.20.4 + +* Fix legacy build CSS ([#439](https://github.com/sveltejs/sapper/issues/439)) +* Enable debugging in Chrome and VSCode ([#435](https://github.com/sveltejs/sapper/issues/435)) + ## 0.20.3 * Inject `nonce` attribute if `res.locals.nonce` is present ([#424](https://github.com/sveltejs/sapper/pull/424)) diff --git a/package.json b/package.json index c5be7ee..9a8eb0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.20.3", + "version": "0.20.4", "description": "Military-grade apps, engineered by Svelte", "main": "dist/middleware.js", "bin": { From f29e7efbd6a3c3c94e6c2de2a0fb72cb5d5c428c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Sep 2018 11:25:04 -0400 Subject: [PATCH 091/178] const -> let --- src/core/create_compilers/extract_css.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/create_compilers/extract_css.ts b/src/core/create_compilers/extract_css.ts index 634da73..c5cf931 100644 --- a/src/core/create_compilers/extract_css.ts +++ b/src/core/create_compilers/extract_css.ts @@ -169,7 +169,7 @@ export default function extract_css(client_result: CompileResult, components: Pa return null; } - const main = client_result.assets.main; + let main = client_result.assets.main; if (process.env.SAPPER_LEGACY_BUILD) main = `legacy/${main}`; const entry = fs.readFileSync(`${dirs.dest}/client/${main}`, 'utf-8'); From 8f064fe5ac651e51814dc92c0af8e1d99afa9ce9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Sep 2018 12:02:11 -0400 Subject: [PATCH 092/178] update folder structure (#432) --- src/api/build.ts | 8 +- src/api/dev.ts | 12 +- src/api/export.ts | 3 +- src/cli/build.ts | 2 +- src/cli/export.ts | 1 + src/config.ts | 5 +- src/core/create_manifests.ts | 9 +- src/interfaces.ts | 2 +- src/middleware.ts | 2 +- src/rollup.ts | 6 +- src/webpack.ts | 6 +- test/app/{app => src}/client.js | 0 test/app/src/manifest/client.js | 299 ++++++++++++++++++ .../manifest/default-layout.html} | 0 test/app/src/manifest/server.js | 285 +++++++++++++++++ test/app/src/manifest/service-worker.js | 64 ++++ test/app/{ => src}/routes/[x]/[y]/[z].html | 0 .../app/{ => src}/routes/[x]/[y]/_layout.html | 0 test/app/{ => src}/routes/[x]/_counts.js | 0 test/app/{ => src}/routes/_error.html | 0 test/app/{ => src}/routes/_layout.html | 0 test/app/{ => src}/routes/about.html | 2 +- test/app/{ => src}/routes/api/delete/[id].js | 0 test/app/{ => src}/routes/blog/[slug].html | 0 test/app/{ => src}/routes/blog/[slug].json.js | 0 test/app/{ => src}/routes/blog/_posts.js | 0 test/app/{ => src}/routes/blog/index.html | 0 test/app/{ => src}/routes/blog/index.json.js | 0 test/app/{ => src}/routes/const.html | 0 .../{ => src}/routes/credentials/index.html | 0 .../{ => src}/routes/credentials/test.json.js | 0 test/app/{ => src}/routes/delete-test.html | 0 .../{ => src}/routes/echo/page/[slug].html | 0 .../routes/echo/server-route/[slug].js | 0 test/app/{ => src}/routes/fünke.html | 0 test/app/{ => src}/routes/fünke.json.js | 0 test/app/{ => src}/routes/index.html | 0 .../{ => src}/routes/missing-index/ok.html | 0 .../routes/non-sapper-redirect-to.html | 0 .../{ => src}/routes/preload-root/index.html | 0 .../routes/preload-values/custom-class.html | 0 test/app/src/routes/preload-values/index.html | 1 + .../{ => src}/routes/preload-values/set.html | 0 test/app/{ => src}/routes/redirect-from.html | 0 test/app/{ => src}/routes/redirect-root.html | 0 test/app/{ => src}/routes/redirect-to.html | 0 test/app/{ => src}/routes/slow-preload.html | 0 test/app/{ => src}/routes/store.html | 0 test/app/{ => src}/routes/throw-an-error.js | 0 .../{ => src}/routes/unsafe-replacement.html | 0 test/app/{app => src}/server.js | 0 test/app/{app => src}/service-worker.js | 0 test/app/{app => src}/template.html | 0 test/app/{assets => static}/favicon.png | Bin test/app/{assets => static}/global.css | 0 test/app/{assets => static}/great-success.png | Bin test/app/{assets => static}/manifest.json | 0 .../{assets => static}/svelte-logo-192.png | Bin .../{assets => static}/svelte-logo-512.png | Bin 59 files changed, 680 insertions(+), 27 deletions(-) rename test/app/{app => src}/client.js (100%) create mode 100644 test/app/src/manifest/client.js rename test/app/{routes/preload-values/index.html => src/manifest/default-layout.html} (100%) create mode 100644 test/app/src/manifest/server.js create mode 100644 test/app/src/manifest/service-worker.js rename test/app/{ => src}/routes/[x]/[y]/[z].html (100%) rename test/app/{ => src}/routes/[x]/[y]/_layout.html (100%) rename test/app/{ => src}/routes/[x]/_counts.js (100%) rename test/app/{ => src}/routes/_error.html (100%) rename test/app/{ => src}/routes/_layout.html (100%) rename test/app/{ => src}/routes/about.html (86%) rename test/app/{ => src}/routes/api/delete/[id].js (100%) rename test/app/{ => src}/routes/blog/[slug].html (100%) rename test/app/{ => src}/routes/blog/[slug].json.js (100%) rename test/app/{ => src}/routes/blog/_posts.js (100%) rename test/app/{ => src}/routes/blog/index.html (100%) rename test/app/{ => src}/routes/blog/index.json.js (100%) rename test/app/{ => src}/routes/const.html (100%) rename test/app/{ => src}/routes/credentials/index.html (100%) rename test/app/{ => src}/routes/credentials/test.json.js (100%) rename test/app/{ => src}/routes/delete-test.html (100%) rename test/app/{ => src}/routes/echo/page/[slug].html (100%) rename test/app/{ => src}/routes/echo/server-route/[slug].js (100%) rename test/app/{ => src}/routes/fünke.html (100%) rename test/app/{ => src}/routes/fünke.json.js (100%) rename test/app/{ => src}/routes/index.html (100%) rename test/app/{ => src}/routes/missing-index/ok.html (100%) rename test/app/{ => src}/routes/non-sapper-redirect-to.html (100%) rename test/app/{ => src}/routes/preload-root/index.html (100%) rename test/app/{ => src}/routes/preload-values/custom-class.html (100%) create mode 100644 test/app/src/routes/preload-values/index.html rename test/app/{ => src}/routes/preload-values/set.html (100%) rename test/app/{ => src}/routes/redirect-from.html (100%) rename test/app/{ => src}/routes/redirect-root.html (100%) rename test/app/{ => src}/routes/redirect-to.html (100%) rename test/app/{ => src}/routes/slow-preload.html (100%) rename test/app/{ => src}/routes/store.html (100%) rename test/app/{ => src}/routes/throw-an-error.js (100%) rename test/app/{ => src}/routes/unsafe-replacement.html (100%) rename test/app/{app => src}/server.js (100%) rename test/app/{app => src}/service-worker.js (100%) rename test/app/{app => src}/template.html (100%) rename test/app/{assets => static}/favicon.png (100%) rename test/app/{assets => static}/global.css (100%) rename test/app/{assets => static}/great-success.png (100%) rename test/app/{assets => static}/manifest.json (100%) rename test/app/{assets => static}/svelte-logo-192.png (100%) rename test/app/{assets => static}/svelte-logo-512.png (100%) diff --git a/src/api/build.ts b/src/api/build.ts index 4315640..18df442 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -39,9 +39,9 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) { mkdirp.sync(`${dirs.dest}/client`); copy_shimport(dirs.dest); - // minify app/template.html + // minify src/template.html // TODO compile this to a function? could be quicker than str.replace(...).replace(...).replace(...) - const template = fs.readFileSync(`${dirs.app}/template.html`, 'utf-8'); + const template = fs.readFileSync(`${dirs.src}/template.html`, 'utf-8'); // remove this in a future version if (template.indexOf('%sapper.base%') === -1) { @@ -54,7 +54,7 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) { const manifest_data = create_manifest_data(); - // create app/manifest/client.js and app/manifest/server.js + // create src/manifest/client.js and src/manifest/server.js create_main_manifests({ bundler: opts.bundler, manifest_data }); const { client, server, serviceworker } = create_compilers(opts.bundler, dirs); @@ -79,7 +79,7 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) { // TODO duration/warnings result: client_result }); - + client_result.to_json(manifest_data, dirs); build_info.legacy_assets = client_result.assets; delete process.env.SAPPER_LEGACY_BUILD; diff --git a/src/api/dev.ts b/src/api/dev.ts index b3f9e45..ff549dd 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -23,7 +23,7 @@ export function dev(opts) { class Watcher extends EventEmitter { bundler: string; dirs: { - app: string; + src: string; dest: string; routes: string; rollup: string; @@ -53,7 +53,7 @@ class Watcher extends EventEmitter { } constructor({ - app = locations.app(), + src = locations.src(), dest = locations.dest(), routes = locations.routes(), 'dev-port': dev_port, @@ -65,7 +65,7 @@ class Watcher extends EventEmitter { rollup = 'rollup', port = +process.env.PORT }: { - app: string, + src: string, dest: string, routes: string, 'dev-port': number, @@ -80,7 +80,7 @@ class Watcher extends EventEmitter { super(); this.bundler = validate_bundler(bundler); - this.dirs = { app, dest, routes, webpack, rollup }; + this.dirs = { src, dest, routes, webpack, rollup }; this.port = port; this.closed = false; @@ -100,7 +100,7 @@ class Watcher extends EventEmitter { }; // remove this in a future version - const template = fs.readFileSync(path.join(app, 'template.html'), 'utf-8'); + const template = fs.readFileSync(path.join(src, 'template.html'), 'utf-8'); if (template.indexOf('%sapper.base%') === -1) { const error = new Error(`As of Sapper v0.10, your template.html file must include %sapper.base% in the `); error.code = `missing-sapper-base`; @@ -175,7 +175,7 @@ class Watcher extends EventEmitter { } ), - fs.watch(`${locations.app()}/template.html`, () => { + fs.watch(`${locations.src()}/template.html`, () => { this.dev_server.send({ action: 'reload' }); diff --git a/src/api/export.ts b/src/api/export.ts index d4deaca..e136398 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -13,6 +13,7 @@ import * as events from './interfaces'; type Opts = { build: string, dest: string, + static: string, basepath?: string, timeout: number | false }; @@ -46,7 +47,7 @@ async function execute(emitter: EventEmitter, opts: Opts) { // Prep output directory sander.rimrafSync(export_dir); - sander.copydirSync('assets').to(export_dir); + sander.copydirSync(opts.static).to(export_dir); sander.copydirSync(opts.build, 'client').to(export_dir, 'client'); if (sander.existsSync(opts.build, 'service-worker.js')) { diff --git a/src/cli/build.ts b/src/cli/build.ts index 362bf99..5f47eb1 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -18,7 +18,7 @@ export function build(opts: { bundler?: string, legacy?: boolean }) { bundler }, { dest: locations.dest(), - app: locations.app(), + src: locations.src(), routes: locations.routes(), webpack: 'webpack', rollup: 'rollup' diff --git a/src/cli/export.ts b/src/cli/export.ts index 51a0246..214228e 100644 --- a/src/cli/export.ts +++ b/src/cli/export.ts @@ -15,6 +15,7 @@ export function exporter(export_dir: string, { try { const emitter = _exporter({ build: locations.dest(), + static: locations.static(), dest: export_dir, basepath, timeout diff --git a/src/config.ts b/src/config.ts index 15aef1b..37074a4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,7 +4,8 @@ export const dev = () => process.env.NODE_ENV !== 'production'; export const locations = { base: () => path.resolve(process.env.SAPPER_BASE || ''), - app: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_APP || 'app'), - routes: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_ROUTES || 'routes'), + src: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_SRC || 'src'), + static: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_STATIC || 'static'), + routes: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_ROUTES || 'src/routes'), dest: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_DEST || `.sapper/${dev() ? 'dev' : 'prod'}`) }; \ No newline at end of file diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index 3d260e6..212bd9b 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -10,7 +10,7 @@ export function create_main_manifests({ bundler, manifest_data, dev_port }: { manifest_data: ManifestData; dev_port?: number; }) { - const manifest_dir = path.join(locations.app(), 'manifest'); + const manifest_dir = path.join(locations.src(), 'manifest'); if (!fs.existsSync(manifest_dir)) fs.mkdirSync(manifest_dir); const path_to_routes = path.relative(manifest_dir, locations.routes()); @@ -30,20 +30,21 @@ export function create_serviceworker_manifest({ manifest_data, client_files }: { manifest_data: ManifestData; client_files: string[]; }) { - const assets = glob('**', { cwd: 'assets', filesOnly: true }); + const files = glob('**', { cwd: locations.static(), filesOnly: true }); let code = ` // This file is generated by Sapper — do not edit it! export const timestamp = ${Date.now()}; - export const assets = [\n\t${assets.map((x: string) => `"${x}"`).join(',\n\t')}\n]; + export const files = [\n\t${files.map((x: string) => `"${x}"`).join(',\n\t')}\n]; + export { files as assets }; // legacy export const shell = [\n\t${client_files.map((x: string) => `"${x}"`).join(',\n\t')}\n]; export const routes = [\n\t${manifest_data.pages.map((r: Page) => `{ pattern: ${r.pattern} }`).join(',\n\t')}\n]; `.replace(/^\t\t/gm, '').trim(); - write_if_changed(`${locations.app()}/manifest/service-worker.js`, code); + write_if_changed(`${locations.src()}/manifest/service-worker.js`, code); } function generate_client( diff --git a/src/interfaces.ts b/src/interfaces.ts index 313da87..a123903 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -43,7 +43,7 @@ export type ServerRoute = { export type Dirs = { dest: string, - app: string, + src: string, routes: string, webpack: string, rollup: string diff --git a/src/middleware.ts b/src/middleware.ts index 705a04d..c8a5ef5 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -288,7 +288,7 @@ function get_page_handler( : (assets => () => assets)(JSON.parse(fs.readFileSync(path.join(output, 'build.json'), 'utf-8'))); const template = dev() - ? () => fs.readFileSync(`${locations.app()}/template.html`, 'utf-8') + ? () => fs.readFileSync(`${locations.src()}/template.html`, 'utf-8') : (str => () => str)(fs.readFileSync(`${locations.dest()}/template.html`, 'utf-8')); const { server_routes, pages } = manifest; diff --git a/src/rollup.ts b/src/rollup.ts index 1240e21..3910f93 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -5,7 +5,7 @@ export default { client: { input: () => { - return `${locations.app()}/client.js` + return `${locations.src()}/client.js` }, output: () => { @@ -24,7 +24,7 @@ export default { server: { input: () => { - return `${locations.app()}/server.js` + return `${locations.src()}/server.js` }, output: () => { @@ -38,7 +38,7 @@ export default { serviceworker: { input: () => { - return `${locations.app()}/service-worker.js`; + return `${locations.src()}/service-worker.js`; }, output: () => { diff --git a/src/webpack.ts b/src/webpack.ts index 28fefec..6fc0ccb 100644 --- a/src/webpack.ts +++ b/src/webpack.ts @@ -6,7 +6,7 @@ export default { client: { entry: () => { return { - main: `${locations.app()}/client` + main: `${locations.src()}/client` }; }, @@ -23,7 +23,7 @@ export default { server: { entry: () => { return { - server: `${locations.app()}/server` + server: `${locations.src()}/server` }; }, @@ -40,7 +40,7 @@ export default { serviceworker: { entry: () => { return { - 'service-worker': `${locations.app()}/service-worker` + 'service-worker': `${locations.src()}/service-worker` }; }, diff --git a/test/app/app/client.js b/test/app/src/client.js similarity index 100% rename from test/app/app/client.js rename to test/app/src/client.js diff --git a/test/app/src/manifest/client.js b/test/app/src/manifest/client.js new file mode 100644 index 0000000..e2db262 --- /dev/null +++ b/test/app/src/manifest/client.js @@ -0,0 +1,299 @@ +// This file is generated by Sapper — do not edit it! +import root from '../routes/_layout.html'; +import error from '../routes/_error.html'; + +const d = decodeURIComponent; + +const index = { + js: () => import(/* webpackChunkName: "index" */ '../routes/index.html'), + css: "__SAPPER_CSS_PLACEHOLDER:index.html__" +}; +const non$45sapper$45redirect$45to = { + js: () => import(/* webpackChunkName: "non$45sapper$45redirect$45to" */ '../routes/non-sapper-redirect-to.html'), + css: "__SAPPER_CSS_PLACEHOLDER:non-sapper-redirect-to.html__" +}; +const unsafe$45replacement = { + js: () => import(/* webpackChunkName: "unsafe$45replacement" */ '../routes/unsafe-replacement.html'), + css: "__SAPPER_CSS_PLACEHOLDER:unsafe-replacement.html__" +}; +const preload$45values = { + js: () => import(/* webpackChunkName: "preload$45values" */ '../routes/preload-values/index.html'), + css: "__SAPPER_CSS_PLACEHOLDER:preload-values/index.html__" +}; +const preload$45values_custom$45class = { + js: () => import(/* webpackChunkName: "preload$45values_custom$45class" */ '../routes/preload-values/custom-class.html'), + css: "__SAPPER_CSS_PLACEHOLDER:preload-values/custom-class.html__" +}; +const preload$45values_set = { + js: () => import(/* webpackChunkName: "preload$45values_set" */ '../routes/preload-values/set.html'), + css: "__SAPPER_CSS_PLACEHOLDER:preload-values/set.html__" +}; +const missing$45index_ok = { + js: () => import(/* webpackChunkName: "missing$45index_ok" */ '../routes/missing-index/ok.html'), + css: "__SAPPER_CSS_PLACEHOLDER:missing-index/ok.html__" +}; +const redirect$45from = { + js: () => import(/* webpackChunkName: "redirect$45from" */ '../routes/redirect-from.html'), + css: "__SAPPER_CSS_PLACEHOLDER:redirect-from.html__" +}; +const redirect$45root = { + js: () => import(/* webpackChunkName: "redirect$45root" */ '../routes/redirect-root.html'), + css: "__SAPPER_CSS_PLACEHOLDER:redirect-root.html__" +}; +const preload$45root = { + js: () => import(/* webpackChunkName: "preload$45root" */ '../routes/preload-root/index.html'), + css: "__SAPPER_CSS_PLACEHOLDER:preload-root/index.html__" +}; +const slow$45preload = { + js: () => import(/* webpackChunkName: "slow$45preload" */ '../routes/slow-preload.html'), + css: "__SAPPER_CSS_PLACEHOLDER:slow-preload.html__" +}; +const credentials = { + js: () => import(/* webpackChunkName: "credentials" */ '../routes/credentials/index.html'), + css: "__SAPPER_CSS_PLACEHOLDER:credentials/index.html__" +}; +const delete$45test = { + js: () => import(/* webpackChunkName: "delete$45test" */ '../routes/delete-test.html'), + css: "__SAPPER_CSS_PLACEHOLDER:delete-test.html__" +}; +const redirect$45to = { + js: () => import(/* webpackChunkName: "redirect$45to" */ '../routes/redirect-to.html'), + css: "__SAPPER_CSS_PLACEHOLDER:redirect-to.html__" +}; +const about = { + js: () => import(/* webpackChunkName: "about" */ '../routes/about.html'), + css: "__SAPPER_CSS_PLACEHOLDER:about.html__" +}; +const const_ = { + js: () => import(/* webpackChunkName: "const_" */ '../routes/const.html'), + css: "__SAPPER_CSS_PLACEHOLDER:const.html__" +}; +const f$252nke = { + js: () => import(/* webpackChunkName: "f$252nke" */ '../routes/fünke.html'), + css: "__SAPPER_CSS_PLACEHOLDER:fünke.html__" +}; +const store = { + js: () => import(/* webpackChunkName: "store" */ '../routes/store.html'), + css: "__SAPPER_CSS_PLACEHOLDER:store.html__" +}; +const blog = { + js: () => import(/* webpackChunkName: "blog" */ '../routes/blog/index.html'), + css: "__SAPPER_CSS_PLACEHOLDER:blog/index.html__" +}; +const blog_$slug = { + js: () => import(/* webpackChunkName: "blog_$slug" */ '../routes/blog/[slug].html'), + css: "__SAPPER_CSS_PLACEHOLDER:blog/[slug].html__" +}; +const echo_page_$slug = { + js: () => import(/* webpackChunkName: "echo_page_$slug" */ '../routes/echo/page/[slug].html'), + css: "__SAPPER_CSS_PLACEHOLDER:echo/page/[slug].html__" +}; +const $x$93_$91y__layout = { + js: () => import(/* webpackChunkName: "$x$93_$91y__layout" */ '../routes/[x]/[y]/_layout.html'), + css: "__SAPPER_CSS_PLACEHOLDER:[x]/[y]/_layout.html__" +}; +const $x$93_$91y$93_$91z = { + js: () => import(/* webpackChunkName: "$x$93_$91y$93_$91z" */ '../routes/[x]/[y]/[z].html'), + css: "__SAPPER_CSS_PLACEHOLDER:[x]/[y]/[z].html__" +}; + +export const manifest = { + ignore: [/^\/throw-an-error$/, /^\/credentials\/test.json$/, /^\/f%C3%BCnke.json$/, /^\/blog.json$/, /^\/blog\/([^\/]+?).json$/, /^\/echo\/server-route\/([^\/]+?)$/, /^\/api\/delete\/([^\/]+?)$/], + + pages: [ + { + // index.html + pattern: /^\/?$/, + parts: [ + { component: index } + ] + }, + + { + // non-sapper-redirect-to.html + pattern: /^\/non-sapper-redirect-to\/?$/, + parts: [ + { component: non$45sapper$45redirect$45to } + ] + }, + + { + // unsafe-replacement.html + pattern: /^\/unsafe-replacement\/?$/, + parts: [ + { component: unsafe$45replacement } + ] + }, + + { + // preload-values/index.html + pattern: /^\/preload-values\/?$/, + parts: [ + null, + { component: preload$45values } + ] + }, + + { + // preload-values/custom-class.html + pattern: /^\/preload-values\/custom-class\/?$/, + parts: [ + null, + { component: preload$45values_custom$45class } + ] + }, + + { + // preload-values/set.html + pattern: /^\/preload-values\/set\/?$/, + parts: [ + null, + { component: preload$45values_set } + ] + }, + + { + // missing-index/ok.html + pattern: /^\/missing-index\/ok\/?$/, + parts: [ + null, + { component: missing$45index_ok } + ] + }, + + { + // redirect-from.html + pattern: /^\/redirect-from\/?$/, + parts: [ + { component: redirect$45from } + ] + }, + + { + // redirect-root.html + pattern: /^\/redirect-root\/?$/, + parts: [ + { component: redirect$45root } + ] + }, + + { + // preload-root/index.html + pattern: /^\/preload-root\/?$/, + parts: [ + null, + { component: preload$45root } + ] + }, + + { + // slow-preload.html + pattern: /^\/slow-preload\/?$/, + parts: [ + { component: slow$45preload } + ] + }, + + { + // credentials/index.html + pattern: /^\/credentials\/?$/, + parts: [ + null, + { component: credentials } + ] + }, + + { + // delete-test.html + pattern: /^\/delete-test\/?$/, + parts: [ + { component: delete$45test } + ] + }, + + { + // redirect-to.html + pattern: /^\/redirect-to\/?$/, + parts: [ + { component: redirect$45to } + ] + }, + + { + // about.html + pattern: /^\/about\/?$/, + parts: [ + { component: about } + ] + }, + + { + // const.html + pattern: /^\/const\/?$/, + parts: [ + { component: const_ } + ] + }, + + { + // fünke.html + pattern: /^\/f%C3%BCnke\/?$/, + parts: [ + { component: f$252nke } + ] + }, + + { + // store.html + pattern: /^\/store\/?$/, + parts: [ + { component: store } + ] + }, + + { + // blog/index.html + pattern: /^\/blog\/?$/, + parts: [ + null, + { component: blog } + ] + }, + + { + // blog/[slug].html + pattern: /^\/blog\/([^\/]+?)\/?$/, + parts: [ + null, + { component: blog_$slug, params: match => ({ slug: d(match[1]) }) } + ] + }, + + { + // echo/page/[slug].html + pattern: /^\/echo\/page\/([^\/]+?)\/?$/, + parts: [ + null, + null, + { component: echo_page_$slug, params: match => ({ slug: d(match[1]) }) } + ] + }, + + { + // [x]/[y]/[z].html + pattern: /^\/([^\/]+?)\/([^\/]+?)\/([^\/]+?)\/?$/, + parts: [ + null, + { component: $x$93_$91y__layout, params: match => ({ x: d(match[1]), y: d(match[2]) }) }, + { component: $x$93_$91y$93_$91z, params: match => ({ x: d(match[1]), y: d(match[2]), z: d(match[3]) }) } + ] + } + ], + + root, + + error +}; + +// this is included for legacy reasons +export const routes = {}; \ No newline at end of file diff --git a/test/app/routes/preload-values/index.html b/test/app/src/manifest/default-layout.html similarity index 100% rename from test/app/routes/preload-values/index.html rename to test/app/src/manifest/default-layout.html diff --git a/test/app/src/manifest/server.js b/test/app/src/manifest/server.js new file mode 100644 index 0000000..8c5e3fa --- /dev/null +++ b/test/app/src/manifest/server.js @@ -0,0 +1,285 @@ +// This file is generated by Sapper — do not edit it! +import * as route_throw$45an$45error from '../routes/throw-an-error.js'; +import * as route_credentials_test_json from '../routes/credentials/test.json.js'; +import * as route_f$252nke_json from '../routes/fünke.json.js'; +import * as route_blog_json from '../routes/blog/index.json.js'; +import * as route_blog_$slug_json from '../routes/blog/[slug].json.js'; +import * as route_echo_server$45route_$slug from '../routes/echo/server-route/[slug].js'; +import * as route_api_delete_$id from '../routes/api/delete/[id].js'; +import index from '../routes/index.html'; +import non$45sapper$45redirect$45to from '../routes/non-sapper-redirect-to.html'; +import unsafe$45replacement from '../routes/unsafe-replacement.html'; +import preload$45values from '../routes/preload-values/index.html'; +import preload$45values_custom$45class from '../routes/preload-values/custom-class.html'; +import preload$45values_set from '../routes/preload-values/set.html'; +import missing$45index_ok from '../routes/missing-index/ok.html'; +import redirect$45from from '../routes/redirect-from.html'; +import redirect$45root from '../routes/redirect-root.html'; +import preload$45root from '../routes/preload-root/index.html'; +import slow$45preload from '../routes/slow-preload.html'; +import credentials from '../routes/credentials/index.html'; +import delete$45test from '../routes/delete-test.html'; +import redirect$45to from '../routes/redirect-to.html'; +import about from '../routes/about.html'; +import const_ from '../routes/const.html'; +import f$252nke from '../routes/fünke.html'; +import store from '../routes/store.html'; +import blog from '../routes/blog/index.html'; +import blog_$slug from '../routes/blog/[slug].html'; +import echo_page_$slug from '../routes/echo/page/[slug].html'; +import $x$93_$91y__layout from '../routes/[x]/[y]/_layout.html'; +import $x$93_$91y$93_$91z from '../routes/[x]/[y]/[z].html'; +import root from '../routes/_layout.html'; +import error from '../routes/_error.html'; + +const d = decodeURIComponent; + +export const manifest = { + server_routes: [ + { + // throw-an-error.js + pattern: /^\/throw-an-error$/, + handlers: route_throw$45an$45error, + params: () => ({}) + }, + + { + // credentials/test.json.js + pattern: /^\/credentials\/test.json$/, + handlers: route_credentials_test_json, + params: () => ({}) + }, + + { + // fünke.json.js + pattern: /^\/f%C3%BCnke.json$/, + handlers: route_f$252nke_json, + params: () => ({}) + }, + + { + // blog/index.json.js + pattern: /^\/blog.json$/, + handlers: route_blog_json, + params: () => ({}) + }, + + { + // blog/[slug].json.js + pattern: /^\/blog\/([^\/]+?).json$/, + handlers: route_blog_$slug_json, + params: match => ({ slug: d(match[1]) }) + }, + + { + // echo/server-route/[slug].js + pattern: /^\/echo\/server-route\/([^\/]+?)$/, + handlers: route_echo_server$45route_$slug, + params: match => ({ slug: d(match[1]) }) + }, + + { + // api/delete/[id].js + pattern: /^\/api\/delete\/([^\/]+?)$/, + handlers: route_api_delete_$id, + params: match => ({ id: d(match[1]) }) + } + ], + + pages: [ + { + // index.html + pattern: /^\/?$/, + parts: [ + { name: "index", file: "index.html", component: index } + ] + }, + + { + // non-sapper-redirect-to.html + pattern: /^\/non-sapper-redirect-to\/?$/, + parts: [ + { name: "non$45sapper$45redirect$45to", file: "non-sapper-redirect-to.html", component: non$45sapper$45redirect$45to } + ] + }, + + { + // unsafe-replacement.html + pattern: /^\/unsafe-replacement\/?$/, + parts: [ + { name: "unsafe$45replacement", file: "unsafe-replacement.html", component: unsafe$45replacement } + ] + }, + + { + // preload-values/index.html + pattern: /^\/preload-values\/?$/, + parts: [ + null, + { name: "preload$45values", file: "preload-values/index.html", component: preload$45values } + ] + }, + + { + // preload-values/custom-class.html + pattern: /^\/preload-values\/custom-class\/?$/, + parts: [ + null, + { name: "preload$45values_custom$45class", file: "preload-values/custom-class.html", component: preload$45values_custom$45class } + ] + }, + + { + // preload-values/set.html + pattern: /^\/preload-values\/set\/?$/, + parts: [ + null, + { name: "preload$45values_set", file: "preload-values/set.html", component: preload$45values_set } + ] + }, + + { + // missing-index/ok.html + pattern: /^\/missing-index\/ok\/?$/, + parts: [ + null, + { name: "missing$45index_ok", file: "missing-index/ok.html", component: missing$45index_ok } + ] + }, + + { + // redirect-from.html + pattern: /^\/redirect-from\/?$/, + parts: [ + { name: "redirect$45from", file: "redirect-from.html", component: redirect$45from } + ] + }, + + { + // redirect-root.html + pattern: /^\/redirect-root\/?$/, + parts: [ + { name: "redirect$45root", file: "redirect-root.html", component: redirect$45root } + ] + }, + + { + // preload-root/index.html + pattern: /^\/preload-root\/?$/, + parts: [ + null, + { name: "preload$45root", file: "preload-root/index.html", component: preload$45root } + ] + }, + + { + // slow-preload.html + pattern: /^\/slow-preload\/?$/, + parts: [ + { name: "slow$45preload", file: "slow-preload.html", component: slow$45preload } + ] + }, + + { + // credentials/index.html + pattern: /^\/credentials\/?$/, + parts: [ + null, + { name: "credentials", file: "credentials/index.html", component: credentials } + ] + }, + + { + // delete-test.html + pattern: /^\/delete-test\/?$/, + parts: [ + { name: "delete$45test", file: "delete-test.html", component: delete$45test } + ] + }, + + { + // redirect-to.html + pattern: /^\/redirect-to\/?$/, + parts: [ + { name: "redirect$45to", file: "redirect-to.html", component: redirect$45to } + ] + }, + + { + // about.html + pattern: /^\/about\/?$/, + parts: [ + { name: "about", file: "about.html", component: about } + ] + }, + + { + // const.html + pattern: /^\/const\/?$/, + parts: [ + { name: "const_", file: "const.html", component: const_ } + ] + }, + + { + // fünke.html + pattern: /^\/f%C3%BCnke\/?$/, + parts: [ + { name: "f$252nke", file: "fünke.html", component: f$252nke } + ] + }, + + { + // store.html + pattern: /^\/store\/?$/, + parts: [ + { name: "store", file: "store.html", component: store } + ] + }, + + { + // blog/index.html + pattern: /^\/blog\/?$/, + parts: [ + null, + { name: "blog", file: "blog/index.html", component: blog } + ] + }, + + { + // blog/[slug].html + pattern: /^\/blog\/([^\/]+?)\/?$/, + parts: [ + null, + { name: "blog_$slug", file: "blog/[slug].html", component: blog_$slug, params: match => ({ slug: d(match[1]) }) } + ] + }, + + { + // echo/page/[slug].html + pattern: /^\/echo\/page\/([^\/]+?)\/?$/, + parts: [ + null, + null, + { name: "echo_page_$slug", file: "echo/page/[slug].html", component: echo_page_$slug, params: match => ({ slug: d(match[1]) }) } + ] + }, + + { + // [x]/[y]/[z].html + pattern: /^\/([^\/]+?)\/([^\/]+?)\/([^\/]+?)\/?$/, + parts: [ + null, + { name: "$x$93_$91y__layout", file: "[x]/[y]/_layout.html", component: $x$93_$91y__layout, params: match => ({ x: d(match[1]), y: d(match[2]) }) }, + { name: "$x$93_$91y$93_$91z", file: "[x]/[y]/[z].html", component: $x$93_$91y$93_$91z, params: match => ({ x: d(match[1]), y: d(match[2]), z: d(match[3]) }) } + ] + } + ], + + root, + + error +}; + +// this is included for legacy reasons +export const routes = {}; \ No newline at end of file diff --git a/test/app/src/manifest/service-worker.js b/test/app/src/manifest/service-worker.js new file mode 100644 index 0000000..01084bf --- /dev/null +++ b/test/app/src/manifest/service-worker.js @@ -0,0 +1,64 @@ +// This file is generated by Sapper — do not edit it! +export const timestamp = 1537372892007; + +export const files = [ + "favicon.png", + "global.css", + "great-success.png", + "manifest.json", + "svelte-logo-192.png", + "svelte-logo-512.png" +]; +export { files as assets }; // legacy + +export const shell = [ + "client/1b495d0233ee9a023603/credentials.12.js", + "client/1b495d0233ee9a023603/main.js", + "client/1b495d0233ee9a023603/non$45sapper$45redirect$45to.2.js", + "client/1b495d0233ee9a023603/unsafe$45replacement.3.js", + "client/1b495d0233ee9a023603/preload$45values.4.js", + "client/1b495d0233ee9a023603/preload$45values_custom$45class.5.js", + "client/1b495d0233ee9a023603/preload$45values_set.6.js", + "client/1b495d0233ee9a023603/missing$45index_ok.7.js", + "client/1b495d0233ee9a023603/redirect$45from.8.js", + "client/1b495d0233ee9a023603/redirect$45root.9.js", + "client/1b495d0233ee9a023603/preload$45root.10.js", + "client/1b495d0233ee9a023603/slow$45preload.11.js", + "client/1b495d0233ee9a023603/index.1.js", + "client/1b495d0233ee9a023603/delete$45test.13.js", + "client/1b495d0233ee9a023603/redirect$45to.14.js", + "client/1b495d0233ee9a023603/about.15.js", + "client/1b495d0233ee9a023603/const_.16.js", + "client/1b495d0233ee9a023603/f$252nke.17.js", + "client/1b495d0233ee9a023603/store.18.js", + "client/1b495d0233ee9a023603/blog.19.js", + "client/1b495d0233ee9a023603/blog_$slug.20.js", + "client/1b495d0233ee9a023603/echo_page_$slug.21.js", + "client/1b495d0233ee9a023603/$x$93_$91y__layout.22.js", + "client/1b495d0233ee9a023603/$x$93_$91y$93_$91z.23.js" +]; + +export const routes = [ + { pattern: /^\/?$/ }, + { pattern: /^\/non-sapper-redirect-to\/?$/ }, + { pattern: /^\/unsafe-replacement\/?$/ }, + { pattern: /^\/preload-values\/?$/ }, + { pattern: /^\/preload-values\/custom-class\/?$/ }, + { pattern: /^\/preload-values\/set\/?$/ }, + { pattern: /^\/missing-index\/ok\/?$/ }, + { pattern: /^\/redirect-from\/?$/ }, + { pattern: /^\/redirect-root\/?$/ }, + { pattern: /^\/preload-root\/?$/ }, + { pattern: /^\/slow-preload\/?$/ }, + { pattern: /^\/credentials\/?$/ }, + { pattern: /^\/delete-test\/?$/ }, + { pattern: /^\/redirect-to\/?$/ }, + { pattern: /^\/about\/?$/ }, + { pattern: /^\/const\/?$/ }, + { pattern: /^\/f%C3%BCnke\/?$/ }, + { pattern: /^\/store\/?$/ }, + { pattern: /^\/blog\/?$/ }, + { pattern: /^\/blog\/([^\/]+?)\/?$/ }, + { pattern: /^\/echo\/page\/([^\/]+?)\/?$/ }, + { pattern: /^\/([^\/]+?)\/([^\/]+?)\/([^\/]+?)\/?$/ } +]; \ No newline at end of file diff --git a/test/app/routes/[x]/[y]/[z].html b/test/app/src/routes/[x]/[y]/[z].html similarity index 100% rename from test/app/routes/[x]/[y]/[z].html rename to test/app/src/routes/[x]/[y]/[z].html diff --git a/test/app/routes/[x]/[y]/_layout.html b/test/app/src/routes/[x]/[y]/_layout.html similarity index 100% rename from test/app/routes/[x]/[y]/_layout.html rename to test/app/src/routes/[x]/[y]/_layout.html diff --git a/test/app/routes/[x]/_counts.js b/test/app/src/routes/[x]/_counts.js similarity index 100% rename from test/app/routes/[x]/_counts.js rename to test/app/src/routes/[x]/_counts.js diff --git a/test/app/routes/_error.html b/test/app/src/routes/_error.html similarity index 100% rename from test/app/routes/_error.html rename to test/app/src/routes/_error.html diff --git a/test/app/routes/_layout.html b/test/app/src/routes/_layout.html similarity index 100% rename from test/app/routes/_layout.html rename to test/app/src/routes/_layout.html diff --git a/test/app/routes/about.html b/test/app/src/routes/about.html similarity index 86% rename from test/app/routes/about.html rename to test/app/src/routes/about.html index 65ebb57..949ad84 100644 --- a/test/app/routes/about.html +++ b/test/app/src/routes/about.html @@ -9,7 +9,7 @@ \ No newline at end of file diff --git a/test/app/src/routes/blog/[slug].html b/test/app/src/routes/blog/[slug].html deleted file mode 100644 index 7fdc8b4..0000000 --- a/test/app/src/routes/blog/[slug].html +++ /dev/null @@ -1,36 +0,0 @@ - - {post.title} - - -

{post.title}

- -
- {@html post.html} -
- - \ No newline at end of file diff --git a/test/app/src/routes/blog/[slug].json.js b/test/app/src/routes/blog/[slug].json.js deleted file mode 100644 index 61ebee8..0000000 --- a/test/app/src/routes/blog/[slug].json.js +++ /dev/null @@ -1,23 +0,0 @@ -import posts from './_posts.js'; - -const lookup = {}; -posts.forEach(post => { - lookup[post.slug] = JSON.stringify(post); -}); - -export function get(req, res, next) { - // the `slug` parameter is available because this file - // is called [slug].js - const { slug } = req.params; - - if (slug in lookup) { - res.writeHead(200, { - 'Content-Type': 'application/json', - 'Cache-Control': `no-cache` - }); - - res.end(lookup[slug]); - } else { - next(); - } -} \ No newline at end of file diff --git a/test/app/src/routes/blog/_posts.js b/test/app/src/routes/blog/_posts.js deleted file mode 100644 index a9a9717..0000000 --- a/test/app/src/routes/blog/_posts.js +++ /dev/null @@ -1,151 +0,0 @@ -// Ordinarily, you'd generate this data from markdown files in your -// repo, or fetch them from a database of some kind. But in order to -// avoid unnecessary dependencies in the starter template, and in the -// service of obviousness, we're just going to leave it here. - -// This file is called `_posts.js` rather than `posts.js`, because -// we don't want to create an `/api/blog/posts` route — the leading -// underscore tells Sapper not to do that. - -const posts = [ - { - title: 'What is Sapper?', - slug: 'what-is-sapper', - html: ` -

First, you have to know what Svelte is. Svelte is a UI framework with a bold new idea: rather than providing a library that you write code with (like React or Vue, for example), it's a compiler that turns your components into highly optimized vanilla JavaScript. If you haven't already read the introductory blog post, you should!

- -

Sapper is a Next.js-style framework (more on that here) built around Svelte. It makes it embarrassingly easy to create extremely high performance web apps. Out of the box, you get:

- -
    -
  • Code-splitting, dynamic imports and hot module replacement, powered by webpack
  • -
  • Server-side rendering (SSR) with client-side hydration
  • -
  • Service worker for offline support, and all the PWA bells and whistles
  • -
  • The nicest development experience you've ever had, or your money back
  • -
- -

It's implemented as Express middleware. Everything is set up and waiting for you to get started, but you keep complete control over the server, service worker, webpack config and everything else, so it's as flexible as you need it to be.

- ` - }, - - { - title: 'How to use Sapper', - slug: 'how-to-use-sapper', - html: ` -

Step one

-

Create a new project, using degit:

- -
npx degit sveltejs/sapper-template my-app
-			cd my-app
-			npm install # or yarn!
-			npm run dev
-			
- -

Step two

-

Go to localhost:3000. Open my-app in your editor. Edit the files in the routes directory or add new ones.

- -

Step three

-

...

- -

Step four

-

Resist overdone joke formats.

- ` - }, - - { - title: 'Why the name?', - slug: 'why-the-name', - html: ` -

In war, the soldiers who build bridges, repair roads, clear minefields and conduct demolitions — all under combat conditions — are known as sappers.

- -

For web developers, the stakes are generally lower than those for combat engineers. But we face our own hostile environment: underpowered devices, poor network connections, and the complexity inherent in front-end engineering. Sapper, which is short for Svelte app maker, is your courageous and dutiful ally.

- ` - }, - - { - title: 'How is Sapper different from Next.js?', - slug: 'how-is-sapper-different-from-next', - html: ` -

Next.js is a React framework from Zeit, and is the inspiration for Sapper. There are a few notable differences, however:

- -
    -
  • It's powered by Svelte instead of React, so it's faster and your apps are smaller
  • -
  • Instead of route masking, we encode route parameters in filenames. For example, the page you're looking at right now is routes/blog/[slug].html
  • -
  • As well as pages (Svelte components, which render on server or client), you can create server routes in your routes directory. These are just .js files that export functions corresponding to HTTP methods, and receive Express request and response objects as arguments. This makes it very easy to, for example, add a JSON API such as the one powering this very page
  • -
  • Links are just <a> elements, rather than framework-specific <Link> components. That means, for example, that this link right here, despite being inside a blob of HTML, works with the router as you'd expect.
  • -
- ` - }, - - { - title: 'How can I get involved?', - slug: 'how-can-i-get-involved', - html: ` -

We're so glad you asked! Come on over to the Svelte and Sapper repos, and join us in the Gitter chatroom. Everyone is welcome, especially you!

- ` - }, - - { - title: 'A very long post with deep links', - slug: 'a-very-long-post', - html: ` -

One

-

I'll have a vodka rocks. (Mom, it's breakfast time.) And a piece of toast. Let me out that Queen. Fried cheese… with club sauce.

-

Her lawyers are claiming the seal is worth $250,000. And that's not even including Buster's Swatch. This was a big get for God. What, so the guy we are meeting with can't even grow his own hair? COME ON! She's always got to wedge herself in the middle of us so that she can control everything. Yeah. Mom's awesome. It's, like, Hey, you want to go down to the whirlpool? Yeah, I don't have a husband. I call it Swing City. The CIA should've just Googled for his hideout, evidently. There are dozens of us! DOZENS! Yeah, like I'm going to take a whiz through this $5,000 suit. COME ON.

- -

Two

-

Tobias Fünke costume. Heart attack never stopped old big bear.

-

Nellie is blowing them all AWAY. I will be a bigger and hairier mole than the one on your inner left thigh! I'll sacrifice anything for my children.

-

Up yours, granny! You couldn't handle it! Hey, Dad. Look at you. You're a year older…and a year closer to death. Buster: Oh yeah, I guess that's kind of funny. Bob Loblaw Law Blog. The guy runs a prison, he can have any piece of ass he wants.

- -

clicking this link should reset scroll

-

clicking this link should not affect scroll

- -

Three

-

I prematurely shot my wad on what was supposed to be a dry run, so now I'm afraid I have something of a mess on my hands. Dead Dove DO NOT EAT. Never once touched my per diem. I'd go to Craft Service, get some raw veggies, bacon, Cup-A-Soup…baby, I got a stew goin'. You're losing blood, aren't you? Gob: Probably, my socks are wet. Sure, let the little fruit do it. HUZZAH! Although George Michael had only got to second base, he'd gone in head first, like Pete Rose. I will pack your sweet pink mouth with so much ice cream you'll be the envy of every Jerry and Jane on the block!

-

Gosh Mom… after all these years, God's not going to take a call from you. Come on, this is a Bluth family celebration. It's no place for children.

-

And I wouldn't just lie there, if that's what you're thinking. That's not what I WAS thinking. Who? i just dont want him to point out my cracker ass in front of ann. When a man needs to prove to a woman that he's actually… When a man loves a woman… Heyyyyyy Uncle Father Oscar. [Stabbing Gob] White power! Gob: I'm white! Let me take off my assistant's skirt and put on my Barbra-Streisand-in-The-Prince-of-Tides ass-masking therapist pantsuit. In the mid '90s, Tobias formed a folk music band with Lindsay and Maebe which he called Dr. Funke's 100 Percent Natural Good Time Family Band Solution. The group was underwritten by the Natural Food Life Company, a division of Chem-Grow, an Allen Crayne acqusition, which was part of the Squimm Group. Their motto was simple: We keep you alive.

- -

Four

-

If you didn't have adult onset diabetes, I wouldn't mind giving you a little sugar. Everybody dance NOW. And the soup of the day is bread. Great, now I'm gonna smell to high heaven like a tuna melt!

-

That's how Tony Wonder lost a nut. She calls it a Mayonegg. Go ahead, touch the Cornballer. There's a new daddy in town. A discipline daddy.

- ` - }, - - { - title: 'Another long post', - slug: 'another-long-post', - html: ` -

One

-

I'll have a vodka rocks. (Mom, it's breakfast time.) And a piece of toast. Let me out that Queen. Fried cheese… with club sauce.

-

Her lawyers are claiming the seal is worth $250,000. And that's not even including Buster's Swatch. This was a big get for God. What, so the guy we are meeting with can't even grow his own hair? COME ON! She's always got to wedge herself in the middle of us so that she can control everything. Yeah. Mom's awesome. It's, like, Hey, you want to go down to the whirlpool? Yeah, I don't have a husband. I call it Swing City. The CIA should've just Googled for his hideout, evidently. There are dozens of us! DOZENS! Yeah, like I'm going to take a whiz through this $5,000 suit. COME ON.

- -

Two

-

Tobias Fünke costume. Heart attack never stopped old big bear.

-

Nellie is blowing them all AWAY. I will be a bigger and hairier mole than the one on your inner left thigh! I'll sacrifice anything for my children.

-

Up yours, granny! You couldn't handle it! Hey, Dad. Look at you. You're a year older…and a year closer to death. Buster: Oh yeah, I guess that's kind of funny. Bob Loblaw Law Blog. The guy runs a prison, he can have any piece of ass he wants.

- -

Three

-

I prematurely shot my wad on what was supposed to be a dry run, so now I'm afraid I have something of a mess on my hands. Dead Dove DO NOT EAT. Never once touched my per diem. I'd go to Craft Service, get some raw veggies, bacon, Cup-A-Soup…baby, I got a stew goin'. You're losing blood, aren't you? Gob: Probably, my socks are wet. Sure, let the little fruit do it. HUZZAH! Although George Michael had only got to second base, he'd gone in head first, like Pete Rose. I will pack your sweet pink mouth with so much ice cream you'll be the envy of every Jerry and Jane on the block!

-

Gosh Mom… after all these years, God's not going to take a call from you. Come on, this is a Bluth family celebration. It's no place for children.

-

And I wouldn't just lie there, if that's what you're thinking. That's not what I WAS thinking. Who? i just dont want him to point out my cracker ass in front of ann. When a man needs to prove to a woman that he's actually… When a man loves a woman… Heyyyyyy Uncle Father Oscar. [Stabbing Gob] White power! Gob: I'm white! Let me take off my assistant's skirt and put on my Barbra-Streisand-in-The-Prince-of-Tides ass-masking therapist pantsuit. In the mid '90s, Tobias formed a folk music band with Lindsay and Maebe which he called Dr. Funke's 100 Percent Natural Good Time Family Band Solution. The group was underwritten by the Natural Food Life Company, a division of Chem-Grow, an Allen Crayne acqusition, which was part of the Squimm Group. Their motto was simple: We keep you alive.

- -

Four

-

If you didn't have adult onset diabetes, I wouldn't mind giving you a little sugar. Everybody dance NOW. And the soup of the day is bread. Great, now I'm gonna smell to high heaven like a tuna melt!

-

That's how Tony Wonder lost a nut. She calls it a Mayonegg. Go ahead, touch the Cornballer. There's a new daddy in town. A discipline daddy.

- ` - }, - - { - title: 'Encödïng test', - slug: 'encödïng-test', - html: ` -

It works

- ` - } -]; - -posts.forEach(post => { - post.html = post.html.replace(/^\t{3}/gm, ''); -}); - -export default posts; \ No newline at end of file diff --git a/test/app/src/routes/blog/index.html b/test/app/src/routes/blog/index.html deleted file mode 100644 index 2e9f428..0000000 --- a/test/app/src/routes/blog/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - Blog - - -

Recent posts

- - - - \ No newline at end of file diff --git a/test/app/src/routes/blog/index.json.js b/test/app/src/routes/blog/index.json.js deleted file mode 100644 index 9d73d78..0000000 --- a/test/app/src/routes/blog/index.json.js +++ /dev/null @@ -1,17 +0,0 @@ -import posts from './_posts.js'; - -const contents = JSON.stringify(posts.map(post => { - return { - title: post.title, - slug: post.slug - }; -})); - -export function get(req, res) { - res.writeHead(200, { - 'Content-Type': 'application/json', - 'Cache-Control': `max-age=${30 * 60 * 1e3}` // cache for 30 minutes - }); - - res.end(contents); -} \ No newline at end of file diff --git a/test/app/src/routes/index.html b/test/app/src/routes/index.html deleted file mode 100644 index 65c7e59..0000000 --- a/test/app/src/routes/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - Sapper project template - - -

Great success!

- -home -about -slow preload -redirect -redirect -redirect (root) -broken link -error link -credentials -blog -const -echo/page/encöded?message=hëllö+wörld -echo/page/empty?message - -
- - \ No newline at end of file diff --git a/test/app/src/routes/missing-index/ok.html b/test/app/src/routes/missing-index/ok.html deleted file mode 100644 index 7cc8f66..0000000 --- a/test/app/src/routes/missing-index/ok.html +++ /dev/null @@ -1 +0,0 @@ -

it works

\ No newline at end of file diff --git a/test/app/src/routes/non-sapper-redirect-to.html b/test/app/src/routes/non-sapper-redirect-to.html deleted file mode 100644 index eeb0dfc..0000000 --- a/test/app/src/routes/non-sapper-redirect-to.html +++ /dev/null @@ -1 +0,0 @@ -

redirected

\ No newline at end of file diff --git a/test/app/src/routes/throw-an-error.js b/test/app/src/routes/throw-an-error.js deleted file mode 100644 index 523f2bc..0000000 --- a/test/app/src/routes/throw-an-error.js +++ /dev/null @@ -1,3 +0,0 @@ -export function get() { - throw new Error('nope'); -} \ No newline at end of file diff --git a/test/app/src/server.js b/test/app/src/server.js deleted file mode 100644 index b75ceeb..0000000 --- a/test/app/src/server.js +++ /dev/null @@ -1,126 +0,0 @@ -import fs from 'fs'; -import { resolve } from 'url'; -import express from 'express'; -import serve from 'serve-static'; -import { Store } from 'svelte/store.js'; -import * as sapper from '../__sapper__/server.js'; - -let pending; -let ended; - -process.on('message', message => { - if (message.action === 'start') { - if (pending) { - throw new Error(`Already capturing`); - } - - pending = new Set(); - ended = false; - process.send({ type: 'ready' }); - } - - if (message.action === 'end') { - ended = true; - if (pending.size === 0) { - process.send({ type: 'done' }); - pending = null; - } - } -}); - -const app = express(); - -const { PORT = 3000, BASEPATH = '' } = process.env; -const base = `http://localhost:${PORT}${BASEPATH}/`; - -// this allows us to do e.g. `fetch('/api/blog')` on the server -const fetch = require('node-fetch'); -global.fetch = (url, opts) => { - return fetch(resolve(base, url), opts); -}; - -const middlewares = [ - serve('assets'), - - // set test cookie - (req, res, next) => { - res.setHeader('Set-Cookie', ['a=1; Path=/', 'b=2; Path=/']); - next(); - }, - - // emit messages so we can capture requests - (req, res, next) => { - if (!pending) return next(); - - pending.add(req.url); - - const { write, end } = res; - const chunks = []; - - res.write = function(chunk) { - chunks.push(new Buffer(chunk)); - write.apply(res, arguments); - }; - - res.end = function(chunk) { - if (chunk) chunks.push(new Buffer(chunk)); - end.apply(res, arguments); - - if (pending) pending.delete(req.url); - - process.send({ - method: req.method, - url: req.url, - status: res.statusCode, - headers: res._headers, - body: Buffer.concat(chunks).toString() - }); - - if (pending && pending.size === 0 && ended) { - process.send({ type: 'done' }); - } - }; - - next(); - }, - - // set up some values for the store - (req, res, next) => { - req.hello = 'hello'; - res.locals = { name: 'world' }; - next(); - }, - - sapper.middleware({ - store: (req, res) => { - return new Store({ - title: `${req.hello} ${res.locals.name}` - }); - }, - ignore: [ - /foobar/i, - '/buzz', - 'fizz', - x => x === '/hello' - ] - }), -]; - -app.get(`${BASEPATH}/non-sapper-redirect-from`, (req, res) => { - res.writeHead(301, { - Location: `${BASEPATH}/non-sapper-redirect-to` - }); - res.end(); -}); - -if (BASEPATH) { - app.use(BASEPATH, ...middlewares); -} else { - app.use(...middlewares); -} - -['foobar', 'buzz', 'fizzer', 'hello'].forEach(uri => { - app.get('/'+uri, (req, res) => res.end(uri)); -}); - -app.listen(PORT); diff --git a/test/app/src/template.html b/test/app/src/template.html deleted file mode 100644 index 0ea5e17..0000000 --- a/test/app/src/template.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - %sapper.base% - - - - - - - %sapper.styles% - - - %sapper.head% - - - -
%sapper.html%
- - - %sapper.scripts% - - \ No newline at end of file diff --git a/test/app/static/favicon.png b/test/app/static/favicon.png deleted file mode 100644 index f76753e93c0acc317d3c56ed4b2a6251a9ca5d9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1917 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℞l)kuUgdlWUl+YT#& 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

-1PVL#&`- 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 J-{^MkzW_VDgRcMp diff --git a/test/app/webpack.config.js b/test/app/webpack.config.js deleted file mode 100644 index 78d0c85..0000000 --- a/test/app/webpack.config.js +++ /dev/null @@ -1,84 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); -const config = require('../../config/webpack.js'); -const sapper_pkg = require('../../package.json'); - -const mode = process.env.NODE_ENV; -const isDev = mode === 'development'; - -module.exports = { - client: { - entry: config.client.entry(), - output: config.client.output(), - resolve: { - extensions: ['.js', '.html'] - }, - module: { - rules: [ - { - test: /\.html$/, - exclude: /node_modules/, - use: { - loader: 'svelte-loader', - options: { - hydratable: true, - cascade: false, - store: true - } - } - } - ] - }, - mode, - optimization: { - minimize: false - }, - plugins: [ - isDev && new webpack.HotModuleReplacementPlugin() - ].filter(Boolean), - devtool: isDev && 'inline-source-map' - }, - - server: { - entry: config.server.entry(), - output: config.server.output(), - target: 'node', - resolve: { - extensions: ['.js', '.html'] - }, - externals: [].concat( - Object.keys(sapper_pkg.dependencies), - Object.keys(sapper_pkg.devDependencies) - ), - module: { - rules: [ - { - test: /\.html$/, - exclude: /node_modules/, - use: { - loader: 'svelte-loader', - options: { - css: false, - cascade: false, - store: true, - generate: 'ssr' - } - } - } - ] - }, - mode, - optimization: { - minimize: false - }, - performance: { - hints: false // it doesn't matter if server.js is large - } - }, - - serviceworker: { - entry: config.serviceworker.entry(), - output: config.serviceworker.output(), - mode - } -}; \ No newline at end of file diff --git a/test/apps/AppRunner.ts b/test/apps/AppRunner.ts new file mode 100644 index 0000000..6a1a48c --- /dev/null +++ b/test/apps/AppRunner.ts @@ -0,0 +1,122 @@ +import * as path from 'path'; +import * as puppeteer from 'puppeteer'; +import * as ports from 'port-authority'; +import { fork, ChildProcess } from 'child_process'; + +declare const start: () => Promise; +declare const prefetchRoutes: () => Promise; +declare const prefetch: (href: string) => Promise; +declare const goto: (href: string) => Promise; + +export class AppRunner { + cwd: string; + entry: string; + port: number; + proc: ChildProcess; + messages: any[]; + + browser: puppeteer.Browser; + page: puppeteer.Page; + + constructor(cwd: string, entry: string) { + this.cwd = cwd; + this.entry = path.join(cwd, entry); + this.messages = []; + } + + async start() { + this.port = await ports.find(3000); + + this.proc = fork(this.entry, [], { + cwd: this.cwd, + env: { + PORT: String(this.port) + } + }); + + this.proc.on('message', message => { + if (!message.__sapper__) return; + this.messages.push(message); + }); + + this.browser = await puppeteer.launch({ args: ['--no-sandbox'] }); + + this.page = await this.browser.newPage(); + this.page.on('console', msg => { + const text = msg.text(); + + if (!text.startsWith('Failed to load resource')) { + console.log(text); + } + }); + + return { + page: this.page, + base: `http://localhost:${this.port}`, + + // helpers + start: () => this.page.evaluate(() => start()), + prefetchRoutes: () => this.page.evaluate(() => prefetchRoutes()), + prefetch: (href: string) => this.page.evaluate((href: string) => prefetch(href), href), + goto: (href: string) => this.page.evaluate((href: string) => goto(href), href) + }; + } + + capture(fn: () => any): Promise { + return new Promise((fulfil, reject) => { + const requests: string[] = []; + const pending: Set = new Set(); + let done = false; + + function handle_request(request: puppeteer.Request) { + const url = request.url(); + requests.push(url); + pending.add(url); + } + + function handle_requestfinished(request: puppeteer.Request) { + const url = request.url(); + pending.delete(url); + + if (done && pending.size === 0) { + cleanup(); + fulfil(requests); + } + } + + function handle_requestfailed(request: puppeteer.Request) { + cleanup(); + reject(new Error(`failed to fetch ${request.url()}`)) + } + + const cleanup = () => { + this.page.removeListener('request', handle_request); + this.page.removeListener('requestfinished', handle_requestfinished); + this.page.removeListener('requestfailed', handle_requestfailed); + }; + + this.page.on('request', handle_request); + this.page.on('requestfinished', handle_requestfinished); + this.page.on('requestfailed', handle_requestfailed); + + return Promise.resolve(fn()).then(() => { + if (pending.size === 0) { + cleanup(); + fulfil(requests); + } + + done = true; + }); + }); + } + + end() { + return Promise.all([ + this.browser.close(), + new Promise(fulfil => { + this.proc.once('exit', fulfil); + this.proc.kill(); + }) + ]); + } +} \ No newline at end of file diff --git a/test/apps/basics/__test__.ts b/test/apps/basics/__test__.ts new file mode 100644 index 0000000..5f35532 --- /dev/null +++ b/test/apps/basics/__test__.ts @@ -0,0 +1,270 @@ +import * as path from 'path'; +import * as assert from 'assert'; +import * as puppeteer from 'puppeteer'; +import * as http from 'http'; +import { build } from '../../../api'; +import { AppRunner } from '../AppRunner'; +import { wait } from '../../utils'; + +declare let deleted: { id: number }; +declare let el: any; + +describe('basics', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + let prefetchRoutes: () => Promise; + let prefetch: (href: string) => Promise; + let goto: (href: string) => Promise; + + // hooks + before(() => { + return new Promise((fulfil, reject) => { + // TODO this is brittle. Make it unnecessary + process.chdir(__dirname); + process.env.NODE_ENV = 'production'; + + // TODO this API isn't great. Rethink it + const emitter = build({ + bundler: 'rollup' + }, { + src: path.join(__dirname, 'src'), + routes: path.join(__dirname, 'src/routes'), + dest: path.join(__dirname, '__sapper__/build') + }); + + emitter.on('error', reject); + + emitter.on('done', async () => { + try { + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start, prefetchRoutes, prefetch, goto } = await runner.start()); + + fulfil(); + } catch (err) { + reject(err); + } + }); + }); + }); + + after(() => runner.end()); + + const title = () => page.$eval('h1', node => node.textContent); + + it('serves /', async () => { + await page.goto(base); + + assert.equal( + await title(), + 'Great success!' + ); + }); + + it('serves /?', async () => { + await page.goto(`${base}?`); + + assert.equal( + await title(), + 'Great success!' + ); + }); + + it('serves static route', async () => { + await page.goto(`${base}/a`); + + assert.equal( + await title(), + 'a' + ); + }); + + it('serves static route from dir/index.html file', async () => { + await page.goto(`${base}/b`); + + assert.equal( + await title(), + 'b' + ); + }); + + it('serves dynamic route', async () => { + await page.goto(`${base}/test-slug`); + + assert.equal( + await title(), + 'TEST-SLUG' + ); + }); + + it('navigates to a new page without reloading', async () => { + await page.goto(base); + await start(); + await prefetchRoutes(); + + const requests: string[] = await runner.capture(async () => { + await page.click('a[href="a"]'); + }); + + assert.deepEqual(requests, []); + + assert.equal( + await title(), + 'a' + ); + }); + + it('navigates programmatically', async () => { + await page.goto(`${base}/a`); + await start(); + + await goto('b'); + + assert.equal( + await title(), + 'b' + ); + }); + + it('prefetches programmatically', async () => { + await page.goto(`${base}/a`); + await start(); + + const requests = await runner.capture(() => prefetch('b')); + + assert.equal(requests.length, 2); + assert.equal(requests[1], `${base}/b.json`); + }); + + + // TODO equivalent test for a webpack app + it('sets Content-Type, Link...modulepreload, and Cache-Control headers', () => { + return new Promise((fulfil, reject) => { + const req = http.get(base, res => { + try { + const { headers } = res; + + assert.equal( + headers['content-type'], + 'text/html' + ); + + assert.equal( + headers['cache-control'], + 'max-age=600' + ); + + // TODO preload more than just the entry point + const regex = /<\/client\/client\.\w+\.js>;rel="modulepreload"/; + const link = headers['link']; + + assert.ok(regex.test(link), link); + + fulfil(); + } catch (err) { + reject(err); + } + }); + + req.on('error', reject); + }); + }); + + it('calls a delete handler', async () => { + await page.goto(`${base}/delete-test`); + await start(); + + await page.click('.del'); + await page.waitForFunction(() => deleted); + + assert.equal(await page.evaluate(() => deleted.id), 42); + }); + + it('hydrates initial route', async () => { + await page.goto(base); + + await page.evaluate(() => { + el = document.querySelector('.hydrate-test'); + }); + + await start(); + + assert.ok(await page.evaluate(() => { + return document.querySelector('.hydrate-test') === el; + })); + }); + + it('does not attempt client-side navigation to server routes', async () => { + await page.goto(base); + await start(); + await prefetchRoutes(); + + await page.click(`[href="ambiguous/ok.json"]`); + await wait(50); + + assert.equal( + await page.evaluate(() => document.body.textContent), + 'ok' + ); + }); + + it('allows reserved words as route names', async () => { + await page.goto(`${base}/const`); + await start(); + + assert.equal( + await title(), + 'reserved words are okay as routes' + ); + }); + + it('accepts value-less query string parameter on server', async () => { + await page.goto(`${base}/echo-query?message`); + + assert.equal( + await title(), + 'message: ""' + ); + }); + + it('accepts value-less query string parameter on client', async () => { + await page.goto(base); + await start(); + await prefetchRoutes(); + + await page.click('a[href="echo-query?message"]') + + assert.equal( + await title(), + 'message: ""' + ); + }); + + // skipped because Nightmare doesn't seem to focus the correctly + it('resets the active element after navigation', async () => { + await page.goto(base); + await start(); + await prefetchRoutes(); + + await page.click('[href="a"]'); + await wait(50); + + assert.equal( + await page.evaluate(() => document.activeElement.nodeName), + 'BODY' + ); + }); + + it('replaces %sapper.xxx% tags safely', async () => { + await page.goto(`${base}/unsafe-replacement`); + await start(); + + const html = await page.evaluate(() => document.body.innerHTML); + assert.equal(html.indexOf('%sapper'), -1); + }); +}); \ No newline at end of file diff --git a/test/apps/basics/rollup.config.js b/test/apps/basics/rollup.config.js new file mode 100644 index 0000000..943b676 --- /dev/null +++ b/test/apps/basics/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/basics/src/client.js b/test/apps/basics/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/basics/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/basics/src/routes/[slug].html b/test/apps/basics/src/routes/[slug].html new file mode 100644 index 0000000..ade66de --- /dev/null +++ b/test/apps/basics/src/routes/[slug].html @@ -0,0 +1 @@ +

{params.slug.toUpperCase()}

\ No newline at end of file diff --git a/test/apps/basics/src/routes/_error.html b/test/apps/basics/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/basics/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/basics/src/routes/a.html b/test/apps/basics/src/routes/a.html new file mode 100644 index 0000000..0e6757b --- /dev/null +++ b/test/apps/basics/src/routes/a.html @@ -0,0 +1 @@ +

a

\ No newline at end of file diff --git a/test/apps/basics/src/routes/ambiguous/[slug].html b/test/apps/basics/src/routes/ambiguous/[slug].html new file mode 100644 index 0000000..e69de29 diff --git a/test/apps/basics/src/routes/ambiguous/[slug].json.js b/test/apps/basics/src/routes/ambiguous/[slug].json.js new file mode 100644 index 0000000..adc3102 --- /dev/null +++ b/test/apps/basics/src/routes/ambiguous/[slug].json.js @@ -0,0 +1,3 @@ +export function get(req, res) { + res.end(req.params.slug); +} \ No newline at end of file diff --git a/test/apps/basics/src/routes/b/index.html b/test/apps/basics/src/routes/b/index.html new file mode 100644 index 0000000..d93079d --- /dev/null +++ b/test/apps/basics/src/routes/b/index.html @@ -0,0 +1,11 @@ +

{letter}

+ + \ No newline at end of file diff --git a/test/apps/basics/src/routes/b/index.json.js b/test/apps/basics/src/routes/b/index.json.js new file mode 100644 index 0000000..29b7ede --- /dev/null +++ b/test/apps/basics/src/routes/b/index.json.js @@ -0,0 +1,3 @@ +export function get(req, res) { + res.end(JSON.stringify('b')); +} \ No newline at end of file diff --git a/test/app/src/routes/const.html b/test/apps/basics/src/routes/const.html similarity index 100% rename from test/app/src/routes/const.html rename to test/apps/basics/src/routes/const.html diff --git a/test/app/src/routes/api/delete/[id].js b/test/apps/basics/src/routes/delete-test/[id].json.js similarity index 100% rename from test/app/src/routes/api/delete/[id].js rename to test/apps/basics/src/routes/delete-test/[id].json.js diff --git a/test/app/src/routes/delete-test.html b/test/apps/basics/src/routes/delete-test/index.html similarity index 67% rename from test/app/src/routes/delete-test.html rename to test/apps/basics/src/routes/delete-test/index.html index 428ab3c..a5280cb 100644 --- a/test/app/src/routes/delete-test.html +++ b/test/apps/basics/src/routes/delete-test/index.html @@ -2,9 +2,13 @@ \ No newline at end of file diff --git a/test/apps/basics/src/routes/index.html b/test/apps/basics/src/routes/index.html new file mode 100644 index 0000000..221c0f5 --- /dev/null +++ b/test/apps/basics/src/routes/index.html @@ -0,0 +1,7 @@ +

Great success!

+ +
a +ok +ok + +
\ No newline at end of file diff --git a/test/app/src/routes/unsafe-replacement.html b/test/apps/basics/src/routes/unsafe-replacement.html similarity index 100% rename from test/app/src/routes/unsafe-replacement.html rename to test/apps/basics/src/routes/unsafe-replacement.html diff --git a/test/apps/basics/src/server.js b/test/apps/basics/src/server.js new file mode 100644 index 0000000..0e7741c --- /dev/null +++ b/test/apps/basics/src/server.js @@ -0,0 +1,8 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use(sapper.middleware()) + .listen(PORT); diff --git a/test/app/src/service-worker.js b/test/apps/basics/src/service-worker.js similarity index 75% rename from test/app/src/service-worker.js rename to test/apps/basics/src/service-worker.js index b19f2d6..9d2ac9d 100644 --- a/test/app/src/service-worker.js +++ b/test/apps/basics/src/service-worker.js @@ -1,10 +1,10 @@ -import { files, shell, timestamp, routes } from '../__sapper__/service-worker.js'; +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; -const ASSETS = `cachetimestamp`; +const ASSETS = `cache${timestamp}`; // `shell` is an array of all the files generated by webpack, -// `assets` is an array of everything in the `assets` directory -const to_cache = shell.concat(files); +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); const cached = new Set(to_cache); self.addEventListener('install', event => { @@ -26,19 +26,24 @@ self.addEventListener('activate', event => { if (key !== ASSETS) await caches.delete(key); } - await self.clients.claim(); + self.clients.claim(); }) ); }); self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + const url = new URL(event.request.url); // don't try to handle e.g. data: URIs if (!url.protocol.startsWith('http')) return; + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + // always serve assets and webpack-generated files from cache - if (cached.has(url.pathname)) { + if (url.host === self.location.host && cached.has(url.pathname)) { event.respondWith(caches.match(event.request)); return; } @@ -53,12 +58,14 @@ self.addEventListener('fetch', event => { } */ + if (event.request.cache === 'only-if-cached') return; + // for everything else, try the network first, falling back to // cache if the user is offline. (If the pages never change, you // might prefer a cache-first approach to a network-first one.) event.respondWith( caches - .open('offline') + .open(`offline${timestamp}`) .then(async cache => { try { const response = await fetch(event.request); diff --git a/test/apps/basics/src/template.html b/test/apps/basics/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/basics/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/credentials/__test__.ts b/test/apps/credentials/__test__.ts new file mode 100644 index 0000000..0f755d2 --- /dev/null +++ b/test/apps/credentials/__test__.ts @@ -0,0 +1,83 @@ +import * as path from 'path'; +import * as assert from 'assert'; +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import { wait } from '../../utils'; +import { AppRunner } from '../AppRunner'; + +describe('credentials', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + let prefetchRoutes: () => Promise; + + // hooks + before(() => { + return new Promise((fulfil, reject) => { + // TODO this is brittle. Make it unnecessary + process.chdir(__dirname); + process.env.NODE_ENV = 'production'; + + // TODO this API isn't great. Rethink it + const emitter = build({ + bundler: 'rollup' + }, { + src: path.join(__dirname, 'src'), + routes: path.join(__dirname, 'src/routes'), + dest: path.join(__dirname, '__sapper__/build') + }); + + emitter.on('error', reject); + + emitter.on('done', async () => { + try { + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start, prefetchRoutes } = await runner.start()); + + fulfil(); + } catch (err) { + reject(err); + } + }); + }); + }); + + after(() => runner.end()); + + it('sends cookies when using this.fetch with credentials: "include"', async () => { + await page.goto(`${base}/credentials?creds=include`); + + assert.equal( + await page.$eval('h1', node => node.textContent), + 'a: 1, b: 2, max-age: undefined' + ); + }); + + it('does not send cookies when using this.fetch without credentials', async () => { + await page.goto(`${base}/credentials`); + + assert.equal( + await page.$eval('h1', node => node.textContent), + 'unauthorized' + ); + }); + + it('delegates to fetch on the client', async () => { + await page.goto(base) + await start(); + await prefetchRoutes(); + + await page.click('[href="credentials?creds=include"]'); + await wait(50); + + assert.equal( + await page.$eval('h1', node => node.textContent), + 'a: 1, b: 2, max-age: undefined' + ); + }); +}); \ No newline at end of file diff --git a/test/apps/credentials/rollup.config.js b/test/apps/credentials/rollup.config.js new file mode 100644 index 0000000..045746e --- /dev/null +++ b/test/apps/credentials/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka', 'cookie'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/credentials/src/client.js b/test/apps/credentials/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/credentials/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/credentials/src/routes/_error.html b/test/apps/credentials/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/credentials/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/app/src/routes/credentials/index.html b/test/apps/credentials/src/routes/credentials/index.html similarity index 100% rename from test/app/src/routes/credentials/index.html rename to test/apps/credentials/src/routes/credentials/index.html diff --git a/test/app/src/routes/credentials/test.json.js b/test/apps/credentials/src/routes/credentials/test.json.js similarity index 100% rename from test/app/src/routes/credentials/test.json.js rename to test/apps/credentials/src/routes/credentials/test.json.js diff --git a/test/apps/credentials/src/routes/index.html b/test/apps/credentials/src/routes/index.html new file mode 100644 index 0000000..fdd338f --- /dev/null +++ b/test/apps/credentials/src/routes/index.html @@ -0,0 +1,3 @@ +

Great success!

+ +link \ No newline at end of file diff --git a/test/apps/credentials/src/server.js b/test/apps/credentials/src/server.js new file mode 100644 index 0000000..2761f50 --- /dev/null +++ b/test/apps/credentials/src/server.js @@ -0,0 +1,13 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use((req, res, next) => { + // set test cookie + res.setHeader('Set-Cookie', ['a=1; Max-Age=3600', 'b=2; Max-Age=3600']); + next(); + }) + .use(sapper.middleware()) + .listen(PORT); diff --git a/test/apps/credentials/src/service-worker.js b/test/apps/credentials/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/credentials/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/credentials/src/template.html b/test/apps/credentials/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/credentials/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/encoding/__test__.ts b/test/apps/encoding/__test__.ts new file mode 100644 index 0000000..c63e029 --- /dev/null +++ b/test/apps/encoding/__test__.ts @@ -0,0 +1,92 @@ +import * as path from 'path'; +import * as assert from 'assert'; +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import { AppRunner } from '../AppRunner'; +import { wait } from '../../utils'; + +describe('encoding', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + let prefetchRoutes: () => Promise; + + // hooks + before(() => { + return new Promise((fulfil, reject) => { + // TODO this is brittle. Make it unnecessary + process.chdir(__dirname); + process.env.NODE_ENV = 'production'; + + // TODO this API isn't great. Rethink it + const emitter = build({ + bundler: 'rollup' + }, { + src: path.join(__dirname, 'src'), + routes: path.join(__dirname, 'src/routes'), + dest: path.join(__dirname, '__sapper__/build') + }); + + emitter.on('error', reject); + + emitter.on('done', async () => { + try { + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start, prefetchRoutes } = await runner.start()); + + fulfil(); + } catch (err) { + reject(err); + } + }); + }); + }); + + after(() => runner.end()); + + it('encodes routes', async () => { + await page.goto(`${base}/fünke`); + + assert.equal( + await page.$eval('h1', node => node.textContent), + `I'm afraid I just blue myself` + ); + }); + + it('encodes req.params and req.query for server-rendered pages', async () => { + await page.goto(`${base}/echo/page/encöded?message=hëllö+wörld`); + + assert.equal( + await page.$eval('h1', node => node.textContent), + 'encöded (hëllö wörld)' + ); + }); + + it('encodes req.params and req.query for client-rendered pages', async () => { + await page.goto(base); + await start(); + await prefetchRoutes(); + + await page.click('a[href="echo/page/encöded?message=hëllö+wörld"]'); + await wait(50); + + assert.equal( + await page.$eval('h1', node => node.textContent), + 'encöded (hëllö wörld)' + ); + }); + + it('encodes req.params for server routes', async () => { + await page.goto(`${base}/echo/server-route/encöded`); + + assert.equal( + await page.$eval('h1', node => node.textContent), + 'encöded' + ); + }); +}); \ No newline at end of file diff --git a/test/apps/encoding/rollup.config.js b/test/apps/encoding/rollup.config.js new file mode 100644 index 0000000..943b676 --- /dev/null +++ b/test/apps/encoding/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/encoding/src/client.js b/test/apps/encoding/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/encoding/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/encoding/src/routes/_error.html b/test/apps/encoding/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/encoding/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/app/src/routes/echo/page/[slug].html b/test/apps/encoding/src/routes/echo/page/[slug].html similarity index 100% rename from test/app/src/routes/echo/page/[slug].html rename to test/apps/encoding/src/routes/echo/page/[slug].html diff --git a/test/app/src/routes/echo/server-route/[slug].js b/test/apps/encoding/src/routes/echo/server-route/[slug].js similarity index 100% rename from test/app/src/routes/echo/server-route/[slug].js rename to test/apps/encoding/src/routes/echo/server-route/[slug].js diff --git a/test/app/src/routes/fünke.html b/test/apps/encoding/src/routes/fünke.html similarity index 100% rename from test/app/src/routes/fünke.html rename to test/apps/encoding/src/routes/fünke.html diff --git a/test/app/src/routes/fünke.json.js b/test/apps/encoding/src/routes/fünke.json.js similarity index 100% rename from test/app/src/routes/fünke.json.js rename to test/apps/encoding/src/routes/fünke.json.js diff --git a/test/apps/encoding/src/routes/index.html b/test/apps/encoding/src/routes/index.html new file mode 100644 index 0000000..bedde7e --- /dev/null +++ b/test/apps/encoding/src/routes/index.html @@ -0,0 +1,3 @@ +

Great success!

+ +link \ No newline at end of file diff --git a/test/apps/encoding/src/server.js b/test/apps/encoding/src/server.js new file mode 100644 index 0000000..0e7741c --- /dev/null +++ b/test/apps/encoding/src/server.js @@ -0,0 +1,8 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use(sapper.middleware()) + .listen(PORT); diff --git a/test/apps/encoding/src/service-worker.js b/test/apps/encoding/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/encoding/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/encoding/src/template.html b/test/apps/encoding/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/encoding/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/errors/__test__.ts b/test/apps/errors/__test__.ts new file mode 100644 index 0000000..f718b06 --- /dev/null +++ b/test/apps/errors/__test__.ts @@ -0,0 +1,137 @@ +import * as path from 'path'; +import * as assert from 'assert'; +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import { AppRunner } from '../AppRunner'; +import { wait } from '../../utils'; + +describe('errors', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + let prefetchRoutes: () => Promise; + + // hooks + before(() => { + return new Promise((fulfil, reject) => { + // TODO this is brittle. Make it unnecessary + process.chdir(__dirname); + process.env.NODE_ENV = 'production'; + + // TODO this API isn't great. Rethink it + const emitter = build({ + bundler: 'rollup' + }, { + src: path.join(__dirname, 'src'), + routes: path.join(__dirname, 'src/routes'), + dest: path.join(__dirname, '__sapper__/build') + }); + + emitter.on('error', reject); + + emitter.on('done', async () => { + try { + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start, prefetchRoutes } = await runner.start()); + + fulfil(); + } catch (err) { + reject(err); + } + }); + }); + }); + + after(() => runner.end()); + + it('handles missing route on server', async () => { + await page.goto(`${base}/nope`); + + assert.equal( + await page.$eval('h1', node => node.textContent), + '404' + ); + }); + + it('handles missing route on client', async () => { + await page.goto(base); + await start(); + + await page.click('[href="nope"]'); + await wait(50); + + assert.equal( + await page.$eval('h1', node => node.textContent), + '404' + ); + }); + + it('handles explicit 4xx on server', async () => { + await page.goto(`${base}/blog/nope`); + + assert.equal( + await page.$eval('h1', node => node.textContent), + '404' + ); + }); + + it('handles explicit 4xx on client', async () => { + await page.goto(base); + await start(); + await prefetchRoutes(); + + await page.click('[href="blog/nope"]'); + await wait(50); + + assert.equal( + await page.$eval('h1', node => node.textContent), + '404' + ); + }); + + it('handles error on server', async () => { + await page.goto(`${base}/throw`); + + assert.equal( + await page.$eval('h1', node => node.textContent), + '500' + ); + }); + + it('handles error on client', async () => { + await page.goto(base); + await start(); + await prefetchRoutes(); + + await page.click('[href="throw"]'); + await wait(50); + + assert.equal( + await page.$eval('h1', node => node.textContent), + '500' + ); + }); + + it('does not serve error page for explicit non-page errors', async () => { + await page.goto(`${base}/nope.json`); + + assert.equal( + await page.evaluate(() => document.body.textContent), + 'nope' + ); + }); + + it('does not serve error page for thrown non-page errors', async () => { + await page.goto(`${base}/throw.json`); + + assert.equal( + await page.evaluate(() => document.body.textContent), + 'oops' + ); + }); +}); \ No newline at end of file diff --git a/test/apps/errors/rollup.config.js b/test/apps/errors/rollup.config.js new file mode 100644 index 0000000..943b676 --- /dev/null +++ b/test/apps/errors/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/errors/src/client.js b/test/apps/errors/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/errors/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/errors/src/routes/_error.html b/test/apps/errors/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/errors/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/errors/src/routes/blog/[slug].html b/test/apps/errors/src/routes/blog/[slug].html new file mode 100644 index 0000000..8a304d0 --- /dev/null +++ b/test/apps/errors/src/routes/blog/[slug].html @@ -0,0 +1,19 @@ +

{post.title}

+ + \ No newline at end of file diff --git a/test/apps/errors/src/routes/blog/[slug].json.js b/test/apps/errors/src/routes/blog/[slug].json.js new file mode 100644 index 0000000..5e2e2a7 --- /dev/null +++ b/test/apps/errors/src/routes/blog/[slug].json.js @@ -0,0 +1,9 @@ +export function get(req, res) { + res.writeHead(404, { + 'Content-Type': 'application/json' + }); + + res.end(JSON.stringify({ + message: 'not found' + })); +} \ No newline at end of file diff --git a/test/apps/errors/src/routes/index.html b/test/apps/errors/src/routes/index.html new file mode 100644 index 0000000..a6b7896 --- /dev/null +++ b/test/apps/errors/src/routes/index.html @@ -0,0 +1,5 @@ +

root

+ +nope +blog/nope +throw \ No newline at end of file diff --git a/test/apps/errors/src/routes/nope.json.js b/test/apps/errors/src/routes/nope.json.js new file mode 100644 index 0000000..3813784 --- /dev/null +++ b/test/apps/errors/src/routes/nope.json.js @@ -0,0 +1,4 @@ +export function get(req, res) { + res.writeHead(500); + res.end('nope'); +} \ No newline at end of file diff --git a/test/apps/errors/src/routes/throw.html b/test/apps/errors/src/routes/throw.html new file mode 100644 index 0000000..16b4131 --- /dev/null +++ b/test/apps/errors/src/routes/throw.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/test/apps/errors/src/routes/throw.json.js b/test/apps/errors/src/routes/throw.json.js new file mode 100644 index 0000000..5247b57 --- /dev/null +++ b/test/apps/errors/src/routes/throw.json.js @@ -0,0 +1,3 @@ +export function get(req, res) { + throw new Error('oops'); +} \ No newline at end of file diff --git a/test/apps/errors/src/server.js b/test/apps/errors/src/server.js new file mode 100644 index 0000000..0e7741c --- /dev/null +++ b/test/apps/errors/src/server.js @@ -0,0 +1,8 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use(sapper.middleware()) + .listen(PORT); diff --git a/test/apps/errors/src/service-worker.js b/test/apps/errors/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/errors/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/errors/src/template.html b/test/apps/errors/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/errors/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/export/__test__.ts b/test/apps/export/__test__.ts new file mode 100644 index 0000000..9d422e3 --- /dev/null +++ b/test/apps/export/__test__.ts @@ -0,0 +1,70 @@ +import * as path from 'path'; +import * as assert from 'assert'; +import { walk } from '../../utils'; +import * as api from '../../../api'; + +describe('export', function() { + this.timeout(10000); + + // hooks + before(() => { + return new Promise((fulfil, reject) => { + // TODO this is brittle. Make it unnecessary + process.chdir(__dirname); + process.env.NODE_ENV = 'production'; + + // TODO this API isn't great. Rethink it + const builder = api.build({ + bundler: 'rollup' + }, { + src: path.join(__dirname, 'src'), + routes: path.join(__dirname, 'src/routes'), + dest: path.join(__dirname, '__sapper__/build') + }); + + builder.on('error', reject); + builder.on('done', () => { + // TODO it'd be nice if build and export returned promises. + // not sure how best to combine promise and event emitter + const exporter = api.exporter({ + build: '__sapper__/build', + dest: '__sapper__/export', + static: 'static', + basepath: '', + timeout: 5000 + }); + + exporter.on('error', (err: Error) => { + console.error(err); + reject(err); + }); + exporter.on('done', () => fulfil()); + }); + }); + }); + + it('crawls a site', () => { + const files = walk('__sapper__/export'); + + const client_assets = files.filter(file => file.startsWith('client/')); + const non_client_assets = files.filter(file => !file.startsWith('client/')).sort(); + + assert.ok(client_assets.length > 0); + + assert.deepEqual(non_client_assets, [ + 'blog.json', + 'blog/bar.json', + 'blog/bar/index.html', + 'blog/baz.json', + 'blog/baz/index.html', + 'blog/foo.json', + 'blog/foo/index.html', + 'blog/index.html', + 'global.css', + 'index.html', + 'service-worker.js' + ]); + }); + + // TODO test timeout, basepath +}); \ No newline at end of file diff --git a/test/apps/export/rollup.config.js b/test/apps/export/rollup.config.js new file mode 100644 index 0000000..943b676 --- /dev/null +++ b/test/apps/export/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/export/src/client.js b/test/apps/export/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/export/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/export/src/routes/_error.html b/test/apps/export/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/export/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/export/src/routes/blog/[slug].html b/test/apps/export/src/routes/blog/[slug].html new file mode 100644 index 0000000..f44adaa --- /dev/null +++ b/test/apps/export/src/routes/blog/[slug].html @@ -0,0 +1,11 @@ +

{post.title}

+ + \ No newline at end of file diff --git a/test/apps/export/src/routes/blog/[slug].json.js b/test/apps/export/src/routes/blog/[slug].json.js new file mode 100644 index 0000000..66781ad --- /dev/null +++ b/test/apps/export/src/routes/blog/[slug].json.js @@ -0,0 +1,19 @@ +import posts from './_posts.js'; + +export function get(req, res) { + const post = posts.find(post => post.slug === req.params.slug); + + if (post) { + res.writeHead(200, { + 'Content-Type': 'application/json' + }); + + res.end(JSON.stringify(post)); + } else { + res.writeHead(404, { + 'Content-Type': 'application/json' + }); + + res.end(JSON.stringify({ message: 'not found' })); + } +} \ No newline at end of file diff --git a/test/apps/export/src/routes/blog/_posts.js b/test/apps/export/src/routes/blog/_posts.js new file mode 100644 index 0000000..d283aa6 --- /dev/null +++ b/test/apps/export/src/routes/blog/_posts.js @@ -0,0 +1,5 @@ +export default [ + { slug: 'foo', title: 'once upon a foo' }, + { slug: 'bar', title: 'a bar is born' }, + { slug: 'baz', title: 'bazzily ever after' } +]; \ No newline at end of file diff --git a/test/apps/export/src/routes/blog/index.html b/test/apps/export/src/routes/blog/index.html new file mode 100644 index 0000000..2eea681 --- /dev/null +++ b/test/apps/export/src/routes/blog/index.html @@ -0,0 +1,15 @@ +

blog

+ +{#each posts as post} +

{post.title}

+{/each} + + \ No newline at end of file diff --git a/test/apps/export/src/routes/blog/index.json.js b/test/apps/export/src/routes/blog/index.json.js new file mode 100644 index 0000000..0097f77 --- /dev/null +++ b/test/apps/export/src/routes/blog/index.json.js @@ -0,0 +1,9 @@ +import posts from './_posts.js'; + +export function get(req, res) { + res.writeHead(200, { + 'Content-Type': 'application/json' + }); + + res.end(JSON.stringify(posts)); +} \ No newline at end of file diff --git a/test/apps/export/src/routes/index.html b/test/apps/export/src/routes/index.html new file mode 100644 index 0000000..37f82e0 --- /dev/null +++ b/test/apps/export/src/routes/index.html @@ -0,0 +1,3 @@ +

Great success!

+ +blog \ No newline at end of file diff --git a/test/apps/export/src/server.js b/test/apps/export/src/server.js new file mode 100644 index 0000000..d5a668b --- /dev/null +++ b/test/apps/export/src/server.js @@ -0,0 +1,15 @@ +import sirv from 'sirv'; +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT, NODE_ENV } = process.env; +const dev = NODE_ENV === 'development'; + +polka() + .use( + sirv('static', { dev }), + sapper.middleware() + ) + .listen(PORT, err => { + if (err) console.log('error', err); + }); diff --git a/test/apps/export/src/service-worker.js b/test/apps/export/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/export/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/export/src/template.html b/test/apps/export/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/export/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/export/static/global.css b/test/apps/export/static/global.css new file mode 100644 index 0000000..800f57a --- /dev/null +++ b/test/apps/export/static/global.css @@ -0,0 +1,3 @@ +body { + font-family: 'Comic Sans MS'; +} \ No newline at end of file diff --git a/test/apps/ignore/__test__.ts b/test/apps/ignore/__test__.ts new file mode 100644 index 0000000..b38423b --- /dev/null +++ b/test/apps/ignore/__test__.ts @@ -0,0 +1,82 @@ +import * as path from 'path'; +import * as assert from 'assert'; +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import { AppRunner } from '../AppRunner'; + +describe('ignore', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // hooks + before(() => { + return new Promise((fulfil, reject) => { + // TODO this is brittle. Make it unnecessary + process.chdir(__dirname); + process.env.NODE_ENV = 'production'; + + // TODO this API isn't great. Rethink it + const emitter = build({ + bundler: 'rollup' + }, { + src: path.join(__dirname, 'src'), + routes: path.join(__dirname, 'src/routes'), + dest: path.join(__dirname, '__sapper__/build') + }); + + emitter.on('error', reject); + + emitter.on('done', async () => { + try { + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page } = await runner.start()); + + fulfil(); + } catch (err) { + reject(err); + } + }); + }); + }); + + after(() => runner.end()); + + it('respects `options.ignore` values (RegExp)', async () => { + await page.goto(`${base}/foobar`); + + assert.equal( + await page.evaluate(() => document.documentElement.textContent), + 'foobar' + ); + }); + + it('respects `options.ignore` values (String #1)', async () => { + await page.goto(`${base}/buzz`); + + assert.equal( + await page.evaluate(() => document.documentElement.textContent), + 'buzz' + ); + }); + + it('respects `options.ignore` values (String #2)', async () => { + await page.goto(`${base}/fizzer`); + + assert.equal( + await page.evaluate(() => document.documentElement.textContent), + 'fizzer' + ); + }); + + it('respects `options.ignore` values (Function)', async () => { + await page.goto(`${base}/hello`); + + assert.equal( + await page.evaluate(() => document.documentElement.textContent), + 'hello' + ); + }); +}); \ No newline at end of file diff --git a/test/apps/ignore/rollup.config.js b/test/apps/ignore/rollup.config.js new file mode 100644 index 0000000..943b676 --- /dev/null +++ b/test/apps/ignore/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/ignore/src/client.js b/test/apps/ignore/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/ignore/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/ignore/src/routes/[slug].html b/test/apps/ignore/src/routes/[slug].html new file mode 100644 index 0000000..ade66de --- /dev/null +++ b/test/apps/ignore/src/routes/[slug].html @@ -0,0 +1 @@ +

{params.slug.toUpperCase()}

\ No newline at end of file diff --git a/test/apps/ignore/src/routes/_error.html b/test/apps/ignore/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/ignore/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/ignore/src/routes/a.html b/test/apps/ignore/src/routes/a.html new file mode 100644 index 0000000..0e6757b --- /dev/null +++ b/test/apps/ignore/src/routes/a.html @@ -0,0 +1 @@ +

a

\ No newline at end of file diff --git a/test/apps/ignore/src/routes/b/index.html b/test/apps/ignore/src/routes/b/index.html new file mode 100644 index 0000000..d93079d --- /dev/null +++ b/test/apps/ignore/src/routes/b/index.html @@ -0,0 +1,11 @@ +

{letter}

+ + \ No newline at end of file diff --git a/test/apps/ignore/src/routes/b/index.json.js b/test/apps/ignore/src/routes/b/index.json.js new file mode 100644 index 0000000..29b7ede --- /dev/null +++ b/test/apps/ignore/src/routes/b/index.json.js @@ -0,0 +1,3 @@ +export function get(req, res) { + res.end(JSON.stringify('b')); +} \ No newline at end of file diff --git a/test/apps/ignore/src/routes/index.html b/test/apps/ignore/src/routes/index.html new file mode 100644 index 0000000..e08c0cb --- /dev/null +++ b/test/apps/ignore/src/routes/index.html @@ -0,0 +1,3 @@ +

Great success!

+ +a \ No newline at end of file diff --git a/test/apps/ignore/src/server.js b/test/apps/ignore/src/server.js new file mode 100644 index 0000000..f987e4a --- /dev/null +++ b/test/apps/ignore/src/server.js @@ -0,0 +1,19 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +const app = polka().use(sapper.middleware({ + ignore: [ + /foobar/i, + '/buzz', + 'fizz', + x => x === '/hello' + ] +})); + +['foobar', 'buzz', 'fizzer', 'hello'].forEach(uri => { + app.get('/'+uri, (req, res) => res.end(uri)); +}); + +app.listen(PORT); diff --git a/test/apps/ignore/src/service-worker.js b/test/apps/ignore/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/ignore/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/ignore/src/template.html b/test/apps/ignore/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/ignore/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/layout/__test__.ts b/test/apps/layout/__test__.ts new file mode 100644 index 0000000..7edc390 --- /dev/null +++ b/test/apps/layout/__test__.ts @@ -0,0 +1,74 @@ +import * as path from 'path'; +import * as assert from 'assert'; +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import { AppRunner } from '../AppRunner'; +import { wait } from '../../utils'; + +describe('layout', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + + // hooks + before(() => { + return new Promise((fulfil, reject) => { + // TODO this is brittle. Make it unnecessary + process.chdir(__dirname); + process.env.NODE_ENV = 'production'; + + // TODO this API isn't great. Rethink it + const emitter = build({ + bundler: 'rollup' + }, { + src: path.join(__dirname, 'src'), + routes: path.join(__dirname, 'src/routes'), + dest: path.join(__dirname, '__sapper__/build') + }); + + emitter.on('error', reject); + + emitter.on('done', async () => { + try { + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start } = await runner.start()); + + fulfil(); + } catch (err) { + reject(err); + } + }); + }); + }); + + after(() => runner.end()); + + it('only recreates components when necessary', async () => { + await page.goto(`${base}/foo/bar/baz`); + await start(); + + const text1 = await page.evaluate(() => document.querySelector('#sapper').textContent); + assert.deepEqual(text1.split('\n').filter(Boolean), [ + 'y: bar 1', + 'z: baz 1', + 'click me', + 'child segment: baz' + ]); + + await page.click('[href="foo/bar/qux"]'); + await wait(50); + + const text2 = await page.evaluate(() => document.querySelector('#sapper').textContent); + assert.deepEqual(text2.split('\n').filter(Boolean), [ + 'y: bar 1', + 'z: qux 2', + 'click me', + 'child segment: qux' + ]); + }); +}); \ No newline at end of file diff --git a/test/apps/layout/rollup.config.js b/test/apps/layout/rollup.config.js new file mode 100644 index 0000000..943b676 --- /dev/null +++ b/test/apps/layout/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/layout/src/client.js b/test/apps/layout/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/layout/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/layout/src/routes/[slug].html b/test/apps/layout/src/routes/[slug].html new file mode 100644 index 0000000..ade66de --- /dev/null +++ b/test/apps/layout/src/routes/[slug].html @@ -0,0 +1 @@ +

{params.slug.toUpperCase()}

\ No newline at end of file diff --git a/test/app/src/routes/[x]/[y]/[z].html b/test/apps/layout/src/routes/[x]/[y]/[z].html similarity index 87% rename from test/app/src/routes/[x]/[y]/[z].html rename to test/apps/layout/src/routes/[x]/[y]/[z].html index 787e413..018f001 100644 --- a/test/app/src/routes/[x]/[y]/[z].html +++ b/test/apps/layout/src/routes/[x]/[y]/[z].html @@ -1,5 +1,5 @@ z: {segment} {count} - +click me + \ No newline at end of file diff --git a/test/app/src/routes/redirect-to.html b/test/apps/redirects/src/routes/redirect-to.html similarity index 100% rename from test/app/src/routes/redirect-to.html rename to test/apps/redirects/src/routes/redirect-to.html diff --git a/test/apps/redirects/src/server.js b/test/apps/redirects/src/server.js new file mode 100644 index 0000000..0e7741c --- /dev/null +++ b/test/apps/redirects/src/server.js @@ -0,0 +1,8 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use(sapper.middleware()) + .listen(PORT); diff --git a/test/apps/redirects/src/service-worker.js b/test/apps/redirects/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/redirects/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/redirects/src/template.html b/test/apps/redirects/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/redirects/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/scroll/__test__.ts b/test/apps/scroll/__test__.ts new file mode 100644 index 0000000..4e6e403 --- /dev/null +++ b/test/apps/scroll/__test__.ts @@ -0,0 +1,86 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import { AppRunner } from '../AppRunner'; +import { wait } from '../../utils'; + +describe('scroll', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + let prefetchRoutes: () => Promise; + + // hooks + before(() => { + return new Promise((fulfil, reject) => { + // TODO this is brittle. Make it unnecessary + process.chdir(__dirname); + process.env.NODE_ENV = 'production'; + + // TODO this API isn't great. Rethink it + const emitter = build({ + bundler: 'rollup' + }, { + src: path.join(__dirname, 'src'), + routes: path.join(__dirname, 'src/routes'), + dest: path.join(__dirname, '__sapper__/build') + }); + + emitter.on('error', reject); + + emitter.on('done', async () => { + try { + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start, prefetchRoutes } = await runner.start()); + + fulfil(); + } catch (err) { + reject(err); + } + }); + }); + }); + + after(() => runner.end()); + + it('scrolls to active deeplink', async () => { + await page.goto(`${base}/tall-page#foo`); + await start(); + + const scrollY = await page.evaluate(() => window.scrollY); + assert.ok(scrollY > 0, scrollY); + }); + + it('resets scroll when a link is clicked', async () => { + await page.goto(`${base}/tall-page#foo`); + await start(); + await prefetchRoutes(); + + await page.click('[href="another-tall-page"]'); + await wait(50); + + assert.equal( + await page.evaluate(() => window.scrollY), + 0 + ); + }); + + it('preserves scroll when a link with sapper-noscroll is clicked', async () => { + await page.goto(`${base}/tall-page#foo`); + await start(); + await prefetchRoutes(); + + await page.click('[href="another-tall-page"][sapper-noscroll]'); + await wait(50); + + const scrollY = await page.evaluate(() => window.scrollY); + + assert.ok(scrollY > 0); + }); +}); \ No newline at end of file diff --git a/test/apps/scroll/rollup.config.js b/test/apps/scroll/rollup.config.js new file mode 100644 index 0000000..943b676 --- /dev/null +++ b/test/apps/scroll/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/scroll/src/client.js b/test/apps/scroll/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/scroll/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/scroll/src/routes/_error.html b/test/apps/scroll/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/scroll/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/scroll/src/routes/another-tall-page.html b/test/apps/scroll/src/routes/another-tall-page.html new file mode 100644 index 0000000..d3e148c --- /dev/null +++ b/test/apps/scroll/src/routes/another-tall-page.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/apps/scroll/src/routes/index.html b/test/apps/scroll/src/routes/index.html new file mode 100644 index 0000000..0cc4b72 --- /dev/null +++ b/test/apps/scroll/src/routes/index.html @@ -0,0 +1 @@ +

Great success!

\ No newline at end of file diff --git a/test/apps/scroll/src/routes/tall-page.html b/test/apps/scroll/src/routes/tall-page.html new file mode 100644 index 0000000..7e42ff9 --- /dev/null +++ b/test/apps/scroll/src/routes/tall-page.html @@ -0,0 +1,6 @@ +
+ +
+ link + link +
\ No newline at end of file diff --git a/test/apps/scroll/src/server.js b/test/apps/scroll/src/server.js new file mode 100644 index 0000000..0e7741c --- /dev/null +++ b/test/apps/scroll/src/server.js @@ -0,0 +1,8 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use(sapper.middleware()) + .listen(PORT); diff --git a/test/apps/scroll/src/service-worker.js b/test/apps/scroll/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/scroll/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/scroll/src/template.html b/test/apps/scroll/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/scroll/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/store/__test__.ts b/test/apps/store/__test__.ts new file mode 100644 index 0000000..c61f3c0 --- /dev/null +++ b/test/apps/store/__test__.ts @@ -0,0 +1,60 @@ +import * as path from 'path'; +import * as assert from 'assert'; +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import { AppRunner } from '../AppRunner'; + +describe('store', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + + // hooks + before(() => { + return new Promise((fulfil, reject) => { + // TODO this is brittle. Make it unnecessary + process.chdir(__dirname); + process.env.NODE_ENV = 'production'; + + // TODO this API isn't great. Rethink it + const emitter = build({ + bundler: 'rollup' + }, { + src: path.join(__dirname, 'src'), + routes: path.join(__dirname, 'src/routes'), + dest: path.join(__dirname, '__sapper__/build') + }); + + emitter.on('error', reject); + + emitter.on('done', async () => { + try { + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start } = await runner.start()); + + fulfil(); + } catch (err) { + reject(err); + } + }); + }); + }); + + after(() => runner.end()); + + const title = () => page.$eval('h1', node => node.textContent); + + it('renders store props', async () => { + await page.goto(`${base}/store`); + + assert.equal(await title(), 'hello world'); + + await start(); + assert.equal(await title(), 'hello world'); + }); +}); \ No newline at end of file diff --git a/test/apps/store/rollup.config.js b/test/apps/store/rollup.config.js new file mode 100644 index 0000000..943b676 --- /dev/null +++ b/test/apps/store/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/store/src/client.js b/test/apps/store/src/client.js new file mode 100644 index 0000000..df66471 --- /dev/null +++ b/test/apps/store/src/client.js @@ -0,0 +1,11 @@ +import { Store } from 'svelte/store.js'; +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper'), + store: data => new Store(data) +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/store/src/routes/_error.html b/test/apps/store/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/store/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/store/src/routes/index.html b/test/apps/store/src/routes/index.html new file mode 100644 index 0000000..221c0f5 --- /dev/null +++ b/test/apps/store/src/routes/index.html @@ -0,0 +1,7 @@ +

Great success!

+ +a +ok +ok + +
\ No newline at end of file diff --git a/test/app/src/routes/store.html b/test/apps/store/src/routes/store.html similarity index 100% rename from test/app/src/routes/store.html rename to test/apps/store/src/routes/store.html diff --git a/test/apps/store/src/server.js b/test/apps/store/src/server.js new file mode 100644 index 0000000..c40e690 --- /dev/null +++ b/test/apps/store/src/server.js @@ -0,0 +1,22 @@ +import polka from 'polka'; +import { Store } from 'svelte/store.js'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use((req, res, next) => { + req.hello = 'hello'; + res.locals = { name: 'world' }; + next(); + }) + .use( + sapper.middleware({ + store: (req, res) => { + return new Store({ + title: `${req.hello} ${res.locals.name}` + }); + } + }) + ) + .listen(PORT); diff --git a/test/apps/store/src/service-worker.js b/test/apps/store/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/store/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/store/src/template.html b/test/apps/store/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/store/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/with-basepath/__test__.ts b/test/apps/with-basepath/__test__.ts new file mode 100644 index 0000000..83a4916 --- /dev/null +++ b/test/apps/with-basepath/__test__.ts @@ -0,0 +1,97 @@ +import * as path from 'path'; +import * as assert from 'assert'; +import * as puppeteer from 'puppeteer'; +import * as api from '../../../api'; +import { walk } from '../../utils'; +import { AppRunner } from '../AppRunner'; + +describe('with-basepath', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // hooks + before(() => { + return new Promise((fulfil, reject) => { + // TODO this is brittle. Make it unnecessary + process.chdir(__dirname); + process.env.NODE_ENV = 'production'; + + // TODO this API isn't great. Rethink it + const builder = api.build({ + bundler: 'rollup' + }, { + src: path.join(__dirname, 'src'), + routes: path.join(__dirname, 'src/routes'), + dest: path.join(__dirname, '__sapper__/build') + }); + + builder.on('error', reject); + builder.on('done', () => { + // TODO it'd be nice if build and export returned promises. + // not sure how best to combine promise and event emitter + const exporter = api.exporter({ + build: '__sapper__/build', + dest: '__sapper__/export', + static: 'static', + basepath: 'custom-basepath', + timeout: 5000 + }); + + exporter.on('error', (err: Error) => { + console.error(err); + reject(err); + }); + + exporter.on('done', async () => { + try { + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page } = await runner.start()); + + fulfil(); + } catch (err) { + reject(err); + } + }); + }); + }); + }); + + after(() => runner.end()); + + it('serves /custom-basepath', async () => { + await page.goto(`${base}/custom-basepath`); + + assert.equal( + await page.$eval('h1', node => node.textContent), + 'Great success!' + ); + }); + + it('emits a basepath message', async () => { + await page.goto(`${base}/custom-basepath`); + + assert.deepEqual(runner.messages, [{ + __sapper__: true, + event: 'basepath', + basepath: '/custom-basepath' + }]); + }); + + it('crawls an exported site with basepath', () => { + const files = walk('__sapper__/export'); + + const client_assets = files.filter(file => file.startsWith('custom-basepath/client/')); + const non_client_assets = files.filter(file => !file.startsWith('custom-basepath/client/')).sort(); + + assert.ok(client_assets.length > 0); + + assert.deepEqual(non_client_assets, [ + 'custom-basepath/global.css', + 'custom-basepath/index.html', + 'custom-basepath/service-worker.js' + ]); + }); +}); \ No newline at end of file diff --git a/test/apps/with-basepath/rollup.config.js b/test/apps/with-basepath/rollup.config.js new file mode 100644 index 0000000..943b676 --- /dev/null +++ b/test/apps/with-basepath/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/with-basepath/src/client.js b/test/apps/with-basepath/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/with-basepath/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/with-basepath/src/routes/_error.html b/test/apps/with-basepath/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/with-basepath/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/with-basepath/src/routes/index.html b/test/apps/with-basepath/src/routes/index.html new file mode 100644 index 0000000..0cc4b72 --- /dev/null +++ b/test/apps/with-basepath/src/routes/index.html @@ -0,0 +1 @@ +

Great success!

\ No newline at end of file diff --git a/test/apps/with-basepath/src/server.js b/test/apps/with-basepath/src/server.js new file mode 100644 index 0000000..b187dad --- /dev/null +++ b/test/apps/with-basepath/src/server.js @@ -0,0 +1,16 @@ +import sirv from 'sirv'; +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT, NODE_ENV } = process.env; +const dev = NODE_ENV === 'development'; + +polka() + .use( + 'custom-basepath', + sirv('static', { dev }), + sapper.middleware() + ) + .listen(PORT, err => { + if (err) console.log('error', err); + }); diff --git a/test/apps/with-basepath/src/service-worker.js b/test/apps/with-basepath/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/with-basepath/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/with-basepath/src/template.html b/test/apps/with-basepath/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/with-basepath/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/with-basepath/static/global.css b/test/apps/with-basepath/static/global.css new file mode 100644 index 0000000..800f57a --- /dev/null +++ b/test/apps/with-basepath/static/global.css @@ -0,0 +1,3 @@ +body { + font-family: 'Comic Sans MS'; +} \ No newline at end of file diff --git a/test/common/test.js b/test/common/test.js deleted file mode 100644 index b48303b..0000000 --- a/test/common/test.js +++ /dev/null @@ -1,874 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); -const Nightmare = require('nightmare'); -const walkSync = require('walk-sync'); -const rimraf = require('rimraf'); -const ports = require('port-authority'); -const fetch = require('node-fetch'); - -Nightmare.action('page', { - title(done) { - this.evaluate_now(() => document.querySelector('h1').textContent, done); - }, - - html(done) { - this.evaluate_now(() => document.documentElement.innerHTML, done); - }, - - text(done) { - this.evaluate_now(() => document.body.textContent, done); - } -}); - -Nightmare.action('init', function(done) { - this.evaluate_now(() => window.init(), done); -}); - -Nightmare.action('prefetchRoutes', function(done) { - this.evaluate_now(() => window.prefetchRoutes(), done); -}); - -const cli = path.resolve(__dirname, '../../sapper'); - -const wait = ms => new Promise(f => setTimeout(f, ms)); - -describe('sapper', function() { - process.chdir(path.resolve(__dirname, '../app')); - - // clean up after previous test runs - rimraf.sync('__sapper__'); - - this.timeout(process.env.CI ? 30000 : 15000); - - // TODO reinstate dev tests - // run({ - // mode: 'development' - // }); - - run({ - mode: 'production' - }); - - run({ - mode: 'production', - basepath: '/custom-basepath' - }); - - testExport({}); - - testExport({ basepath: '/custom-basepath' }); -}); - -function testExport({ basepath = '' }) { - describe(basepath ? `export --basepath ${basepath}` : 'export', () => { - before(() => { - if (basepath) { - process.env.BASEPATH = basepath; - } - - return exec(`node ${cli} export ${basepath ? `--basepath ${basepath}` : ''}`); - }); - - it('export all pages', () => { - const dest = path.resolve(__dirname, '../app/__sapper__/export'); - - // Pages that should show up in the extraction directory. - const expectedPages = [ - 'index.html', - 'about/index.html', - 'slow-preload/index.html', - - 'redirect-from/index.html', - 'redirect-to/index.html', - 'non-sapper-redirect-from/index.html', - 'non-sapper-redirect-to/index.html', - - 'blog/index.html', - 'blog/a-very-long-post/index.html', - 'blog/how-can-i-get-involved/index.html', - 'blog/how-is-sapper-different-from-next/index.html', - 'blog/how-to-use-sapper/index.html', - 'blog/what-is-sapper/index.html', - 'blog/why-the-name/index.html', - 'blog/encödïng-test/index.html', - - 'blog.json', - 'blog/a-very-long-post.json', - 'blog/how-can-i-get-involved.json', - 'blog/how-is-sapper-different-from-next.json', - 'blog/how-to-use-sapper.json', - 'blog/what-is-sapper.json', - 'blog/why-the-name.json', - 'blog/encödïng-test.json', - - 'favicon.png', - 'global.css', - 'great-success.png', - 'manifest.json', - 'service-worker.js', - 'svelte-logo-192.png', - 'svelte-logo-512.png', - ].map(file => { - return basepath ? `${basepath.replace(/^[\/\\]/, '')}/${file}` : file; - }); - - // Client scripts that should show up in the extraction directory. - const expectedClientRegexes = [ - /client\/[^/]+\/main(\.\d+)?\.js/, - /client\/[^/]+\/index(\.\d+)?\.js/, - /client\/[^/]+\/about(\.\d+)?\.js/, - /client\/[^/]+\/blog_\$slug(\.\d+)?\.js/, - /client\/[^/]+\/blog(\.\d+)?\.js/, - /client\/[^/]+\/slow\$45preload(\.\d+)?\.js/, - ]; - const allPages = walkSync(dest); - - expectedPages.forEach((expectedPage) => { - assert.ok(allPages.includes(expectedPage),`Could not find page matching ${expectedPage}`); - }); - - expectedClientRegexes.forEach((expectedRegex) => { - // Ensure each client page regular expression matches at least one - // generated page. - let matched = false; - for (const page of allPages) { - if (expectedRegex.test(page)) { - matched = true; - break; - } - } - assert.ok(matched, `Could not find client page matching ${expectedRegex}`); - }); - }); - }); -} - -function run({ mode, basepath = '' }) { - describe(`mode=${mode}`, function () { - let proc; - let capture; - - let base; - let captured_basepath; - - const nightmare = new Nightmare(); - - nightmare.on('console', (type, ...args) => { - console[type](...args); - }); - - nightmare.on('page', (type, ...args) => { - if (type === 'error') { - console.error(args[1]); - } else { - console.warn(type, args); - } - }); - - before(() => { - const promise = mode === 'production' - ? exec(`node ${cli} build -l`).then(() => ports.find(3000)) - : ports.find(3000).then(port => { - exec(`node ${cli} dev`); - return ports.wait(port).then(() => port); - }); - - return promise.then(port => { - base = `http://localhost:${port}`; - if (basepath) base += basepath; - - const dir = mode === 'production' ? '__sapper__/build' : '__sapper__/dev'; - - if (mode === 'production') { - assert.ok(fs.existsSync('__sapper__/build/index.js')); - } - - proc = require('child_process').fork(`${dir}/server/server.js`, { - cwd: process.cwd(), - env: { - NODE_ENV: mode, - BASEPATH: basepath, - SAPPER_DEST: dir, - PORT: port - } - }); - - let handler; - - proc.on('message', message => { - if (message.__sapper__) { - if (message.event === 'basepath') { - captured_basepath = basepath; - } - return; - } - - if (handler) handler(message); - }); - - capture = fn => { - return new Promise((fulfil, reject) => { - const captured = []; - - let start = Date.now(); - - handler = message => { - if (message.type === 'ready') { - fn().then(() => { - proc.send({ - action: 'end' - }); - }, reject); - } - - else if (message.type === 'done') { - fulfil(captured); - handler = null; - } - - else { - captured.push(message); - } - }; - - proc.send({ - action: 'start' - }); - }); - }; - }); - }); - - after(() => { - // give a chance to clean up - return Promise.all([ - nightmare.end(), - new Promise(fulfil => { - proc.on('exit', fulfil); - proc.kill(); - }) - ]); - }); - - describe('basic functionality', () => { - it('serves /', () => { - return nightmare.goto(base).page.title().then(title => { - assert.equal(title, 'Great success!'); - }); - }); - - it('serves /?', () => { - return nightmare.goto(`${base}?`).page.title().then(title => { - assert.equal(title, 'Great success!'); - }); - }); - - it('serves static route', () => { - 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`).page.title().then(title => { - assert.equal(title, 'What is Sapper?'); - }); - }); - - it('navigates to a new page without reloading', () => { - return nightmare.goto(base).init().prefetchRoutes() - .then(() => { - return capture(() => nightmare.click('a[href="about"]')); - }) - .then(requests => { - assert.deepEqual(requests.map(r => r.url), []); - }) - .then(() => wait(100)) - .then(() => nightmare.path()) - .then(path => { - assert.equal(path, `${basepath}/about`); - return nightmare.title(); - }) - .then(title => { - assert.equal(title, 'About'); - }); - }); - - it('navigates programmatically', () => { - return nightmare - .goto(`${base}/about`) - .init() - .evaluate(() => window.goto('blog/what-is-sapper')) - .title() - .then(title => { - assert.equal(title, 'What is Sapper?'); - }); - }); - - it('prefetches programmatically', () => { - return capture(() => nightmare.goto(`${base}/about`).init()) - .then(() => { - return capture(() => { - return nightmare - .click('.prefetch') - .wait(200); - }); - }) - .then(requests => { - assert.ok(!!requests.find(r => r.url === `/blog/why-the-name.json`)); - }); - }); - - it('scrolls to active deeplink', () => { - return nightmare - .goto(`${base}/blog/a-very-long-post#four`) - .init() - .evaluate(() => window.scrollY) - .then(scrollY => { - assert.ok(scrollY > 0, scrollY); - }); - }); - - it.skip('reuses prefetch promise', () => { - return nightmare - .goto(`${base}/blog`) - .init() - .then(() => { - return capture(() => { - return nightmare - .evaluate(() => { - const a = document.querySelector('[href="blog/what-is-sapper"]'); - a.dispatchEvent(new MouseEvent('mousemove')); - }) - .wait(200); - }); - }) - .then(mouseover_requests => { - assert.ok(mouseover_requests.findIndex(r => r.url === `/blog/what-is-sapper.json`) !== -1); - - return capture(() => { - return nightmare - .click('[href="blog/what-is-sapper"]') - .wait(200); - }); - }) - .then(click_requests => { - assert.ok(click_requests.findIndex(r => r.url === `/blog/what-is-sapper.json`) === -1); - }); - }); - - it('cancels navigation if subsequent navigation occurs during preload', () => { - return nightmare - .goto(base) - .init() - .click('a[href="slow-preload"]') - .wait(100) - .click('a[href="about"]') - .wait(100) - .then(() => nightmare.path()) - .then(path => { - assert.equal(path, `${basepath}/about`); - return nightmare.title(); - }) - .then(title => { - assert.equal(title, 'About'); - return nightmare.evaluate(() => window.fulfil({})).wait(100); - }) - .then(() => nightmare.path()) - .then(path => { - assert.equal(path, `${basepath}/about`); - return nightmare.title(); - }) - .then(title => { - assert.equal(title, 'About'); - }); - }); - - it('calls a delete handler', () => { - return nightmare - .goto(`${base}/delete-test`) - .init() - .click('.del') - .wait(() => window.deleted) - .evaluate(() => window.deleted.id) - .then(id => { - assert.equal(id, 42); - }); - }); - - it('hydrates initial route', () => { - return nightmare.goto(base) - .wait('.hydrate-test') - .evaluate(() => { - window.el = document.querySelector('.hydrate-test'); - }) - .init() - .evaluate(() => { - return document.querySelector('.hydrate-test') === window.el; - }) - .then(matches => { - assert.ok(matches); - }); - }); - - it('redirects on server', () => { - return nightmare.goto(`${base}/redirect-from`) - .path() - .then(path => { - assert.equal(path, `${basepath}/redirect-to`); - }) - .then(() => nightmare.page.title()) - .then(title => { - assert.equal(title, 'redirected'); - }); - }); - - it('redirects in client', () => { - return nightmare.goto(base) - .wait('[href="redirect-from"]') - .click('[href="redirect-from"]') - .wait(200) - .path() - .then(path => { - assert.equal(path, `${basepath}/redirect-to`); - }) - .then(() => nightmare.page.title()) - .then(title => { - assert.equal(title, 'redirected'); - }); - }); - - it('redirects on server (root)', () => { - return nightmare.goto(`${base}/redirect-root`) - .path() - .then(path => { - assert.equal(path, `${basepath}/`); - }) - .then(() => nightmare.page.title()) - .then(title => { - assert.equal(title, 'Great success!'); - }); - }); - - it('redirects in client (root)', () => { - return nightmare.goto(base) - .wait('[href="redirect-root"]') - .click('[href="redirect-root"]') - .wait(200) - .path() - .then(path => { - assert.equal(path, `${basepath}/`); - }) - .then(() => nightmare.page.title()) - .then(title => { - assert.equal(title, 'Great success!'); - }); - }); - - it('handles 4xx error on server', () => { - return nightmare.goto(`${base}/blog/nope`) - .path() - .then(path => { - assert.equal(path, `${basepath}/blog/nope`); - }) - .then(() => nightmare.page.title()) - .then(title => { - assert.equal(title, '404') - }); - }); - - it('handles 4xx error in client', () => { - return nightmare.goto(base) - .init() - .click('[href="blog/nope"]') - .wait(200) - .path() - .then(path => { - assert.equal(path, `${basepath}/blog/nope`); - }) - .then(() => nightmare.page.title()) - .then(title => { - assert.equal(title, '404'); - }); - }); - - it('handles non-4xx error on server', () => { - return nightmare.goto(`${base}/blog/throw-an-error`) - .path() - .then(path => { - assert.equal(path, `${basepath}/blog/throw-an-error`); - }) - .then(() => nightmare.page.title()) - .then(title => { - assert.equal(title, '500') - }); - }); - - it('handles non-4xx error in client', () => { - return nightmare.goto(base) - .init() - .click('[href="blog/throw-an-error"]') - .wait(200) - .path() - .then(path => { - assert.equal(path, `${basepath}/blog/throw-an-error`); - }) - .then(() => nightmare.page.title()) - .then(title => { - assert.equal(title, '500'); - }); - }); - - // Ignores are meant for top-level escape. - // ~> Sapper **should** own the entire {basepath} when designated. - if (!basepath) { - it('respects `options.ignore` values (RegExp)', () => { - return nightmare.goto(`${base}/foobar`) - .evaluate(() => document.documentElement.textContent) - .then(text => { - assert.equal(text, 'foobar'); - }); - }); - - it('respects `options.ignore` values (String #1)', () => { - return nightmare.goto(`${base}/buzz`) - .evaluate(() => document.documentElement.textContent) - .then(text => { - assert.equal(text, 'buzz'); - }); - }); - - it('respects `options.ignore` values (String #2)', () => { - return nightmare.goto(`${base}/fizzer`) - .evaluate(() => document.documentElement.textContent) - .then(text => { - assert.equal(text, 'fizzer'); - }); - }); - - it('respects `options.ignore` values (Function)', () => { - return nightmare.goto(`${base}/hello`) - .evaluate(() => document.documentElement.textContent) - .then(text => { - assert.equal(text, 'hello'); - }); - }); - } - - it('does not attempt client-side navigation to server routes', () => { - return nightmare.goto(`${base}/blog/how-is-sapper-different-from-next`) - .init() - .click(`[href="blog/how-is-sapper-different-from-next.json"]`) - .wait(200) - .page.text() - .then(text => { - JSON.parse(text); - }); - }); - - it('does not serve error page for non-page errors', () => { - return nightmare.goto(`${base}/throw-an-error`) - .page.text() - .then(text => { - assert.equal(text, 'nope'); - }); - }); - - it('encodes routes', () => { - return nightmare.goto(`${base}/fünke`) - .page.title() - .then(title => { - assert.equal(title, `I'm afraid I just blue myself`); - }); - }); - - it('serializes Set objects returned from preload', () => { - return nightmare.goto(`${base}/preload-values/set`) - .page.title() - .then(title => { - assert.equal(title, 'true'); - return nightmare.init().page.title(); - }) - .then(title => { - assert.equal(title, 'true'); - }); - }); - - it('bails on custom classes returned from preload', () => { - return nightmare.goto(`${base}/preload-values/custom-class`) - .page.title() - .then(title => { - assert.equal(title, '42'); - return nightmare.init().page.title(); - }) - .then(title => { - assert.equal(title, '42'); - }); - }); - - it('renders store props', () => { - return nightmare.goto(`${base}/store`) - .page.title() - .then(title => { - assert.equal(title, 'hello world'); - return nightmare.init().page.title(); - }) - .then(title => { - assert.equal(title, 'hello world'); - }); - }); - - it('sends cookies when using this.fetch with credentials: "include"', () => { - return nightmare.goto(`${base}/credentials?creds=include`) - .page.title() - .then(title => { - assert.equal(title, 'a: 1, b: 2, max-age: undefined'); - }); - }); - - it('does not send cookies when using this.fetch without credentials', () => { - return nightmare.goto(`${base}/credentials`) - .page.title() - .then(title => { - assert.equal(title, 'unauthorized'); - }); - }); - - it('delegates to fetch on the client', () => { - return nightmare.goto(base).init() - .click('[href="credentials?creds=include"]') - .wait(100) - .page.title() - .then(title => { - assert.equal(title, 'a: 1, b: 2, max-age: undefined'); - }); - }); - - it('includes service worker', () => { - return nightmare.goto(base).page.html().then(html => { - assert.ok(html.indexOf('service-worker.js') !== -1); - }); - }); - - it('sets preloading true when appropriate', () => { - return nightmare - .goto(base) - .init() - .click('a[href="slow-preload"]') - .wait(100) - .evaluate(() => { - const progress = document.querySelector('progress'); - return !!progress; - }) - .then(hasProgressIndicator => { - assert.ok(hasProgressIndicator); - }) - .then(() => nightmare.evaluate(() => window.fulfil())) - .then(() => nightmare.evaluate(() => { - const progress = document.querySelector('progress'); - return !!progress; - })) - .then(hasProgressIndicator => { - assert.ok(!hasProgressIndicator); - }); - }); - - 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'); - }); - }); - - it('replaces %sapper.xxx% tags safely', () => { - return nightmare - .goto(`${base}/unsafe-replacement`) - .init() - .page.html() - .then(html => { - assert.equal(html.indexOf('%sapper'), -1); - }); - }); - - it('only recreates components when necessary', () => { - return nightmare - .goto(`${base}/foo/bar/baz`) - .init() - .evaluate(() => document.querySelector('#sapper').textContent) - .then(text => { - assert.deepEqual(text.split('\n').filter(Boolean), [ - 'y: bar 1', - 'z: baz 1', - 'child segment: baz' - ]); - - return nightmare.click(`a`) - .then(() => wait(100)) - .then(() => { - return nightmare.evaluate(() => document.querySelector('#sapper').textContent); - }); - }) - .then(text => { - assert.deepEqual(text.split('\n').filter(Boolean), [ - 'y: bar 1', - 'z: qux 2', - 'child segment: qux' - ]); - }); - }); - - it('uses a fallback index component if none is provided', () => { - return nightmare.goto(`${base}/missing-index/ok`) - .page.title() - .then(title => { - assert.equal(title, 'it works'); - }); - }); - - it('runs preload in root component', () => { - return nightmare.goto(`${base}/preload-root`) - .page.title() - .then(title => { - assert.equal(title, 'root preload function ran: true'); - }); - }); - - it('allows reserved words as route names', () => { - return nightmare.goto(`${base}/const`).init() - .page.title() - .then(title => { - assert.equal(title, 'reserved words are okay as routes'); - }); - }); - - it('encodes req.params and req.query for server-rendered pages', () => { - return nightmare.goto(`${base}/echo/page/encöded?message=hëllö+wörld`) - .page.title() - .then(title => { - assert.equal(title, 'encöded (hëllö wörld)'); - }); - }); - - it('encodes req.params and req.query for client-rendered pages', () => { - return nightmare.goto(base).init() - .click('a[href="echo/page/encöded?message=hëllö+wörld"]') - .wait(100) - .page.title() - .then(title => { - assert.equal(title, 'encöded (hëllö wörld)'); - }); - }); - - it('accepts value-less query string parameter on server', () => { - return nightmare.goto(`${base}/echo/page/empty?message`) - .page.title() - .then(title => { - assert.equal(title, 'empty ()'); - }); - }); - - it('accepts value-less query string parameter on client', () => { - return nightmare.goto(base).init() - .click('a[href="echo/page/empty?message"]') - .wait(100) - .page.title() - .then(title => { - assert.equal(title, 'empty ()'); - }); - }); - - it('encodes req.params for server routes', () => { - return nightmare.goto(`${base}/echo/server-route/encöded`) - .page.title() - .then(title => { - assert.equal(title, 'encöded'); - }); - }); - - it('resets scroll when a link is clicked', () => { - return nightmare.goto(`${base}/blog/a-very-long-post`) - .init() - .evaluate(() => window.scrollTo(0, 200)) - .click('[href="blog/another-long-post"]') - .wait(100) - .evaluate(() => window.scrollY) - .then(scrollY => { - assert.equal(scrollY, 0); - }); - }); - - it('preserves scroll when a link with sapper-noscroll is clicked', () => { - return nightmare.goto(`${base}/blog/a-very-long-post`) - .init() - .evaluate(() => window.scrollTo(0, 200)) - .click('[href="blog/another-long-post"][sapper-noscroll]') - .wait(100) - .evaluate(() => window.scrollY) - .then(scrollY => { - assert.equal(scrollY, 200); - }); - }); - }); - - describe('headers', () => { - it('sets Content-Type, Link...preload, and Cache-Control headers', () => { - return capture(() => fetch(base)).then(responses => { - const { headers } = responses[0]; - - assert.equal( - headers['content-type'], - 'text/html' - ); - - assert.equal( - headers['cache-control'], - 'max-age=600' - ); - - const str = ['main', '.+?\\.\\d+'] - .map(file => { - return `<${basepath}/client/[^/]+/${file}\\.js>;rel="preload";as="script"`; - }) - .join(', '); - - const regex = new RegExp(str); - - assert.ok( - regex.test(headers['link']), - headers['link'] - ); - }); - }); - }); - }); -} - -function exec(cmd) { - return new Promise((fulfil, reject) => { - const parts = cmd.trim().split(' '); - const proc = require('child_process').spawn(parts.shift(), parts); - - proc.stdout.on('data', data => { - process.stdout.write(data); - }); - - proc.stderr.on('data', data => { - process.stderr.write(data); - }); - - proc.on('error', reject); - - proc.on('close', () => fulfil()); - }); -} diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000..fa802fa --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,23 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +export function wait(ms: number) { + return new Promise(fulfil => setTimeout(fulfil, ms)); +} + +export function walk(cwd: string, dir = cwd, files: string[] = []) { + fs.readdirSync(dir).forEach(file => { + const resolved = path.resolve(dir, file); + if (fs.statSync(resolved).isDirectory()) { + walk(cwd, resolved, files); + } else { + files.push(posixify(path.relative(cwd, resolved))); + } + }); + + return files; +} + +function posixify(str: string) { + return str.replace(/\\/g, '/'); +} \ No newline at end of file From 52f40f9e63dab19ad11f5073b2446b2632c85179 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 8 Oct 2018 19:21:15 -0400 Subject: [PATCH 135/178] Improve internal API --- mocha.opts | 2 +- package-lock.json | 15 -- package.json | 2 - rollup.config.js | 4 +- src/api.ts | 10 +- src/api/build.ts | 98 +++++---- src/api/dev.ts | 116 +++++----- src/api/export.ts | 73 +++---- src/api/find_page.ts | 3 +- src/api/interfaces.ts | 45 ---- src/api/utils/noop.ts | 1 + src/{cli => api}/utils/validate_bundler.ts | 0 src/cli.ts | 200 +++++++++++++++--- src/cli/build.ts | 54 ----- src/cli/dev.ts | 80 ------- src/cli/export.ts | 51 ----- src/cli/start.ts | 39 ---- src/cli/upgrade.ts | 53 ----- src/config.ts | 11 - src/config/env.ts | 7 + src/{ => config}/rollup.ts | 20 +- src/{ => config}/webpack.ts | 16 +- src/core/create_compilers/RollupCompiler.ts | 6 +- src/core/create_compilers/RollupResult.ts | 2 +- src/core/create_compilers/extract_css.ts | 2 +- src/core/create_compilers/index.ts | 17 +- src/core/create_manifest_data.ts | 5 +- src/core/create_manifests.ts | 68 +++--- src/core/read_template.ts | 3 +- src/core/utils.ts | 81 ------- src/interfaces.ts | 48 ++++- src/utils.ts | 111 +++++++++- test/apps/basics/{__test__.ts => test.ts} | 32 +-- .../apps/credentials/{__test__.ts => test.ts} | 32 +-- test/apps/encoding/{__test__.ts => test.ts} | 32 +-- test/apps/errors/{__test__.ts => test.ts} | 32 +-- test/apps/export/__test__.ts | 70 ------ test/apps/export/test.ts | 38 ++++ test/apps/ignore/{__test__.ts => test.ts} | 32 +-- test/apps/layout/{__test__.ts => test.ts} | 32 +-- test/apps/preloading/{__test__.ts => test.ts} | 31 +-- test/apps/redirects/{__test__.ts => test.ts} | 32 +-- test/apps/scroll/{__test__.ts => test.ts} | 32 +-- test/apps/store/__test__.ts | 60 ------ test/apps/store/test.ts | 36 ++++ .../with-basepath/{__test__.ts => test.ts} | 53 +---- 46 files changed, 696 insertions(+), 1091 deletions(-) delete mode 100644 src/api/interfaces.ts create mode 100644 src/api/utils/noop.ts rename src/{cli => api}/utils/validate_bundler.ts (100%) delete mode 100644 src/cli/build.ts delete mode 100644 src/cli/dev.ts delete mode 100644 src/cli/export.ts delete mode 100644 src/cli/start.ts delete mode 100644 src/cli/upgrade.ts delete mode 100644 src/config.ts create mode 100644 src/config/env.ts rename src/{ => config}/rollup.ts (57%) rename src/{ => config}/webpack.ts (66%) delete mode 100644 src/core/utils.ts rename test/apps/basics/{__test__.ts => test.ts} (86%) rename test/apps/credentials/{__test__.ts => test.ts} (62%) rename test/apps/encoding/{__test__.ts => test.ts} (66%) rename test/apps/errors/{__test__.ts => test.ts} (74%) delete mode 100644 test/apps/export/__test__.ts create mode 100644 test/apps/export/test.ts rename test/apps/ignore/{__test__.ts => test.ts} (61%) rename test/apps/layout/{__test__.ts => test.ts} (58%) rename test/apps/preloading/{__test__.ts => test.ts} (74%) rename test/apps/redirects/{__test__.ts => test.ts} (68%) rename test/apps/scroll/{__test__.ts => test.ts} (63%) delete mode 100644 test/apps/store/__test__.ts create mode 100644 test/apps/store/test.ts rename test/apps/with-basepath/{__test__.ts => test.ts} (50%) diff --git a/mocha.opts b/mocha.opts index e793278..c769c5c 100644 --- a/mocha.opts +++ b/mocha.opts @@ -2,4 +2,4 @@ --require ts-node/register --recursive test/unit/*/*.ts -test/apps/*/__test__.ts \ No newline at end of file +test/apps/*/test.ts \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8a86dca..c519e27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4423,12 +4423,6 @@ "error-ex": "^1.2.0" } }, - "parse-ms": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", - "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", - "dev": true - }, "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", @@ -4599,15 +4593,6 @@ "integrity": "sha512-wa5+qGVg9Yt7PB6rYm3kXlKzgzgivYTLRandezh43jjRqgyDyP+9YxfJpJiLs9yKD1WeU8/OvtToWpW7255FtA==", "dev": true }, - "pretty-ms": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-3.2.0.tgz", - "integrity": "sha512-ZypexbfVUGTFxb0v+m1bUyy92DHe5SyYlnyY0msyms5zd3RwyvNgyxZZsXXgoyzlxjx5MiqtXUdhUfvQbe0A2Q==", - "dev": true, - "requires": { - "parse-ms": "^1.0.0" - } - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", diff --git a/package.json b/package.json index 528ff2a..6318672 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "polka": "^0.4.0", "port-authority": "^1.0.5", "pretty-bytes": "^5.0.0", - "pretty-ms": "^3.1.0", "puppeteer": "^1.9.0", "require-relative": "^0.8.7", "rimraf": "^2.6.2", @@ -61,7 +60,6 @@ "sirv": "^0.2.2", "svelte": "^2.6.3", "svelte-loader": "^2.9.0", - "tiny-glob": "^0.2.2", "ts-node": "^7.0.1", "typescript": "^2.8.3", "webpack": "^4.8.3", diff --git a/rollup.config.js b/rollup.config.js index 8fd7c4b..7e13cd6 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -43,8 +43,8 @@ export default [ `src/api.ts`, `src/cli.ts`, `src/core.ts`, - `src/rollup.ts`, - `src/webpack.ts` + `src/config/rollup.ts`, + `src/config/webpack.ts` ], output: { dir: 'dist', diff --git a/src/api.ts b/src/api.ts index b8cee72..d90cdfd 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,6 +1,4 @@ -import { dev } from './api/dev'; -import { build } from './api/build'; -import { exporter } from './api/export'; -import { find_page } from './api/find_page'; - -export { dev, build, exporter, find_page }; \ No newline at end of file +export { dev } from './api/dev'; +export { build } from './api/build'; +export { export } from './api/export'; +export { find_page } from './api/find_page'; \ No newline at end of file diff --git a/src/api/build.ts b/src/api/build.ts index c9d93f4..5fd251c 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -2,44 +2,51 @@ import * as fs from 'fs'; import * as path from 'path'; import mkdirp from 'mkdirp'; import rimraf from 'rimraf'; -import { EventEmitter } from 'events'; import minify_html from './utils/minify_html'; import { create_compilers, create_main_manifests, create_manifest_data, create_serviceworker_manifest } from '../core'; -import * as events from './interfaces'; import { copy_shimport } from './utils/copy_shimport'; -import { Dirs } from '../interfaces'; import read_template from '../core/read_template'; +import { CompileResult } from '../core/create_compilers/interfaces'; +import { noop } from './utils/noop'; +import validate_bundler from './utils/validate_bundler'; type Opts = { - legacy: boolean; - bundler: 'rollup' | 'webpack'; + cwd?: string; + src?: string; + routes?: string; + dest?: string; + output?: string; + static_files?: string; + legacy?: boolean; + bundler?: 'rollup' | 'webpack'; + oncompile?: ({ type, result }: { type: string, result: CompileResult }) => void; }; -export function build(opts: Opts, dirs: Dirs) { - const emitter = new EventEmitter(); +export async function build({ + cwd = process.cwd(), + src = path.join(cwd, 'src'), + routes = path.join(cwd, 'src/routes'), + output = path.join(cwd, '__sapper__'), + static_files = path.join(cwd, 'static'), + dest = path.join(cwd, '__sapper__/build'), - execute(emitter, opts, dirs).then( - () => { - emitter.emit('done', {}); // TODO do we need to pass back any info? - }, - error => { - emitter.emit('error', { - error - }); - } - ); + bundler, + legacy = false, + oncompile = noop +}: Opts = {}) { + bundler = validate_bundler(bundler); - return emitter; -} + if (legacy && bundler === 'webpack') { + throw new Error(`Legacy builds are not supported for projects using webpack`); + } -async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) { - rimraf.sync(path.join(dirs.dest, '**/*')); - mkdirp.sync(`${dirs.dest}/client`); - copy_shimport(dirs.dest); + rimraf.sync(path.join(dest, '**/*')); + mkdirp.sync(`${dest}/client`); + copy_shimport(dest); // minify src/template.html // TODO compile this to a function? could be quicker than str.replace(...).replace(...).replace(...) - const template = read_template(); + const template = read_template(src); // remove this in a future version if (template.indexOf('%sapper.base%') === -1) { @@ -48,47 +55,53 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) { throw error; } - fs.writeFileSync(`${dirs.dest}/template.html`, minify_html(template)); + fs.writeFileSync(`${dest}/template.html`, minify_html(template)); - const manifest_data = create_manifest_data(); + const manifest_data = create_manifest_data(routes); // create src/manifest/client.js and src/manifest/server.js - create_main_manifests({ bundler: opts.bundler, manifest_data }); + create_main_manifests({ + bundler, + manifest_data, + cwd, + src, + dest, + routes, + output, + dev: false + }); - const { client, server, serviceworker } = await create_compilers(opts.bundler); + const { client, server, serviceworker } = await create_compilers(bundler, cwd, src, dest, true); const client_result = await client.compile(); - emitter.emit('build', { + oncompile({ type: 'client', - // TODO duration/warnings result: client_result }); - const build_info = client_result.to_json(manifest_data, dirs); + const build_info = client_result.to_json(manifest_data, { src, routes, dest }); - if (opts.legacy) { + if (legacy) { process.env.SAPPER_LEGACY_BUILD = 'true'; - const { client } = await create_compilers(opts.bundler); + const { client } = await create_compilers(bundler, cwd, src, dest, true); const client_result = await client.compile(); - emitter.emit('build', { + oncompile({ type: 'client (legacy)', - // TODO duration/warnings result: client_result }); - client_result.to_json(manifest_data, dirs); + client_result.to_json(manifest_data, { src, routes, dest }); build_info.legacy_assets = client_result.assets; delete process.env.SAPPER_LEGACY_BUILD; } - fs.writeFileSync(path.join(dirs.dest, 'build.json'), JSON.stringify(build_info)); + fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify(build_info)); const server_stats = await server.compile(); - emitter.emit('build', { + oncompile({ type: 'server', - // TODO duration/warnings result: server_stats }); @@ -97,14 +110,15 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) { if (serviceworker) { create_serviceworker_manifest({ manifest_data, - client_files: client_result.chunks.map(chunk => `client/${chunk.file}`) + output, + client_files: client_result.chunks.map(chunk => `client/${chunk.file}`), + static_files }); serviceworker_stats = await serviceworker.compile(); - emitter.emit('build', { + oncompile({ type: 'serviceworker', - // TODO duration/warnings result: serviceworker_stats }); } diff --git a/src/api/dev.ts b/src/api/dev.ts index 37833ed..405a727 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -5,30 +5,45 @@ import * as child_process from 'child_process'; import * as ports from 'port-authority'; import mkdirp from 'mkdirp'; import rimraf from 'rimraf'; -import { locations } from '../config'; import { EventEmitter } from 'events'; import { create_manifest_data, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core'; import { Compiler, Compilers } from '../core/create_compilers'; -import { CompileResult, CompileError } from '../core/create_compilers/interfaces'; +import { CompileResult } from '../core/create_compilers/interfaces'; import Deferred from './utils/Deferred'; -import * as events from './interfaces'; -import validate_bundler from '../cli/utils/validate_bundler'; +import validate_bundler from './utils/validate_bundler'; import { copy_shimport } from './utils/copy_shimport'; -import { ManifestData } from '../interfaces'; +import { ManifestData, FatalEvent, ErrorEvent, ReadyEvent, InvalidEvent } from '../interfaces'; import read_template from '../core/read_template'; +import { noop } from './utils/noop'; -export function dev(opts) { +type Opts = { + cwd?: string, + src?: string, + dest?: string, + routes?: string, + output?: string, + static_files?: string, + 'dev-port'?: number, + live?: boolean, + hot?: boolean, + 'devtools-port'?: number, + bundler?: 'rollup' | 'webpack', + port?: number +}; + +export function dev(opts: Opts) { return new Watcher(opts); } class Watcher extends EventEmitter { - bundler: string; + bundler: 'rollup' | 'webpack'; dirs: { + cwd: string; src: string; dest: string; routes: string; - rollup: string; - webpack: string; + output: string; + static_files: string; } port: number; closed: boolean; @@ -54,34 +69,23 @@ class Watcher extends EventEmitter { } constructor({ - src = locations.src(), - dest = locations.dest(), - routes = locations.routes(), + cwd = process.cwd(), + src = path.join(cwd, 'src'), + routes = path.join(cwd, 'src/routes'), + output = path.join(cwd, '__sapper__'), + static_files = path.join(cwd, 'static'), + dest = path.join(cwd, '__sapper__/dev'), 'dev-port': dev_port, live, hot, 'devtools-port': devtools_port, bundler, - webpack = 'webpack', - rollup = 'rollup', port = +process.env.PORT - }: { - src: string, - dest: string, - routes: string, - 'dev-port': number, - live: boolean, - hot: boolean, - 'devtools-port': number, - bundler?: string, - webpack: string, - rollup: string, - port: number - }) { + }: Opts) { super(); this.bundler = validate_bundler(bundler); - this.dirs = { src, dest, routes, webpack, rollup }; + this.dirs = { cwd, src, dest, routes, output, static_files }; this.port = port; this.closed = false; @@ -101,7 +105,7 @@ class Watcher extends EventEmitter { }; // remove this in a future version - const template = read_template(); + const template = read_template(src); if (template.indexOf('%sapper.base%') === -1) { const error = new Error(`As of Sapper v0.10, your template.html file must include %sapper.base% in the `); error.code = `missing-sapper-base`; @@ -120,7 +124,7 @@ class Watcher extends EventEmitter { async init() { if (this.port) { if (!await ports.check(this.port)) { - this.emit('fatal', { + this.emit('fatal', { message: `Port ${this.port} is unavailable` }); return; @@ -129,7 +133,7 @@ class Watcher extends EventEmitter { this.port = await ports.find(3000); } - const { dest } = this.dirs; + const { cwd, src, dest, routes, output, static_files } = this.dirs; rimraf.sync(dest); mkdirp.sync(`${dest}/client`); if (this.bundler === 'rollup') copy_shimport(dest); @@ -142,10 +146,16 @@ class Watcher extends EventEmitter { let manifest_data: ManifestData; try { - manifest_data = create_manifest_data(); - create_main_manifests({ bundler: this.bundler, manifest_data, dev_port: this.dev_port }); + manifest_data = create_manifest_data(routes); + create_main_manifests({ + bundler: this.bundler, + manifest_data, + dev: true, + dev_port: this.dev_port, + cwd, src, dest, routes, output + }); } catch (err) { - this.emit('fatal', { + this.emit('fatal', { message: err.message }); return; @@ -155,7 +165,7 @@ class Watcher extends EventEmitter { this.filewatchers.push( watch_dir( - locations.routes(), + routes, ({ path: file, stats }) => { if (stats.isDirectory()) { return path.basename(file)[0] !== '_'; @@ -164,19 +174,25 @@ class Watcher extends EventEmitter { }, () => { try { - const new_manifest_data = create_manifest_data(); - create_main_manifests({ bundler: this.bundler, manifest_data, dev_port: this.dev_port }); + const new_manifest_data = create_manifest_data(routes); + create_main_manifests({ + bundler: this.bundler, + manifest_data, // TODO is this right? not new_manifest_data? + dev: true, + dev_port: this.dev_port, + cwd, src, dest, routes, output + }); manifest_data = new_manifest_data; } catch (err) { - this.emit('error', { + this.emit('error', { message: err.message }); } } ), - fs.watch(`${locations.src()}/template.html`, () => { + fs.watch(`${src}/template.html`, () => { this.dev_server.send({ action: 'reload' }); @@ -186,12 +202,12 @@ class Watcher extends EventEmitter { let deferred = new Deferred(); // TODO watch the configs themselves? - const compilers: Compilers = await create_compilers(this.bundler, this.dirs); + const compilers: Compilers = await create_compilers(this.bundler, cwd, src, dest, false); let log = ''; const emitFatal = () => { - this.emit('fatal', { + this.emit('fatal', { message: `Server crashed`, log }); @@ -215,7 +231,7 @@ class Watcher extends EventEmitter { ports.wait(this.port) .then((() => { - this.emit('ready', { + this.emit('ready', { port: this.port, process: this.proc }); @@ -233,7 +249,7 @@ class Watcher extends EventEmitter { .catch(err => { if (this.crashed) return; - this.emit('fatal', { + this.emit('fatal', { message: `Server is not listening on port ${this.port}`, log }); @@ -312,7 +328,9 @@ class Watcher extends EventEmitter { create_serviceworker_manifest({ manifest_data, - client_files + output, + client_files, + static_files }); deferred.fulfil(); @@ -361,7 +379,7 @@ class Watcher extends EventEmitter { }; process.nextTick(() => { - this.emit('invalid', { + this.emit('invalid', { changed: Array.from(this.current_build.changed), invalid: { server: this.current_build.rebuilding.has('server'), @@ -384,7 +402,7 @@ class Watcher extends EventEmitter { compiler.watch((err?: Error, result?: CompileResult) => { if (err) { - this.emit('error', { + this.emit('error', { type: name, message: err.message }); @@ -455,14 +473,12 @@ class DevServer { } } -function noop() {} - function watch_dir( dir: string, filter: ({ path, stats }: { path: string, stats: fs.Stats }) => boolean, callback: () => void ) { - let watch; + let watch: any; let closed = false; import('cheap-watch').then(CheapWatch => { @@ -470,7 +486,7 @@ function watch_dir( watch = new CheapWatch({ dir, filter, debounce: 50 }); - watch.on('+', ({ isNew }) => { + watch.on('+', ({ isNew }: { isNew: boolean }) => { if (isNew) callback(); }); diff --git a/src/api/export.ts b/src/api/export.ts index 9b9fa46..86bf804 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -4,58 +4,52 @@ import * as sander from 'sander'; import * as url from 'url'; import fetch from 'node-fetch'; import * as ports from 'port-authority'; -import { EventEmitter } from 'events'; import clean_html from './utils/clean_html'; import minify_html from './utils/minify_html'; import Deferred from './utils/Deferred'; -import * as events from './interfaces'; +import { noop } from './utils/noop'; type Opts = { - build: string, - dest: string, - static: string, + build_dir?: string, + export_dir?: string, + cwd?: string, + static?: string, basepath?: string, - timeout: number | false + timeout?: number | false, + oninfo?: ({ message }: { message: string }) => void; + onfile?: ({ file, size, status }: { file: string, size: number, status: number }) => void; }; -export function exporter(opts: Opts) { - const emitter = new EventEmitter(); - - execute(emitter, opts).then( - () => { - emitter.emit('done', {}); // TODO do we need to pass back any info? - }, - error => { - emitter.emit('error', { - error - }); - } - ); - - return emitter; -} - function resolve(from: string, to: string) { return url.parse(url.resolve(from, to)); } type URL = url.UrlWithStringQuery; -async function execute(emitter: EventEmitter, opts: Opts) { - const export_dir = path.join(opts.dest, opts.basepath); +export { _export as export }; +async function _export({ + cwd = process.cwd(), + static: static_files = path.join(cwd, 'static'), + build_dir = path.join(cwd, '__sapper__/build'), + basepath = '', + export_dir = path.join(cwd, '__sapper__/export', basepath), + timeout = 5000, + oninfo = noop, + onfile = noop +}: Opts = {}) { // Prep output directory sander.rimrafSync(export_dir); - sander.copydirSync(opts.static).to(export_dir); - sander.copydirSync(opts.build, 'client').to(export_dir, 'client'); + sander.copydirSync(static_files).to(export_dir); + sander.copydirSync(build_dir, 'client').to(export_dir, 'client'); - 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_dir, 'service-worker.js')) { + sander.copyFileSync(build_dir, 'service-worker.js').to(export_dir, 'service-worker.js'); } - if (sander.existsSync(opts.build, 'service-worker.js.map')) { - sander.copyFileSync(opts.build, 'service-worker.js.map').to(export_dir, 'service-worker.js.map'); + if (sander.existsSync(build_dir, 'service-worker.js.map')) { + sander.copyFileSync(build_dir, 'service-worker.js.map').to(export_dir, 'service-worker.js.map'); } const port = await ports.find(3000); @@ -64,19 +58,18 @@ async function execute(emitter: EventEmitter, opts: Opts) { const host = `localhost:${port}`; const origin = `${protocol}//${host}`; - const root = resolve(origin, opts.basepath || ''); + const root = resolve(origin, basepath); if (!root.href.endsWith('/')) root.href += '/'; - emitter.emit('info', { + oninfo({ message: `Crawling ${root.href}` }); - const proc = child_process.fork(path.resolve(`${opts.build}/server/server.js`), [], { - cwd: process.cwd(), + const proc = child_process.fork(path.resolve(`${build_dir}/server/server.js`), [], { + cwd, env: Object.assign({ PORT: port, NODE_ENV: 'production', - SAPPER_DEST: opts.build, SAPPER_EXPORT: 'true' }, process.env) }); @@ -98,7 +91,7 @@ async function execute(emitter: EventEmitter, opts: Opts) { body = minify_html(body); } - emitter.emit('file', { + onfile({ file, size: body.length, status @@ -119,9 +112,9 @@ async function execute(emitter: EventEmitter, opts: Opts) { seen.add(pathname); const timeout_deferred = new Deferred(); - const timeout = setTimeout(() => { + const the_timeout = setTimeout(() => { timeout_deferred.reject(new Error(`Timed out waiting for ${url.href}`)); - }, opts.timeout); + }, timeout); const r = await Promise.race([ fetch(url.href, { @@ -130,7 +123,7 @@ async function execute(emitter: EventEmitter, opts: Opts) { timeout_deferred.promise ]); - clearTimeout(timeout); // prevent it hanging at the end + clearTimeout(the_timeout); // prevent it hanging at the end let type = r.headers.get('Content-Type'); let body = await r.text(); diff --git a/src/api/find_page.ts b/src/api/find_page.ts index e986176..6f7cac9 100644 --- a/src/api/find_page.ts +++ b/src/api/find_page.ts @@ -1,7 +1,6 @@ -import { locations } from '../config'; import { create_manifest_data } from '../core'; -export function find_page(pathname: string, cwd = locations.routes()) { +export function find_page(pathname: string, cwd = 'src/routes') { const { pages } = create_manifest_data(cwd); for (let i = 0; i < pages.length; i += 1) { diff --git a/src/api/interfaces.ts b/src/api/interfaces.ts deleted file mode 100644 index 34d8ded..0000000 --- a/src/api/interfaces.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as child_process from 'child_process'; -import { CompileResult } from '../core/create_compilers/interfaces'; - -export type ReadyEvent = { - port: number; - process: child_process.ChildProcess; -}; - -export type ErrorEvent = { - type: string; - message: string; -}; - -export type FatalEvent = { - message: string; - log?: string; -}; - -export type InvalidEvent = { - changed: string[]; - invalid: { - client: boolean; - server: boolean; - serviceworker: boolean; - } -}; - -export type BuildEvent = { - type: string; - errors: Array<{ file: string, message: string, duplicate: boolean }>; - warnings: Array<{ file: string, message: string, duplicate: boolean }>; - duration: number; - result: CompileResult; -} - -export type FileEvent = { - file: string; - size: number; -} - -export type FailureEvent = { - -} - -export type DoneEvent = {}; \ No newline at end of file diff --git a/src/api/utils/noop.ts b/src/api/utils/noop.ts new file mode 100644 index 0000000..628cbae --- /dev/null +++ b/src/api/utils/noop.ts @@ -0,0 +1 @@ +export function noop() {} \ No newline at end of file diff --git a/src/cli/utils/validate_bundler.ts b/src/api/utils/validate_bundler.ts similarity index 100% rename from src/cli/utils/validate_bundler.ts rename to src/api/utils/validate_bundler.ts diff --git a/src/cli.ts b/src/cli.ts index e44e5d3..d5bb287 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -2,11 +2,21 @@ import * as fs from 'fs'; import * as path from 'path'; import sade from 'sade'; import colors from 'kleur'; -import prettyMs from 'pretty-ms'; import * as pkg from '../package.json'; +import { elapsed, repeat, left_pad, format_milliseconds } from './utils'; +import { InvalidEvent, ErrorEvent, FatalEvent, BuildEvent, ReadyEvent } from './interfaces'; const prog = sade('sapper').version(pkg.version); +if (process.argv[2] === 'start') { + // remove this in a future version + console.error(colors.bold.red(`'sapper start' has been removed`)); + console.error(`Use 'node [build_dir]' instead`); + process.exit(1); +} + +const start = Date.now(); + prog.command('dev') .describe('Start a development server') .option('-p, --port', 'Specify a port') @@ -21,10 +31,93 @@ prog.command('dev') 'dev-port': number, live: boolean, hot: boolean, - bundler?: string + bundler?: 'rollup' | 'webpack' }) => { - const { dev } = await import('./cli/dev'); - dev(opts); + const cwd = path.resolve(process.env.SAPPER_BASE || ''); + + const { dev } = await import('./api/dev'); + + try { + const watcher = dev({ + cwd, + port: opts.port, + 'dev-port': opts['dev-port'], + live: opts.live, + hot: opts.hot, + bundler: opts.bundler + }); + + let first = true; + + watcher.on('ready', async (event: ReadyEvent) => { + if (first) { + console.log(colors.bold.cyan(`> Listening on http://localhost:${event.port}`)); + if (opts.open) { + const { exec } = await import('child_process'); + exec(`open http://localhost:${event.port}`); + } + first = false; + } + + // TODO clear screen? + + event.process.stdout.on('data', data => { + process.stdout.write(data); + }); + + event.process.stderr.on('data', data => { + process.stderr.write(data); + }); + }); + + watcher.on('invalid', (event: InvalidEvent) => { + const changed = event.changed.map(filename => path.relative(process.cwd(), filename)).join(', '); + console.log(`\n${colors.bold.cyan(changed)} changed. rebuilding...`); + }); + + watcher.on('error', (event: ErrorEvent) => { + console.log(colors.red(`✗ ${event.type}`)); + console.log(colors.red(event.message)); + }); + + watcher.on('fatal', (event: FatalEvent) => { + console.log(colors.bold.red(`> ${event.message}`)); + if (event.log) console.log(event.log); + }); + + watcher.on('build', (event: BuildEvent) => { + if (event.errors.length) { + console.log(colors.bold.red(`✗ ${event.type}`)); + + event.errors.filter(e => !e.duplicate).forEach(error => { + if (error.file) console.log(colors.bold(error.file)); + console.log(error.message); + }); + + const hidden = event.errors.filter(e => e.duplicate).length; + if (hidden > 0) { + console.log(`${hidden} duplicate ${hidden === 1 ? 'error' : 'errors'} hidden\n`); + } + } else if (event.warnings.length) { + console.log(colors.bold.yellow(`• ${event.type}`)); + + event.warnings.filter(e => !e.duplicate).forEach(warning => { + if (warning.file) console.log(colors.bold(warning.file)); + console.log(warning.message); + }); + + const hidden = event.warnings.filter(e => e.duplicate).length; + if (hidden > 0) { + console.log(`${hidden} duplicate ${hidden === 1 ? 'warning' : 'warnings'} hidden\n`); + } + } else { + console.log(`${colors.bold.green(`✔ ${event.type}`)} ${colors.gray(`(${format_milliseconds(event.duration)})`)}`); + } + }); + } catch (err) { + console.log(colors.bold.red(`> ${err.message}`)); + process.exit(1); + } }); prog.command('build [dest]') @@ -36,25 +129,20 @@ prog.command('build [dest]') .action(async (dest = '__sapper__/build', opts: { port: string, legacy: boolean, - bundler?: string + bundler?: 'rollup' | 'webpack' }) => { console.log(`> Building...`); - process.env.NODE_ENV = process.env.NODE_ENV || 'production'; - process.env.SAPPER_DEST = dest; - - const start = Date.now(); + const cwd = path.resolve(process.env.SAPPER_BASE || ''); try { - const { build } = await import('./cli/build'); - await build(opts); + await _build(opts.bundler, opts.legacy, cwd, dest); const launcher = path.resolve(dest, 'index.js'); fs.writeFileSync(launcher, ` // generated by sapper build at ${new Date().toISOString()} process.env.NODE_ENV = process.env.NODE_ENV || 'production'; - process.env.SAPPER_DEST = __dirname; process.env.PORT = process.env.PORT || ${opts.port || 3000}; console.log('Starting server on port ' + process.env.PORT); @@ -68,15 +156,6 @@ prog.command('build [dest]') } }); -prog.command('start [dir]') - .describe('Start your app') - .option('-p, --port', 'Specify a port') - .option('-o, --open', 'Open a browser window') - .action(async (dir = 'build', opts: { port: number, open: boolean }) => { - const { start } = await import('./cli/start'); - start(dir, opts); - }); - prog.command('export [dest]') .describe('Export your app as static files (if possible)') .option('--build', '(Re)build app before exporting', true) @@ -88,26 +167,46 @@ prog.command('export [dest]') .action(async (dest = '__sapper__/export', opts: { build: boolean, legacy: boolean, - bundler?: string, + bundler?: 'rollup' | 'webpack', 'build-dir': string, basepath?: string, timeout: number | false }) => { - process.env.NODE_ENV = 'production'; - process.env.SAPPER_DEST = opts['build-dir']; - - const start = Date.now(); + const cwd = path.resolve(process.env.SAPPER_BASE || ''); try { if (opts.build) { console.log(`> Building...`); - const { build } = await import('./cli/build'); - await build(opts); + await _build(opts.bundler, opts.legacy, cwd, opts['build-dir']); console.error(`\n> Built in ${elapsed(start)}`); } - const { exporter } = await import('./cli/export'); - await exporter(dest, opts); + const { export: _export } = await import('./api/export'); + const { default: pb } = await import('pretty-bytes'); + + await _export({ + static: path.resolve(cwd, process.env.SAPPER_STATIC || 'static'), + build_dir: opts['build-dir'], + export_dir: dest, + basepath: opts.basepath, + timeout: opts.timeout, + + oninfo: event => { + console.log(colors.bold.cyan(`> ${event.message}`)); + }, + + onfile: event => { + const size_color = event.size > 150000 ? colors.bold.red : event.size > 50000 ? colors.bold.yellow : colors.bold.gray; + const size_label = size_color(left_pad(pb(event.size), 10)); + + const file_label = event.status === 200 + ? event.file + : colors.bold[event.status >= 400 ? 'red' : 'yellow'](`(${event.status}) ${event.file}`); + + console.log(`${size_label} ${file_label}`); + } + }); + console.error(`\n> Finished in ${elapsed(start)}. Type ${colors.bold.cyan(`npx serve ${dest}`)} to run the app.`); } catch (err) { console.error(colors.bold.red(`> ${err.message}`)); @@ -115,10 +214,41 @@ prog.command('export [dest]') } }); -// TODO upgrade - prog.parse(process.argv); -function elapsed(start: number) { - return prettyMs(Date.now() - start); -} + +async function _build( + bundler: 'rollup' | 'webpack', + legacy: boolean, + cwd: string, + dest: string +) { + const { build } = await import('./api/build'); + + await build({ + bundler, + legacy, + cwd, + src: path.resolve(cwd, process.env.SAPPER_SRC || 'src'), + routes: path.resolve(cwd, process.env.SAPPER_ROUTES || 'src/routes'), + dest: path.resolve(cwd, dest), + + oncompile: event => { + let banner = `built ${event.type}`; + let c = colors.cyan; + + const { warnings } = event.result; + if (warnings.length > 0) { + banner += ` with ${warnings.length} ${warnings.length === 1 ? 'warning' : 'warnings'}`; + c = colors.yellow; + } + + console.log(); + console.log(c(`┌─${repeat('─', banner.length)}─┐`)); + console.log(c(`│ ${colors.bold(banner) } │`)); + console.log(c(`└─${repeat('─', banner.length)}─┘`)); + + console.log(event.result.print()); + } + }); +} \ No newline at end of file diff --git a/src/cli/build.ts b/src/cli/build.ts deleted file mode 100644 index 20051bb..0000000 --- a/src/cli/build.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { build as _build } from '../api/build'; -import colors from 'kleur'; -import { locations } from '../config'; -import validate_bundler from './utils/validate_bundler'; -import { repeat } from '../utils'; - -export function build(opts: { bundler?: string, legacy?: boolean }) { - const bundler = validate_bundler(opts.bundler); - - if (opts.legacy && bundler === 'webpack') { - throw new Error(`Legacy builds are not supported for projects using webpack`); - } - - return new Promise((fulfil, reject) => { - try { - const emitter = _build({ - legacy: opts.legacy, - bundler - }, { - dest: locations.dest(), - src: locations.src(), - routes: locations.routes() - }); - - emitter.on('build', event => { - let banner = `built ${event.type}`; - let c = colors.cyan; - - const { warnings } = event.result; - if (warnings.length > 0) { - banner += ` with ${warnings.length} ${warnings.length === 1 ? 'warning' : 'warnings'}`; - c = colors.yellow; - } - - console.log(); - console.log(c(`┌─${repeat('─', banner.length)}─┐`)); - console.log(c(`│ ${colors.bold(banner) } │`)); - console.log(c(`└─${repeat('─', banner.length)}─┘`)); - - console.log(event.result.print()); - }); - - emitter.on('error', event => { - reject(event.error); - }); - - emitter.on('done', event => { - fulfil(); - }); - } catch (err) { - reject(err); - } - }); -} \ No newline at end of file diff --git a/src/cli/dev.ts b/src/cli/dev.ts deleted file mode 100644 index ecaa4a3..0000000 --- a/src/cli/dev.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as path from 'path'; -import colors from 'kleur'; -import * as child_process from 'child_process'; -import prettyMs from 'pretty-ms'; -import { dev as _dev } from '../api/dev'; -import * as events from '../api/interfaces'; - -export function dev(opts: { port: number, open: boolean, bundler?: string }) { - try { - const watcher = _dev(opts); - - let first = true; - - watcher.on('ready', (event: events.ReadyEvent) => { - if (first) { - console.log(colors.bold.cyan(`> Listening on http://localhost:${event.port}`)); - if (opts.open) child_process.exec(`open http://localhost:${event.port}`); - first = false; - } - - // TODO clear screen? - - event.process.stdout.on('data', data => { - process.stdout.write(data); - }); - - event.process.stderr.on('data', data => { - process.stderr.write(data); - }); - }); - - watcher.on('invalid', (event: events.InvalidEvent) => { - const changed = event.changed.map(filename => path.relative(process.cwd(), filename)).join(', '); - console.log(`\n${colors.bold.cyan(changed)} changed. rebuilding...`); - }); - - watcher.on('error', (event: events.ErrorEvent) => { - console.log(colors.red(`✗ ${event.type}`)); - console.log(colors.red(event.message)); - }); - - watcher.on('fatal', (event: events.FatalEvent) => { - console.log(colors.bold.red(`> ${event.message}`)); - if (event.log) console.log(event.log); - }); - - watcher.on('build', (event: events.BuildEvent) => { - if (event.errors.length) { - console.log(colors.bold.red(`✗ ${event.type}`)); - - event.errors.filter(e => !e.duplicate).forEach(error => { - if (error.file) console.log(colors.bold(error.file)); - console.log(error.message); - }); - - const hidden = event.errors.filter(e => e.duplicate).length; - if (hidden > 0) { - console.log(`${hidden} duplicate ${hidden === 1 ? 'error' : 'errors'} hidden\n`); - } - } else if (event.warnings.length) { - console.log(colors.bold.yellow(`• ${event.type}`)); - - event.warnings.filter(e => !e.duplicate).forEach(warning => { - if (warning.file) console.log(colors.bold(warning.file)); - console.log(warning.message); - }); - - const hidden = event.warnings.filter(e => e.duplicate).length; - if (hidden > 0) { - console.log(`${hidden} duplicate ${hidden === 1 ? 'warning' : 'warnings'} hidden\n`); - } - } else { - console.log(`${colors.bold.green(`✔ ${event.type}`)} ${colors.gray(`(${prettyMs(event.duration)})`)}`); - } - }); - } catch (err) { - console.log(colors.bold.red(`> ${err.message}`)); - process.exit(1); - } -} \ No newline at end of file diff --git a/src/cli/export.ts b/src/cli/export.ts deleted file mode 100644 index 214228e..0000000 --- a/src/cli/export.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { exporter as _exporter } from '../api/export'; -import colors from 'kleur'; -import pb from 'pretty-bytes'; -import { locations } from '../config'; -import { left_pad } from '../utils'; - -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(), - static: locations.static(), - dest: export_dir, - basepath, - timeout - }); - - emitter.on('file', event => { - const size_color = event.size > 150000 ? colors.bold.red : event.size > 50000 ? colors.bold.yellow : colors.bold.gray; - const size_label = size_color(left_pad(pb(event.size), 10)); - - const file_label = event.status === 200 - ? event.file - : colors.bold[event.status >= 400 ? 'red' : 'yellow'](`(${event.status}) ${event.file}`); - - console.log(`${size_label} ${file_label}`); - }); - - emitter.on('info', event => { - console.log(colors.bold.cyan(`> ${event.message}`)); - }); - - emitter.on('error', event => { - reject(event.error); - }); - - emitter.on('done', event => { - fulfil(); - }); - } catch (err) { - console.log(`${colors.bold.red(`> ${err.message}`)}`); - process.exit(1); - } - }); -} \ No newline at end of file diff --git a/src/cli/start.ts b/src/cli/start.ts deleted file mode 100644 index ba658ae..0000000 --- a/src/cli/start.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as child_process from 'child_process'; -import colors from 'kleur'; -import * as ports from 'port-authority'; - -export async function start(dir: string, opts: { port: number, open: boolean }) { - let port = opts.port || +process.env.PORT; - - const resolved = path.resolve(dir); - const server = path.resolve(dir, 'server.js'); - - if (!fs.existsSync(server)) { - console.log(`${colors.bold.red(`> ${dir}/server.js does not exist — type ${colors.bold.cyan(dir === 'build' ? `npx sapper build` : `npx sapper build ${dir}`)} to create it`)}`); - return; - } - - if (port) { - if (!await ports.check(port)) { - console.log(`${colors.bold.red(`> Port ${port} is unavailable`)}`); - return; - } - } else { - port = await ports.find(3000); - } - - child_process.fork(server, [], { - cwd: process.cwd(), - env: Object.assign({ - NODE_ENV: 'production', - PORT: port, - SAPPER_DEST: dir - }, process.env) - }); - - await ports.wait(port); - console.log(`${colors.bold.cyan(`> Listening on http://localhost:${port}`)}`); - if (opts.open) child_process.exec(`open http://localhost:${port}`); -} diff --git a/src/cli/upgrade.ts b/src/cli/upgrade.ts deleted file mode 100644 index 7d8b16b..0000000 --- a/src/cli/upgrade.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as fs from 'fs'; -import colors from 'kleur'; - -export default async function upgrade() { - const upgraded = [ - await upgrade_sapper_main() - ].filter(Boolean); - - if (upgraded.length === 0) { - console.log(`No changes!`); - } -} - -async function upgrade_sapper_main() { - const _2xx = read('templates/2xx.html'); - const _4xx = read('templates/4xx.html'); - const _5xx = read('templates/5xx.html'); - - const pattern = / \ No newline at end of file diff --git a/test/apps/scroll/test.ts b/test/apps/scroll/test.ts index 0c0db39..e687405 100644 --- a/test/apps/scroll/test.ts +++ b/test/apps/scroll/test.ts @@ -59,4 +59,15 @@ describe('scroll', function() { assert.ok(scrollY > 0); }); + + it('scrolls into a deeplink on a new page', async () => { + await page.goto(`${base}/tall-page#foo`); + await start(); + await prefetchRoutes(); + + await page.click('[href="another-tall-page#bar"]'); + await wait(50); + const scrollY = await page.evaluate(() => window.scrollY); + assert.ok(scrollY > 0); + }); }); \ No newline at end of file From 44736754adfeec016572d207aedbed4a30fe48fc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 15 Oct 2018 21:27:52 -0400 Subject: [PATCH 142/178] fix file extension --- src/core/create_compilers/extract_css.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/create_compilers/extract_css.ts b/src/core/create_compilers/extract_css.ts index 587997d..e885154 100644 --- a/src/core/create_compilers/extract_css.ts +++ b/src/core/create_compilers/extract_css.ts @@ -195,7 +195,7 @@ export default function extract_css(client_result: CompileResult, components: Pa }); if (chunks_with_css.has(chunk)) { - css_dependencies.push(chunk.file); + css_dependencies.push(chunk.file.replace(/\.js$/, '.css')); chunk.modules.forEach(file => { unclaimed.delete(file); From 2e3aef8b2110fc1e5f13b57b2c619c85e9e80e7c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 15 Oct 2018 21:36:40 -0400 Subject: [PATCH 143/178] simplify --- templates/src/client/start/index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/templates/src/client/start/index.ts b/templates/src/client/start/index.ts index d2dfa0a..258e791 100644 --- a/templates/src/client/start/index.ts +++ b/templates/src/client/start/index.ts @@ -83,12 +83,8 @@ function handle_click(event: MouseEvent) { const svg = typeof a.href === 'object' && a.href.constructor.name === 'SVGAnimatedString'; const href = String(svg ? (a).href.baseVal : a.href); - if (location.hash && href === location.href) { - return; - } - if (href === location.href) { - event.preventDefault(); + if (!location.hash) event.preventDefault(); return; } From cca417a85a0515e052e035c6762d4671c2b034fe Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 15 Oct 2018 22:07:23 -0400 Subject: [PATCH 144/178] simplify, preserve monomorphism --- templates/src/client/app.ts | 39 ++++++++++++++++------------- templates/src/client/start/index.ts | 14 ++--------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/templates/src/client/app.ts b/templates/src/client/app.ts index 5603dca..3773c37 100644 --- a/templates/src/client/app.ts +++ b/templates/src/client/app.ts @@ -106,17 +106,19 @@ export function scroll_state() { }; } -export function navigate(target: Target, id: number, scroll_to?: ScrollPosition | string): Promise { - let scroll: ScrollPosition | string; +export function navigate(target: Target, id: number, noscroll?: boolean, hash?: string): Promise { + let scroll: ScrollPosition; if (id) { // popstate or initial navigation cid = id; - scroll = scroll_to ? scroll_to : scroll_history[id]; } else { + const current_scroll = scroll_state(); + // clicked on a link. preserve scroll state - scroll_history[cid] = scroll_state(); + scroll_history[cid] = current_scroll; + id = cid = ++uid; - scroll = scroll_to ? scroll_to : { x: 0, y: 0 }; + scroll_history[cid] = noscroll ? current_scroll : { x: 0, y: 0 }; } cid = id; @@ -136,12 +138,12 @@ export function navigate(target: Target, id: number, scroll_to?: ScrollPosition if (redirect) { return goto(redirect.location, { replaceState: true }); } - render(data, nullable_depth, scroll, token); + render(data, nullable_depth, scroll_history[id], noscroll, hash, token); if (document.activeElement) document.activeElement.blur(); }); } -function render(data: any, nullable_depth: number, scroll: ScrollPosition | string, token: {}) { +function render(data: any, nullable_depth: number, scroll: ScrollPosition, noscroll: boolean, hash: string, token: {}) { if (current_token !== token) return; if (root_component) { @@ -180,19 +182,20 @@ function render(data: any, nullable_depth: number, scroll: ScrollPosition | stri }); } - if (scroll) { - let scrollPos: ScrollPosition; - if (typeof scroll === 'string') { + if (!noscroll) { + if (hash) { // scroll is an element id (from a hash), we need to compute y. - const deep_linked = document.getElementById(scroll); - scrollPos = deep_linked ? - { x: 0, y: deep_linked.getBoundingClientRect().top } : - scroll_state(); - } else { - scrollPos = scroll; + const deep_linked = document.querySelector(hash); + if (deep_linked) { + scroll = { + x: 0, + y: deep_linked.getBoundingClientRect().top + }; + } } - scroll_history[cid] = scrollPos; - scrollTo(scrollPos.x, scrollPos.y); + + scroll_history[cid] = scroll; + if (scroll) scrollTo(scroll.x, scroll.y); } Object.assign(root_props, data); diff --git a/templates/src/client/start/index.ts b/templates/src/client/start/index.ts index a0da46e..99c01e9 100644 --- a/templates/src/client/start/index.ts +++ b/templates/src/client/start/index.ts @@ -35,13 +35,12 @@ export default function start(opts: { return Promise.resolve().then(() => { const { hash, href } = location; - const scroll_to = hash ? hash.slice(1) : scroll_state(); history.replaceState({ id: uid }, '', href); if (!initial_data.error) { const target = select_route(new URL(location.href)); - if (target) return navigate(target, uid, scroll_to); + if (target) return navigate(target, uid, false, hash); } }); } @@ -100,16 +99,7 @@ function handle_click(event: MouseEvent) { const target = select_route(url); if (target) { const noscroll = a.hasAttribute('sapper-noscroll'); - let scroll_to: ScrollPosition | string; - if (noscroll) { - scroll_to = scroll_state(); - } else if (url.hash) { - scroll_to = url.hash.slice(1); - } else { - scroll_to = { x: 0, y: 0 }; - } - - navigate(target, null, scroll_to); + navigate(target, null, noscroll, url.hash); event.preventDefault(); history.pushState({ id: cid }, '', url.href); } From 4fdc7055c110cf65b32585a3f30fa126fde0fcf1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 15 Oct 2018 22:10:18 -0400 Subject: [PATCH 145/178] -> v0.23.1 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14edc87..e5821c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # sapper changelog +## 0.23.1 + +* Scroll to deeplink that matches current URL ([#472](https://github.com/sveltejs/sapper/pull/472)) +* Scroll to deeplink on another page ([#341](https://github.com/sveltejs/sapper/issues/341)) + ## 0.23.0 * Overhaul internal APIs ([#468](https://github.com/sveltejs/sapper/pull/468)) diff --git a/package.json b/package.json index dfab36a..bb8d535 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.23.0", + "version": "0.23.1", "description": "Military-grade apps, engineered by Svelte", "bin": { "sapper": "./sapper" From f39455014a34da597ce52b740a923a78af1eac75 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 16 Oct 2018 08:44:22 -0400 Subject: [PATCH 146/178] update deps --- package-lock.json | 1059 +++++++++++++++++++-------------------------- package.json | 44 +- 2 files changed, 457 insertions(+), 646 deletions(-) diff --git a/package-lock.json b/package-lock.json index c519e27..5b955dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.22.10", + "version": "0.23.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,6 +10,26 @@ "integrity": "sha1-MU+BaPUK5IoDLP2tX9tDb0ZKl6w=", "dev": true }, + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, "@polka/url": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz", @@ -24,14 +44,14 @@ }, "@types/events": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", "dev": true }, "@types/glob": { - "version": "5.0.36", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.36.tgz", - "integrity": "sha512-KEzSKuP2+3oOjYYjujue6Z3Yqis5HKA1BsIC+jZ1v3lrRNdsqyNNtX0rQf6LSuI4DJJ2z5UV//zBZCcvM0xikg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", "dev": true, "requires": { "@types/events": "*", @@ -61,9 +81,9 @@ "dev": true }, "@types/node": { - "version": "10.11.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.11.4.tgz", - "integrity": "sha512-ojnbBiKkZFYRfQpmtnnWTMw+rzGp/JiystjluW9jgN3VzRwilXddJ6aGQ9V/7iuDG06SBgn7ozW9k3zcAnYjYQ==", + "version": "10.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.0.tgz", + "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==", "dev": true }, "@types/puppeteer": { @@ -285,20 +305,12 @@ } }, "acorn-jsx": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", + "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", "dev": true, "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } + "acorn": "^5.0.3" } }, "agadoo": { @@ -333,21 +345,21 @@ } }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", + "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", "dev": true }, "ansi-escapes": { @@ -357,16 +369,19 @@ "dev": true }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } }, "anymatch": { "version": "2.0.0", @@ -419,6 +434,15 @@ } } }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -651,6 +675,12 @@ "snapdragon": "^0.8.1", "to-regex": "^3.0.2" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -796,41 +826,6 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1151,38 +1146,18 @@ "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "cheap-watch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/cheap-watch/-/cheap-watch-0.3.0.tgz", - "integrity": "sha512-5i7VOOgjZgxOA51L+Y6hFYsSLt2llTmOv+/91QUZgQdOzxTBqnVn9k4XTQEP7TrNBF0aI0SmHezm1wjbjsAUrA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheap-watch/-/cheap-watch-1.0.0.tgz", + "integrity": "sha512-ZeqXFSLfd/NzLXyFDKfa48/14nytP43gBD2Go1L1WUYLXAkEqIo4TTmp89AEbnXVOk47//sB2/16UbVs3vfijA==", "dev": true }, "chokidar": { @@ -1387,12 +1362,6 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -1429,12 +1398,6 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "compare-versions": { - "version": "2.0.1", - "resolved": "http://registry.npmjs.org/compare-versions/-/compare-versions-2.0.1.tgz", - "integrity": "sha1-Htwfk2h/2XoyXFn1XkWgfbEGrKY=", - "dev": true - }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", @@ -1550,12 +1513,14 @@ } }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } @@ -1592,12 +1557,12 @@ "dev": true }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "decode-uri-component": { @@ -1619,14 +1584,6 @@ "dev": true, "requires": { "object-keys": "^1.0.12" - }, - "dependencies": { - "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", - "dev": true - } } }, "define-property": { @@ -1752,9 +1709,9 @@ "dev": true }, "duplexify": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", - "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", + "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", "dev": true, "requires": { "end-of-stream": "^1.0.0", @@ -1868,66 +1825,49 @@ "dev": true }, "eslint": { - "version": "4.19.1", - "resolved": "http://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.7.0.tgz", + "integrity": "sha512-zYCeFQahsxffGl87U2aJ7DPyH8CbWgxBC213Y8+TCanhUTf2gEvfq3EKpHmEcozTLyPmGe9LZdMAwC/CpJBM5A==", "dev": true, "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^2.0.0", "functional-red-black-tree": "^1.0.1", "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", + "globals": "^11.7.0", + "ignore": "^4.0.6", "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", + "inquirer": "^6.1.0", + "is-resolvable": "^1.1.0", + "js-yaml": "^3.12.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", "optionator": "^0.8.2", "path-is-inside": "^1.0.2", "pluralize": "^7.0.0", "progress": "^2.0.0", - "regexpp": "^1.0.1", + "regexpp": "^2.0.1", "require-uncached": "^1.0.3", - "semver": "^5.3.0", + "semver": "^5.5.1", "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" - }, - "dependencies": { - "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" } }, "eslint-import-resolver-node": { @@ -1938,6 +1878,23 @@ "requires": { "debug": "^2.6.9", "resolve": "^1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "eslint-module-utils": { @@ -1948,6 +1905,23 @@ "requires": { "debug": "^2.6.8", "pkg-dir": "^1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "eslint-plugin-import": { @@ -1968,6 +1942,15 @@ "resolve": "^1.6.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "doctrine": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", @@ -1977,19 +1960,31 @@ "esutils": "^2.0.2", "isarray": "^1.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -1997,13 +1992,13 @@ "dev": true }, "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", + "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", "dev": true, "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" + "acorn": "^5.6.0", + "acorn-jsx": "^4.1.1" } }, "esprima": { @@ -2120,13 +2115,13 @@ } }, "external-editor": { - "version": "2.2.0", - "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, @@ -2149,12 +2144,29 @@ "debug": "2.6.9", "mkdirp": "0.5.1", "yauzl": "2.4.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, "fast-json-stable-stringify": { @@ -2356,28 +2368,24 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "bundled": true, "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "bundled": true, "dev": true }, "aproba": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "bundled": true, "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2387,14 +2395,12 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "bundled": true, "dev": true }, "brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "bundled": true, "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -2403,40 +2409,34 @@ }, "chownr": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "bundled": true, "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "bundled": true, "dev": true }, "concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "bundled": true, "dev": true }, "console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "bundled": true, "dev": true }, "core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "bundled": true, "dev": true, "optional": true }, "debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2445,29 +2445,25 @@ }, "deep-extend": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", - "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", + "bundled": true, "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "bundled": true, "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bundled": true, "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2476,15 +2472,13 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "bundled": true, "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2500,8 +2494,7 @@ }, "glob": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2515,15 +2508,13 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "bundled": true, "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.21", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", - "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2532,8 +2523,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2542,8 +2532,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2553,21 +2542,18 @@ }, "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "bundled": true, "dev": true }, "ini": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "bundled": true, "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "bundled": true, "dev": true, "requires": { "number-is-nan": "^1.0.0" @@ -2575,15 +2561,13 @@ }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "bundled": true, "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "bundled": true, "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -2591,14 +2575,12 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "bundled": true, "dev": true }, "minipass": { "version": "2.2.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", - "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "^5.1.1", @@ -2607,8 +2589,7 @@ }, "minizlib": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", - "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2617,8 +2598,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "bundled": true, "dev": true, "requires": { "minimist": "0.0.8" @@ -2626,15 +2606,13 @@ }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "bundled": true, "dev": true, "optional": true }, "needle": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.0.tgz", - "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2645,8 +2623,7 @@ }, "node-pre-gyp": { "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz", - "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2664,8 +2641,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2675,15 +2651,13 @@ }, "npm-bundled": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", - "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", + "bundled": true, "dev": true, "optional": true }, "npm-packlist": { "version": "1.1.10", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", - "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2693,8 +2667,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2706,21 +2679,18 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "bundled": true, "dev": true }, "object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "bundled": true, "dev": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "bundled": true, "dev": true, "requires": { "wrappy": "1" @@ -2728,22 +2698,19 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "bundled": true, "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "bundled": true, "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2753,22 +2720,19 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "bundled": true, "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "bundled": true, "dev": true, "optional": true }, "rc": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.7.tgz", - "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2780,8 +2744,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "bundled": true, "dev": true, "optional": true } @@ -2789,8 +2752,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2805,8 +2767,7 @@ }, "rimraf": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2815,49 +2776,42 @@ }, "safe-buffer": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "bundled": true, "dev": true }, "safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "bundled": true, "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "bundled": true, "dev": true, "optional": true }, "semver": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "bundled": true, "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "bundled": true, "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "bundled": true, "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -2867,8 +2821,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2877,8 +2830,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -2886,15 +2838,13 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "bundled": true, "dev": true, "optional": true }, "tar": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.1.tgz", - "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2909,15 +2859,13 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "bundled": true, "dev": true, "optional": true }, "wide-align": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -2926,14 +2874,12 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "bundled": true, "dev": true }, "yallist": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "bundled": true, "dev": true } } @@ -3042,15 +2988,6 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -3196,19 +3133,13 @@ }, "dependencies": { "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true } } }, @@ -3234,9 +3165,9 @@ "dev": true }, "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "imurmurhash": { @@ -3268,22 +3199,21 @@ "dev": true }, "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", + "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", "dev": true, "requires": { "ansi-escapes": "^3.0.0", "chalk": "^2.0.0", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^2.0.4", + "external-editor": "^3.0.0", "figures": "^2.0.0", - "lodash": "^4.3.0", + "lodash": "^4.17.10", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", + "rxjs": "^6.1.0", "string-width": "^2.1.0", "strip-ansi": "^4.0.0", "through": "^2.3.6" @@ -3536,9 +3466,9 @@ } }, "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { @@ -3558,9 +3488,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -3681,12 +3611,12 @@ } }, "magic-string": { - "version": "0.22.5", - "resolved": "http://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", - "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", + "integrity": "sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==", "dev": true, "requires": { - "vlq": "^0.2.2" + "sourcemap-codec": "^1.4.1" } }, "make-dir": { @@ -3806,6 +3736,12 @@ "brorand": "^1.0.1" } }, + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "dev": true + }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", @@ -3855,24 +3791,6 @@ "pumpify": "^1.3.3", "stream-each": "^1.1.0", "through2": "^2.0.0" - }, - "dependencies": { - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "dev": true, - "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - } } }, "mixin-deep": { @@ -3953,6 +3871,12 @@ "path-is-absolute": "^1.0.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -3985,9 +3909,9 @@ "dev": true }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, "mute-stream": { @@ -4049,9 +3973,9 @@ "dev": true }, "neo-async": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.2.tgz", - "integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", "dev": true }, "nice-try": { @@ -4103,6 +4027,14 @@ "url": "^0.11.0", "util": "^0.10.3", "vm-browserify": "0.0.4" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } } }, "normalize-package-data": { @@ -4143,28 +4075,6 @@ "string.prototype.padend": "^3.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -4243,6 +4153,12 @@ } } }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -4423,12 +4339,6 @@ "error-ex": "^1.2.0" } }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "dev": true - }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -4554,13 +4464,13 @@ "dev": true }, "polka": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/polka/-/polka-0.4.0.tgz", - "integrity": "sha1-RT/FE81TCgid1V2P89rql1LxYd8=", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/polka/-/polka-0.5.1.tgz", + "integrity": "sha512-de0VgqoAgGIJoa4mTj+e9KKIt5uRJOGyE37TbsoYEuDzI+cbJZ8nKBRCmiwv9DUMKrYhAMHW/di7/6/65zgZZQ==", "dev": true, "requires": { - "parseurl": "^1.3.2", - "trouter": "^1.1.0" + "@polka/url": "^0.5.0", + "trouter": "^2.0.1" } }, "port-authority": { @@ -4680,9 +4590,9 @@ } }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "puppeteer": { @@ -4702,25 +4612,13 @@ }, "dependencies": { "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { "ms": "^2.1.1" } - }, - "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true } } }, @@ -4879,6 +4777,15 @@ } } }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -5111,6 +5018,12 @@ "snapdragon": "^0.8.1", "to-regex": "^3.0.2" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -5134,9 +5047,9 @@ } }, "regexpp": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, "relateurl": { @@ -5235,9 +5148,9 @@ } }, "rollup": { - "version": "0.65.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.65.2.tgz", - "integrity": "sha512-BbXOrpxVbx0MpElI6vVLR2B6vnWHvYU/QAMw3GcEXvs601bvgrozuaW30cnvt43B96a6DeoYA0i9T5THanN+Rw==", + "version": "0.66.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.66.6.tgz", + "integrity": "sha512-J7/SWanrcb83vfIHqa8+aVVGzy457GcjA6GVZEnD0x2u4OnOd0Q1pCrEoNe8yLwM6z6LZP02zBT2uW0yh5TqOw==", "dev": true, "requires": { "@types/estree": "0.0.39", @@ -5245,15 +5158,15 @@ } }, "rollup-plugin-commonjs": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.1.8.tgz", - "integrity": "sha512-c3nAfVVyEwbq9OohIeQudfQQdGV9Cl1RE8MUc90fH9UdtCiWAYpI+au3HxGwNf1DdV51HfBjCDbT4fwjsZEUUg==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.2.0.tgz", + "integrity": "sha512-0RM5U4Vd6iHjL6rLvr3lKBwnPsaVml+qxOGaaNUWN1lSq6S33KhITOfHmvxV3z2vy9Mk4t0g4rNlVaJJsNQPWA==", "dev": true, "requires": { - "estree-walker": "^0.5.1", - "magic-string": "^0.22.4", - "resolve": "^1.5.0", - "rollup-pluginutils": "^2.0.1" + "estree-walker": "^0.5.2", + "magic-string": "^0.25.1", + "resolve": "^1.8.1", + "rollup-pluginutils": "^2.3.3" } }, "rollup-plugin-json": { @@ -5285,12 +5198,12 @@ } }, "rollup-plugin-replace": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-replace/-/rollup-plugin-replace-2.0.0.tgz", - "integrity": "sha512-pK9mTd/FNrhtBxcTBXoh0YOwRIShV0gGhv9qvUtNcXHxIMRZMXqfiZKVBmCRGp8/2DJRy62z2JUE7/5tP6WxOQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-replace/-/rollup-plugin-replace-2.1.0.tgz", + "integrity": "sha512-SxrAIgpH/B5/W4SeULgreOemxcpEgKs2gcD42zXw50bhqGWmcnlXneVInQpAqzA/cIly4bJrOpeelmB9p4YXSQ==", "dev": true, "requires": { - "magic-string": "^0.22.4", + "magic-string": "^0.25.1", "minimatch": "^3.0.2", "rollup-pluginutils": "^2.0.1" } @@ -5334,40 +5247,13 @@ } }, "rollup-plugin-typescript": { - "version": "0.8.1", - "resolved": "http://registry.npmjs.org/rollup-plugin-typescript/-/rollup-plugin-typescript-0.8.1.tgz", - "integrity": "sha1-L/fuzCHPa7K0P8J+W2iJUs5xkko=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript/-/rollup-plugin-typescript-1.0.0.tgz", + "integrity": "sha512-d2KDNMJXgaaB//dDGd/YmyMiopt1Pz965Iu3zmEoL08YqNcKRBz26uHqqc47rFGfrJV5kFqifC9IYlh6dpSCLg==", "dev": true, "requires": { - "compare-versions": "2.0.1", - "object-assign": "^4.0.1", - "rollup-pluginutils": "^1.3.1", - "tippex": "^2.1.1", - "typescript": "^1.8.9" - }, - "dependencies": { - "estree-walker": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.2.1.tgz", - "integrity": "sha1-va/oCVOD2EFNXcLs9MkXO225QS4=", - "dev": true - }, - "rollup-pluginutils": { - "version": "1.5.2", - "resolved": "http://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz", - "integrity": "sha1-HhVud4+UtyVb+hs9AXi+j1xVJAg=", - "dev": true, - "requires": { - "estree-walker": "^0.2.1", - "minimatch": "^3.0.2" - } - }, - "typescript": { - "version": "1.8.10", - "resolved": "http://registry.npmjs.org/typescript/-/typescript-1.8.10.tgz", - "integrity": "sha1-tHXW4N/wv1DyluXKbvn7tccyDx4=", - "dev": true - } + "resolve": "^1.8.1", + "rollup-pluginutils": "^2.3.1" } }, "rollup-plugin-virtual": { @@ -5404,19 +5290,13 @@ "aproba": "^1.1.1" } }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { - "rx-lite": "*" + "tslib": "^1.9.0" } }, "sade": { @@ -5469,44 +5349,12 @@ "requires": { "ajv": "^6.1.0", "ajv-keywords": "^3.1.0" - }, - "dependencies": { - "ajv": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", - "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - } } }, "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", "dev": true }, "serialize-javascript": { @@ -5601,14 +5449,6 @@ "@polka/url": "^0.5.0", "mime": "^2.3.1", "tiny-glob": "^0.2.0" - }, - "dependencies": { - "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", - "dev": true - } } }, "slice-ansi": { @@ -5636,6 +5476,15 @@ "use": "^3.1.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -5654,6 +5503,12 @@ "is-extendable": "^0.1.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -5735,9 +5590,9 @@ } }, "source-list-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", - "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", "dev": true }, "source-map": { @@ -5905,14 +5760,6 @@ "readable-stream": "^2.3.6", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" - }, - "dependencies": { - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - } } }, "stream-shift": { @@ -5963,14 +5810,6 @@ "dev": true, "requires": { "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } } }, "strip-bom": { @@ -5986,10 +5825,13 @@ "dev": true }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } }, "svelte": { "version": "2.13.5", @@ -6015,15 +5857,13 @@ } }, "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/table/-/table-5.1.0.tgz", + "integrity": "sha512-e542in22ZLhD/fOIuXs/8yDZ9W61ltF8daM88rkRNtgTIct+vI2fTnAyu/Db2TCfEcI8i7mjZz6meLq0nW7TYg==", "dev": true, "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", + "ajv": "^6.5.3", + "lodash": "^4.17.10", "slice-ansi": "1.0.0", "string-width": "^2.1.1" } @@ -6046,6 +5886,16 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, "timers-browserify": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", @@ -6065,12 +5915,6 @@ "globrex": "^0.1.1" } }, - "tippex": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tippex/-/tippex-2.3.1.tgz", - "integrity": "sha1-ov1bcIfXy/sgyYBqbBYQjCwPr9o=", - "dev": true - }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -6129,9 +5973,9 @@ } }, "trouter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/trouter/-/trouter-1.1.0.tgz", - "integrity": "sha512-EH5irPolH2hPRpgH77V8ZDS3T05oMFJ7Pcq0Vn5Dlzol4xKLhjOe4ftWnnDnHmTFzPUgUF//H6xyjv5dYAE6Kg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/trouter/-/trouter-2.0.1.tgz", + "integrity": "sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==", "dev": true, "requires": { "matchit": "^1.0.0" @@ -6188,9 +6032,9 @@ "dev": true }, "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.3.tgz", + "integrity": "sha512-+81MUSyX+BaSo+u2RbozuQk/UWx6hfG0a5gHu4ANEM4sU96XbuIyAB+rWBW1u70c6a5QuZfuYICn3s2UjuHUpA==", "dev": true }, "uglify-js": { @@ -6353,14 +6197,6 @@ "dev": true, "requires": { "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - } } }, "urix": { @@ -6418,12 +6254,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "vlq": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", - "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", - "dev": true - }, "vm-browserify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", @@ -6476,24 +6306,6 @@ "webpack-sources": "^1.3.0" }, "dependencies": { - "ajv": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", - "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", - "dev": true - }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -6535,14 +6347,13 @@ } } }, - "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "ms": "2.0.0" } }, "expand-brackets": { @@ -6673,12 +6484,6 @@ } } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -6757,12 +6562,6 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -6789,6 +6588,12 @@ "snapdragon": "^0.8.1", "to-regex": "^3.0.2" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -6859,6 +6664,12 @@ "async-limiter": "~1.0.0" } }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/package.json b/package.json index bb8d535..41de454 100644 --- a/package.json +++ b/package.json @@ -17,53 +17,53 @@ "test": "test" }, "dependencies": { - "html-minifier": "^3.5.16", + "html-minifier": "^3.5.20", "shimport": "0.0.11", - "source-map-support": "^0.5.6", - "sourcemap-codec": "^1.4.1", + "source-map-support": "^0.5.9", + "sourcemap-codec": "^1.4.3", "string-hash": "^1.1.3", - "tslib": "^1.9.1" + "tslib": "^1.9.3" }, "devDependencies": { "@types/mkdirp": "^0.5.2", "@types/mocha": "^5.2.5", - "@types/node": "^10.7.1", + "@types/node": "^10.12.0", "@types/puppeteer": "^1.9.0", "@types/rimraf": "^2.0.2", "agadoo": "^1.0.1", - "cheap-watch": "^0.3.0", + "cheap-watch": "^1.0.0", "cookie": "^0.3.1", "devalue": "^1.0.4", - "eslint": "^4.13.1", - "eslint-plugin-import": "^2.12.0", - "kleur": "^2.0.1", + "eslint": "^5.7.0", + "eslint-plugin-import": "^2.14.0", + "kleur": "^2.0.2", "mkdirp": "^0.5.1", "mocha": "^5.2.0", - "node-fetch": "^2.1.1", + "node-fetch": "^2.2.0", "npm-run-all": "^4.1.3", - "polka": "^0.4.0", + "polka": "^0.5.1", "port-authority": "^1.0.5", - "pretty-bytes": "^5.0.0", + "pretty-bytes": "^5.1.0", "puppeteer": "^1.9.0", "require-relative": "^0.8.7", "rimraf": "^2.6.2", - "rollup": "^0.65.0", - "rollup-plugin-commonjs": "^9.1.3", - "rollup-plugin-json": "^3.0.0", + "rollup": "^0.66.6", + "rollup-plugin-commonjs": "^9.2.0", + "rollup-plugin-json": "^3.1.0", "rollup-plugin-node-resolve": "^3.4.0", - "rollup-plugin-replace": "^2.0.0", + "rollup-plugin-replace": "^2.1.0", "rollup-plugin-string": "^2.0.2", "rollup-plugin-svelte": "^4.3.2", - "rollup-plugin-typescript": "^0.8.1", + "rollup-plugin-typescript": "^1.0.0", "sade": "^1.4.1", "sander": "^0.6.0", "sirv": "^0.2.2", - "svelte": "^2.6.3", - "svelte-loader": "^2.9.0", + "svelte": "^2.13.5", + "svelte-loader": "^2.11.0", "ts-node": "^7.0.1", - "typescript": "^2.8.3", - "webpack": "^4.8.3", - "webpack-format-messages": "^2.0.1" + "typescript": "^3.1.3", + "webpack": "^4.20.2", + "webpack-format-messages": "^2.0.3" }, "scripts": { "test": "mocha --opts mocha.opts", From cb45bb0fbe3e663ff11c50f664c8e81430e8c30b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 16 Oct 2018 08:58:02 -0400 Subject: [PATCH 147/178] -> v0.23.2 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5821c0..58e998b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.23.2 + +* Fix entry point CSS ([#471](https://github.com/sveltejs/sapper/pull/471)) + ## 0.23.1 * Scroll to deeplink that matches current URL ([#472](https://github.com/sveltejs/sapper/pull/472)) diff --git a/package.json b/package.json index 41de454..91b10a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.23.1", + "version": "0.23.2", "description": "Military-grade apps, engineered by Svelte", "bin": { "sapper": "./sapper" From 64e5065aa5d2ec14af85410a96d9ae8e6b6fc9cd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 16 Oct 2018 15:59:57 -0400 Subject: [PATCH 148/178] clear errors on successful render --- templates/src/client/app.ts | 2 +- test/apps/AppRunner.ts | 3 ++- .../apps/errors/src/routes/enhance-your-calm.html | 7 +++++++ test/apps/errors/src/routes/no-error.html | 3 +++ test/apps/errors/test.ts | 15 ++++++++++++++- 5 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 test/apps/errors/src/routes/enhance-your-calm.html create mode 100644 test/apps/errors/src/routes/no-error.html diff --git a/templates/src/client/app.ts b/templates/src/client/app.ts index 3773c37..43b0764 100644 --- a/templates/src/client/app.ts +++ b/templates/src/client/app.ts @@ -310,7 +310,7 @@ export function prepare_page(target: Target): Promise<{ }; } - const props = { path, query }; + const props = { path, query, error: null, status: null }; const data = { path, preloading: false, diff --git a/test/apps/AppRunner.ts b/test/apps/AppRunner.ts index 6a1a48c..077a3b7 100644 --- a/test/apps/AppRunner.ts +++ b/test/apps/AppRunner.ts @@ -58,7 +58,8 @@ export class AppRunner { start: () => this.page.evaluate(() => start()), prefetchRoutes: () => this.page.evaluate(() => prefetchRoutes()), prefetch: (href: string) => this.page.evaluate((href: string) => prefetch(href), href), - goto: (href: string) => this.page.evaluate((href: string) => goto(href), href) + goto: (href: string) => this.page.evaluate((href: string) => goto(href), href), + title: () => this.page.$eval('h1', node => node.textContent) }; } diff --git a/test/apps/errors/src/routes/enhance-your-calm.html b/test/apps/errors/src/routes/enhance-your-calm.html new file mode 100644 index 0000000..398584e --- /dev/null +++ b/test/apps/errors/src/routes/enhance-your-calm.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/test/apps/errors/src/routes/no-error.html b/test/apps/errors/src/routes/no-error.html new file mode 100644 index 0000000..8dc116a --- /dev/null +++ b/test/apps/errors/src/routes/no-error.html @@ -0,0 +1,3 @@ +

{error ? error.message : 'No error here'}

+ +
Enhance your calm \ No newline at end of file diff --git a/test/apps/errors/test.ts b/test/apps/errors/test.ts index 57a7dbc..158bbbc 100644 --- a/test/apps/errors/test.ts +++ b/test/apps/errors/test.ts @@ -14,13 +14,14 @@ describe('errors', function() { // helpers let start: () => Promise; let prefetchRoutes: () => Promise; + let title: () => Promise; // hooks before(async () => { await build({ cwd: __dirname }); runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, prefetchRoutes } = await runner.start()); + ({ base, page, start, prefetchRoutes, title } = await runner.start()); }); after(() => runner.end()); @@ -110,4 +111,16 @@ describe('errors', function() { 'oops' ); }); + + it('clears props.error on successful render', async () => { + await page.goto(`${base}/no-error`); + await start(); + await prefetchRoutes(); + + await page.click('[href="enhance-your-calm"]'); + assert.equal(await title(), '420'); + + await page.goBack(); + assert.equal(await title(), 'No error here'); + }); }); \ No newline at end of file From e5d7d8ab2b3b25430c31f8494d8c047422141166 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 16 Oct 2018 16:16:11 -0400 Subject: [PATCH 149/178] -> v0.23.3 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58e998b..de10c89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.23.3 + +* Clear `error` and `status` on successful render ([#477](https://github.com/sveltejs/sapper/pull/477)) + ## 0.23.2 * Fix entry point CSS ([#471](https://github.com/sveltejs/sapper/pull/471)) diff --git a/package.json b/package.json index 91b10a3..4094617 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.23.2", + "version": "0.23.3", "description": "Military-grade apps, engineered by Svelte", "bin": { "sapper": "./sapper" From 464924ed67b8626da24e45df50bf59ce68d5ff56 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Sat, 20 Oct 2018 22:40:21 +0200 Subject: [PATCH 150/178] handle async route errors Related to #487 --- .../src/server/middleware/get_server_route_handler.ts | 7 ++++++- test/apps/errors/src/routes/async-throw.json.js | 3 +++ test/apps/errors/test.ts | 9 +++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 test/apps/errors/src/routes/async-throw.json.js diff --git a/templates/src/server/middleware/get_server_route_handler.ts b/templates/src/server/middleware/get_server_route_handler.ts index c788f4d..8270777 100644 --- a/templates/src/server/middleware/get_server_route_handler.ts +++ b/templates/src/server/middleware/get_server_route_handler.ts @@ -53,7 +53,12 @@ export function get_server_route_handler(routes: ServerRoute[]) { }; try { - handle_method(req, res, handle_next); + const result = handle_method(req, res, handle_next); + + // catch failures in async functions + if (Promise.resolve(result) === result) { + result.catch(handle_next); + } } catch (err) { handle_next(err); } diff --git a/test/apps/errors/src/routes/async-throw.json.js b/test/apps/errors/src/routes/async-throw.json.js new file mode 100644 index 0000000..918049e --- /dev/null +++ b/test/apps/errors/src/routes/async-throw.json.js @@ -0,0 +1,3 @@ +export async function get(req, res) { + throw new Error('oops'); +} \ No newline at end of file diff --git a/test/apps/errors/test.ts b/test/apps/errors/test.ts index 158bbbc..60b6397 100644 --- a/test/apps/errors/test.ts +++ b/test/apps/errors/test.ts @@ -112,6 +112,15 @@ describe('errors', function() { ); }); + it('does not serve error page for async non-page error', async () => { + await page.goto(`${base}/async-throw.json`); + + assert.equal( + await page.evaluate(() => document.body.textContent), + 'oops' + ); + }); + it('clears props.error on successful render', async () => { await page.goto(`${base}/no-error`); await start(); From 3fe7b55955ebd924f4cbaa1f19711972b431701f Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Sat, 20 Oct 2018 22:46:42 +0200 Subject: [PATCH 151/178] async -> Promise.reject --- test/apps/errors/src/routes/async-throw.json.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/apps/errors/src/routes/async-throw.json.js b/test/apps/errors/src/routes/async-throw.json.js index 918049e..25158f7 100644 --- a/test/apps/errors/src/routes/async-throw.json.js +++ b/test/apps/errors/src/routes/async-throw.json.js @@ -1,3 +1,3 @@ -export async function get(req, res) { - throw new Error('oops'); +export function get(req, res) { + return Promise.reject(new Error('oops')); } \ No newline at end of file From 281e183c61a3b5115eaa0b745b7c941635300bde Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 24 Oct 2018 15:38:45 -0400 Subject: [PATCH 152/178] ignore empty anchors when exporting --- src/api/export.ts | 2 +- test/apps/export/src/routes/index.html | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/api/export.ts b/src/api/export.ts index 0d6d471..e2f7f5d 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -187,6 +187,6 @@ async function _export({ } function get_href(attrs: string) { - const match = /href\s*=\s*(?:"(.+?)"|'(.+?)'|([^\s>]+))/.exec(attrs); + const match = /href\s*=\s*(?:"(.*?)"|'(.+?)'|([^\s>]+))/.exec(attrs); return match[1] || match[2] || match[3]; } \ No newline at end of file diff --git a/test/apps/export/src/routes/index.html b/test/apps/export/src/routes/index.html index 37f82e0..cdeb5a8 100644 --- a/test/apps/export/src/routes/index.html +++ b/test/apps/export/src/routes/index.html @@ -1,3 +1,4 @@

Great success!

-blog \ No newline at end of file +blog +empty anchor \ No newline at end of file From ffd56e2a202f2bf00856d9961cda8d3d2f510886 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 24 Oct 2018 15:51:44 -0400 Subject: [PATCH 153/178] -> v0.23.4 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de10c89..db97647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.23.4 + +* Ignore empty anchors when exporting ([#491](https://github.com/sveltejs/sapper/pull/491)) + ## 0.23.3 * Clear `error` and `status` on successful render ([#477](https://github.com/sveltejs/sapper/pull/477)) diff --git a/package.json b/package.json index 4094617..69742ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.23.3", + "version": "0.23.4", "description": "Military-grade apps, engineered by Svelte", "bin": { "sapper": "./sapper" From 410c52df41172059cb05c175ee526dcca6e697a6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 24 Oct 2018 18:48:38 -0400 Subject: [PATCH 154/178] fix lazy css bug, add tests --- src/core/create_compilers/extract_css.ts | 2 +- test/apps/css/rollup.config.js | 64 +++++++++++++++ test/apps/css/src/client.js | 9 ++ .../css/src/routes/_components/Title.html | 7 ++ test/apps/css/src/routes/_error.html | 3 + test/apps/css/src/routes/bar.html | 11 +++ test/apps/css/src/routes/foo.html | 7 ++ test/apps/css/src/routes/index.html | 10 +++ test/apps/css/src/server.js | 8 ++ test/apps/css/src/service-worker.js | 82 +++++++++++++++++++ test/apps/css/src/template.html | 14 ++++ test/apps/css/test.ts | 78 ++++++++++++++++++ 12 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 test/apps/css/rollup.config.js create mode 100644 test/apps/css/src/client.js create mode 100644 test/apps/css/src/routes/_components/Title.html create mode 100644 test/apps/css/src/routes/_error.html create mode 100644 test/apps/css/src/routes/bar.html create mode 100644 test/apps/css/src/routes/foo.html create mode 100644 test/apps/css/src/routes/index.html create mode 100644 test/apps/css/src/server.js create mode 100644 test/apps/css/src/service-worker.js create mode 100644 test/apps/css/src/template.html create mode 100644 test/apps/css/test.ts diff --git a/src/core/create_compilers/extract_css.ts b/src/core/create_compilers/extract_css.ts index e885154..a60d848 100644 --- a/src/core/create_compilers/extract_css.ts +++ b/src/core/create_compilers/extract_css.ts @@ -219,7 +219,7 @@ export default function extract_css(client_result: CompileResult, components: Pa }); unclaimed.forEach(file => { - entry_css_modules.push(css_map.get(file)); + entry_css_modules.push(file); }); const leftover = get_css_from_modules(entry_css_modules, css_map, dirs); diff --git a/test/apps/css/rollup.config.js b/test/apps/css/rollup.config.js new file mode 100644 index 0000000..943b676 --- /dev/null +++ b/test/apps/css/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: config.client.output(), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/css/src/client.js b/test/apps/css/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/css/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/css/src/routes/_components/Title.html b/test/apps/css/src/routes/_components/Title.html new file mode 100644 index 0000000..05cf195 --- /dev/null +++ b/test/apps/css/src/routes/_components/Title.html @@ -0,0 +1,7 @@ +

Title

+ + \ No newline at end of file diff --git a/test/apps/css/src/routes/_error.html b/test/apps/css/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/css/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/css/src/routes/bar.html b/test/apps/css/src/routes/bar.html new file mode 100644 index 0000000..876ee63 --- /dev/null +++ b/test/apps/css/src/routes/bar.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/test/apps/css/src/routes/foo.html b/test/apps/css/src/routes/foo.html new file mode 100644 index 0000000..3aeeeeb --- /dev/null +++ b/test/apps/css/src/routes/foo.html @@ -0,0 +1,7 @@ +

Foo

+ + \ No newline at end of file diff --git a/test/apps/css/src/routes/index.html b/test/apps/css/src/routes/index.html new file mode 100644 index 0000000..a9fd9fd --- /dev/null +++ b/test/apps/css/src/routes/index.html @@ -0,0 +1,10 @@ +

Great success!

+ +foo +bar + + \ No newline at end of file diff --git a/test/apps/css/src/server.js b/test/apps/css/src/server.js new file mode 100644 index 0000000..0e7741c --- /dev/null +++ b/test/apps/css/src/server.js @@ -0,0 +1,8 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use(sapper.middleware()) + .listen(PORT); diff --git a/test/apps/css/src/service-worker.js b/test/apps/css/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/css/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/css/src/template.html b/test/apps/css/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/css/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/css/test.ts b/test/apps/css/test.ts new file mode 100644 index 0000000..c6539af --- /dev/null +++ b/test/apps/css/test.ts @@ -0,0 +1,78 @@ +import * as assert from 'assert'; +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import { AppRunner } from '../AppRunner'; +import { wait } from '../../utils'; + +describe('css', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + let prefetchRoutes: () => Promise; + let prefetch: (href: string) => Promise; + let goto: (href: string) => Promise; + let title: () => Promise; + + // hooks + before(async () => { + await build({ cwd: __dirname }); + + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start, prefetchRoutes, prefetch, goto, title } = await runner.start()); + }); + + after(() => runner.end()); + + it('includes critical CSS with server render', async () => { + await page.goto(base); + + assert.equal( + await page.evaluate(() => { + const h1 = document.querySelector('h1'); + return getComputedStyle(h1).color; + }), + 'rgb(255, 0, 0)' + ); + }); + + it('loads CSS when navigating client-side', async () => { + await page.goto(base); + + await start(); + await prefetchRoutes(); + + await page.click(`[href="foo"]`); + await wait(50); + + assert.equal( + await page.evaluate(() => { + const h1 = document.querySelector('h1'); + return getComputedStyle(h1).color; + }), + 'rgb(0, 0, 255)' + ); + }); + + it('loads CSS for a lazily-rendered component', async () => { + await page.goto(base); + + await start(); + await prefetchRoutes(); + + await page.click(`[href="bar"]`); + await wait(50); + + assert.equal( + await page.evaluate(() => { + const h1 = document.querySelector('h1'); + return getComputedStyle(h1).color; + }), + 'rgb(0, 128, 0)' + ); + }); +}); \ No newline at end of file From c4aee66c3297985907521f70e993a93a529add1c Mon Sep 17 00:00:00 2001 From: mrkishi Date: Wed, 24 Oct 2018 21:19:03 -0300 Subject: [PATCH 155/178] Fix search params decoding --- templates/src/client/app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/src/client/app.ts b/templates/src/client/app.ts index 43b0764..ed46630 100644 --- a/templates/src/client/app.ts +++ b/templates/src/client/app.ts @@ -90,8 +90,8 @@ export function select_route(url: URL): Target { const query: Record = {}; if (url.search.length > 0) { url.search.slice(1).split('&').forEach(searchParam => { - const [, key, value] = /([^=]+)(?:=(.*))?/.exec(searchParam); - query[key] = decodeURIComponent((value || '').replace(/\+/g, ' ')); + const [, key, value] = /([^=]*)(?:=(.*))?/.exec(searchParam); + query[decodeURIComponent(key)] = decodeURIComponent((value || '').replace(/\+/g, ' ')); }); } return { url, path, page, match, query }; From 4991f3b359646650e09221617a430e71cb362efd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 24 Oct 2018 21:05:25 -0400 Subject: [PATCH 156/178] support non-native promise implementations --- .../src/server/middleware/get_server_route_handler.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/templates/src/server/middleware/get_server_route_handler.ts b/templates/src/server/middleware/get_server_route_handler.ts index 8270777..1e2f3db 100644 --- a/templates/src/server/middleware/get_server_route_handler.ts +++ b/templates/src/server/middleware/get_server_route_handler.ts @@ -2,7 +2,7 @@ import { IGNORE } from '../placeholders'; import { Req, Res, ServerRoute } from './types'; export function get_server_route_handler(routes: ServerRoute[]) { - function handle_route(route: ServerRoute, req: Req, res: Res, next: () => void) { + async function handle_route(route: ServerRoute, req: Req, res: Res, next: () => void) { req.params = route.params(route.pattern.exec(req.path)); const method = req.method.toLowerCase(); @@ -53,12 +53,7 @@ export function get_server_route_handler(routes: ServerRoute[]) { }; try { - const result = handle_method(req, res, handle_next); - - // catch failures in async functions - if (Promise.resolve(result) === result) { - result.catch(handle_next); - } + await handle_method(req, res, handle_next); } catch (err) { handle_next(err); } From 28186227a95038d60fd8ec4572b8aacc5faf0e0f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 24 Oct 2018 21:20:27 -0400 Subject: [PATCH 157/178] add tests --- test/apps/encoding/src/routes/echo/page/[slug].html | 7 +++---- test/apps/encoding/src/routes/index.html | 2 +- test/apps/encoding/test.ts | 8 ++++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/test/apps/encoding/src/routes/echo/page/[slug].html b/test/apps/encoding/src/routes/echo/page/[slug].html index 7b2f0a8..2cdc03d 100644 --- a/test/apps/encoding/src/routes/echo/page/[slug].html +++ b/test/apps/encoding/src/routes/echo/page/[slug].html @@ -1,11 +1,10 @@ -

{slug} ({message})

+

{slug} {JSON.stringify(query)}

\ No newline at end of file +

{JSON.stringify(query)}

\ No newline at end of file diff --git a/test/apps/basics/src/routes/index.html b/test/apps/basics/src/routes/index.html index 221c0f5..b61b244 100644 --- a/test/apps/basics/src/routes/index.html +++ b/test/apps/basics/src/routes/index.html @@ -3,5 +3,6 @@ a ok ok +ok
\ No newline at end of file diff --git a/test/apps/basics/test.ts b/test/apps/basics/test.ts index 7190fd9..f7e1ac9 100644 --- a/test/apps/basics/test.ts +++ b/test/apps/basics/test.ts @@ -204,7 +204,7 @@ describe('basics', function() { assert.equal( await title(), - 'message: ""' + '{"message":""}' ); }); @@ -217,7 +217,29 @@ describe('basics', function() { assert.equal( await title(), - 'message: ""' + '{"message":""}' + ); + }); + + it('accepts duplicated query string parameter on server', async () => { + await page.goto(`${base}/echo-query?p=one&p=two`); + + assert.equal( + await title(), + '{"p":["one","two"]}' + ); + }); + + it('accepts duplicated query string parameter on client', async () => { + await page.goto(base); + await start(); + await prefetchRoutes(); + + await page.click('a[href="echo-query?p=one&p=two"]') + + assert.equal( + await title(), + '{"p":["one","two"]}' ); }); From f50f83c4a481da8c3e12e0502d5dc4a11c2c3fda Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 27 Oct 2018 11:41:02 -0400 Subject: [PATCH 163/178] strip leading slash from basepath - fixes #495 --- src/api/export.ts | 2 ++ test/apps/with-basepath/test.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/export.ts b/src/api/export.ts index e2f7f5d..482ba51 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -38,6 +38,8 @@ async function _export({ oninfo = noop, onfile = noop }: Opts = {}) { + basepath = basepath.replace(/^\//, '') + cwd = path.resolve(cwd); static_files = path.resolve(cwd, static_files); build_dir = path.resolve(cwd, build_dir); diff --git a/test/apps/with-basepath/test.ts b/test/apps/with-basepath/test.ts index f535b03..93bbcbf 100644 --- a/test/apps/with-basepath/test.ts +++ b/test/apps/with-basepath/test.ts @@ -17,7 +17,7 @@ describe('with-basepath', function() { await api.export({ cwd: __dirname, - basepath: 'custom-basepath' + basepath: '/custom-basepath' }); runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); From e69cb3639adc97d61fa878f454f22693093c98f6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 27 Oct 2018 12:14:28 -0400 Subject: [PATCH 164/178] redirect to external URLs - closes #490 --- .../src/server/middleware/get_page_handler.ts | 4 +- test/apps/AppRunner.ts | 14 ++++++ test/apps/redirects/src/routes/index.html | 3 +- .../src/routes/redirect-to-external.html | 9 ++++ test/apps/redirects/test.ts | 44 ++++++++++++++++--- 5 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 test/apps/redirects/src/routes/redirect-to-external.html diff --git a/templates/src/server/middleware/get_page_handler.ts b/templates/src/server/middleware/get_page_handler.ts index c2049f2..78eb559 100644 --- a/templates/src/server/middleware/get_page_handler.ts +++ b/templates/src/server/middleware/get_page_handler.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import cookie from 'cookie'; import devalue from 'devalue'; import fetch from 'node-fetch'; -import { URL } from 'url'; +import { URL, resolve } from 'url'; import { build_dir, dev, src_dir, IGNORE } from '../placeholders'; import { Manifest, Page, Props, Req, Res, Store } from './types'; @@ -160,7 +160,7 @@ export function get_page_handler( try { if (redirect) { - const location = `${req.baseUrl}/${redirect.location}`; + const location = resolve(req.baseUrl || '/', redirect.location); res.statusCode = redirect.statusCode; res.setHeader('Location', location); diff --git a/test/apps/AppRunner.ts b/test/apps/AppRunner.ts index 077a3b7..0965329 100644 --- a/test/apps/AppRunner.ts +++ b/test/apps/AppRunner.ts @@ -50,6 +50,20 @@ export class AppRunner { } }); + await this.page.setRequestInterception(true); + + this.page.on('request', interceptedRequest => { + if (/example\.com/.test(interceptedRequest.url())) { + interceptedRequest.respond({ + status: 200, + contentType: 'text/html', + body: `

external

` + }); + } else { + interceptedRequest.continue(); + } + }); + return { page: this.page, base: `http://localhost:${this.port}`, diff --git a/test/apps/redirects/src/routes/index.html b/test/apps/redirects/src/routes/index.html index 6a94654..804a3dc 100644 --- a/test/apps/redirects/src/routes/index.html +++ b/test/apps/redirects/src/routes/index.html @@ -1,4 +1,5 @@

root

redirect from -redirect to root \ No newline at end of file +redirect to root +redirect to external \ No newline at end of file diff --git a/test/apps/redirects/src/routes/redirect-to-external.html b/test/apps/redirects/src/routes/redirect-to-external.html new file mode 100644 index 0000000..fec2fee --- /dev/null +++ b/test/apps/redirects/src/routes/redirect-to-external.html @@ -0,0 +1,9 @@ +

unredirected

+ + \ No newline at end of file diff --git a/test/apps/redirects/test.ts b/test/apps/redirects/test.ts index 8054924..719a92c 100644 --- a/test/apps/redirects/test.ts +++ b/test/apps/redirects/test.ts @@ -14,13 +14,14 @@ describe('redirects', function() { // helpers let start: () => Promise; let prefetchRoutes: () => Promise; + let title: () => Promise; // hooks before(async () => { await build({ cwd: __dirname }); runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, prefetchRoutes } = await runner.start()); + ({ base, page, start, prefetchRoutes, title } = await runner.start()); }); after(() => runner.end()); @@ -34,7 +35,7 @@ describe('redirects', function() { ); assert.equal( - await page.$eval('h1', node => node.textContent), + await title(), 'redirected' ); }); @@ -53,7 +54,7 @@ describe('redirects', function() { ); assert.equal( - await page.$eval('h1', node => node.textContent), + await title(), 'redirected' ); }); @@ -67,7 +68,7 @@ describe('redirects', function() { ); assert.equal( - await page.$eval('h1', node => node.textContent), + await title(), 'root' ); }); @@ -86,8 +87,41 @@ describe('redirects', function() { ); assert.equal( - await page.$eval('h1', node => node.textContent), + await title(), 'root' ); }); + + it('redirects to external URL on server', async () => { + await page.goto(`${base}/redirect-to-external`); + + assert.equal( + page.url(), + `https://example.com/` + ); + + assert.equal( + await title(), + 'external' + ); + }); + + it('redirects to external URL in client', async () => { + await page.goto(base); + await start(); + await prefetchRoutes(); + + await page.click('[href="redirect-to-external"]'); + await wait(50); + + assert.equal( + page.url(), + `https://example.com/` + ); + + assert.equal( + await title(), + 'external' + ); + }); }); \ No newline at end of file From 4f720446b26d2e784c8ccee00999e3cbf5c086b3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 27 Oct 2018 12:26:53 -0400 Subject: [PATCH 165/178] change scroll[X|Y] to page[X|Y]Offset - closes #480 --- templates/src/client/app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/src/client/app.ts b/templates/src/client/app.ts index ed46630..512efb0 100644 --- a/templates/src/client/app.ts +++ b/templates/src/client/app.ts @@ -101,8 +101,8 @@ export function select_route(url: URL): Target { export function scroll_state() { return { - x: scrollX, - y: scrollY + x: pageXOffset, + y: pageYOffset }; } From 03c5f5b446705c674f07ebedff40e106ed6ec0eb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 27 Oct 2018 12:40:27 -0400 Subject: [PATCH 166/178] -> v0.24.0 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78db423..b0b5d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # sapper changelog +## 0.24.0 + +* Handle external URLs in `this.redirect` ([#490](https://github.com/sveltejs/sapper/issues/490)) +* Strip leading `/` from basepath ([#495](https://github.com/sveltejs/sapper/issues/495)) +* Treat duplicate query string parameters as arrays ([#497](https://github.com/sveltejs/sapper/issues/497)) +* Don't buffer `stdout` and `stderr` ([#305](https://github.com/sveltejs/sapper/issues/305)) +* Posixify `build_dir` ([#498](https://github.com/sveltejs/sapper/pull/498)) +* Use `page[XY]Offset` instead of `scroll[XY]` ([#480](https://github.com/sveltejs/sapper/issues/480)) + ## 0.23.5 * Include lazily-imported CSS in main CSS chunk ([#492](https://github.com/sveltejs/sapper/pull/492)) diff --git a/package.json b/package.json index 0862183..deeb2af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.23.5", + "version": "0.24.0", "description": "Military-grade apps, engineered by Svelte", "bin": { "sapper": "./sapper" From 14e809af6efb72b2d190abb7ec710a045b929fb8 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 6 Dec 2018 07:02:04 -0500 Subject: [PATCH 167/178] update npm-run-all to remove malicious flatmap-stream@0.1.1 --- package-lock.json | 127 +++++++++++++--------------------------------- package.json | 2 +- 2 files changed, 36 insertions(+), 93 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5b955dc..b6b895e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.23.1", + "version": "0.24.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1702,12 +1702,6 @@ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, - "duplexer": { - "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", - "dev": true - }, "duplexify": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", @@ -2043,22 +2037,6 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, - "event-stream": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz", - "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==", - "dev": true, - "requires": { - "duplexer": "^0.1.1", - "flatmap-stream": "^0.1.0", - "from": "^0.1.7", - "map-stream": "0.0.7", - "pause-stream": "^0.0.11", - "split": "^1.0.1", - "stream-combiner": "^0.2.2", - "through": "^2.3.8" - } - }, "events": { "version": "1.1.1", "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -2281,12 +2259,6 @@ "write": "^0.2.1" } }, - "flatmap-stream": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/flatmap-stream/-/flatmap-stream-0.1.1.tgz", - "integrity": "sha512-lAq4tLbm3sidmdCN8G3ExaxH7cUCtP5mgDvrYowsx84dcYkJJ4I28N7gkxA6+YlSXzaGLJYIDEi9WGfXzMiXdw==", - "dev": true - }, "flush-write-stream": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", @@ -2321,12 +2293,6 @@ "map-cache": "^0.2.2" } }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -2396,12 +2362,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2416,17 +2384,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2543,7 +2514,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2555,6 +2527,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2569,6 +2542,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2576,12 +2550,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -2600,6 +2576,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2680,7 +2657,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2692,6 +2670,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2813,6 +2792,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3648,12 +3628,6 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, - "map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", - "dev": true - }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -4059,17 +4033,17 @@ } }, "npm-run-all": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.3.tgz", - "integrity": "sha512-aOG0N3Eo/WW+q6sUIdzcV2COS8VnTZCmdji0VQIAZF3b+a3YWb0AD0vFIyjKec18A7beLGbaQ5jFTNI2bPt9Cg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.4", + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", "memorystream": "^0.3.1", "minimatch": "^3.0.4", - "ps-tree": "^1.1.0", + "pidtree": "^0.3.0", "read-pkg": "^3.0.0", "shell-quote": "^1.6.1", "string.prototype.padend": "^3.0.0" @@ -4399,15 +4373,6 @@ "pify": "^2.0.0" } }, - "pause-stream": { - "version": "0.0.11", - "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "requires": { - "through": "~2.3" - } - }, "pbkdf2": { "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", @@ -4427,6 +4392,12 @@ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", "dev": true }, + "pidtree": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz", + "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -4539,15 +4510,6 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, - "ps-tree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", - "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", - "dev": true, - "requires": { - "event-stream": "~3.3.0" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -5665,15 +5627,6 @@ "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", "dev": true }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "requires": { - "through": "2" - } - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -5729,16 +5682,6 @@ "readable-stream": "^2.0.2" } }, - "stream-combiner": { - "version": "0.2.2", - "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", - "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "through": "~2.3.4" - } - }, "stream-each": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", diff --git a/package.json b/package.json index deeb2af..31a9f1c 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "mkdirp": "^0.5.1", "mocha": "^5.2.0", "node-fetch": "^2.2.0", - "npm-run-all": "^4.1.3", + "npm-run-all": "^4.1.5", "polka": "^0.5.1", "port-authority": "^1.0.5", "pretty-bytes": "^5.1.0", From af2a7925086af3770a8a98aeefe64910861b8a87 Mon Sep 17 00:00:00 2001 From: Artyom Stepanishchev Date: Fri, 7 Dec 2018 23:28:28 +0300 Subject: [PATCH 168/178] Added css build info for webpack apps --- src/core/create_compilers/WebpackResult.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/core/create_compilers/WebpackResult.ts b/src/core/create_compilers/WebpackResult.ts index 4b015a6..c014f11 100644 --- a/src/core/create_compilers/WebpackResult.ts +++ b/src/core/create_compilers/WebpackResult.ts @@ -55,14 +55,25 @@ export default class WebpackResult implements CompileResult { } to_json(manifest_data: ManifestData, dirs: Dirs): BuildInfo { + const extract_css = (assets: string[] | string) => { + assets = Array.isArray(assets) ? assets : [assets]; + return assets.find(asset => /\.css$/.test(asset)); + }; + return { bundler: 'webpack', shimport: null, // webpack has its own loader assets: this.assets, css: { - // TODO - main: null, - chunks: {} + main: extract_css(this.assets.main), + chunks: Object + .keys(this.assets) + .filter(chunkName => chunkName !== 'main') + .reduce((chunks: { [key: string]: string }, chukName) => { + const assets = this.assets[chukName]; + chunks[chukName] = extract_css(assets); + return chunks; + }, {}) } }; } From 8ebfcc9a54f2d3a58e1a67c8f87dc1e19c0cb84f Mon Sep 17 00:00:00 2001 From: Artyom Stepanishchev Date: Fri, 7 Dec 2018 23:59:51 +0300 Subject: [PATCH 169/178] Fixed rel="preload" `as` attribute for styles --- templates/src/server/middleware/get_page_handler.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/templates/src/server/middleware/get_page_handler.ts b/templates/src/server/middleware/get_page_handler.ts index 78eb559..802ee0f 100644 --- a/templates/src/server/middleware/get_page_handler.ts +++ b/templates/src/server/middleware/get_page_handler.ts @@ -67,7 +67,10 @@ export function get_page_handler( } else { const link = preloaded_chunks .filter(file => file && !file.match(/\.map$/)) - .map(file => `<${req.baseUrl}/client/${file}>;rel="preload";as="script"`) + .map((file) => { + const as = /\.css$/.test(file) ? 'style' : 'script'; + return `<${req.baseUrl}/client/${file}>;rel="preload";as="${as}"`; + }) .join(', '); res.setHeader('Link', link); @@ -335,4 +338,4 @@ function escape_html(html: string) { }; return html.replace(/["'&<>]/g, c => `&${chars[c]};`); -} \ No newline at end of file +} From ed19a19fedc124d3f067aae986074a2ff54c8ba4 Mon Sep 17 00:00:00 2001 From: Artyom Stepanishchev Date: Sun, 9 Dec 2018 17:28:59 +0300 Subject: [PATCH 170/178] Fixed tests for node 6 --- test/apps/AppRunner.ts | 23 +++++++++-------------- test/apps/redirects/test.ts | 14 +++++++++++++- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/test/apps/AppRunner.ts b/test/apps/AppRunner.ts index 0965329..84dd6b6 100644 --- a/test/apps/AppRunner.ts +++ b/test/apps/AppRunner.ts @@ -8,6 +8,10 @@ declare const prefetchRoutes: () => Promise; declare const prefetch: (href: string) => Promise; declare const goto: (href: string) => Promise; +type StartOpts = { + requestInterceptor?: (interceptedRequst: puppeteer.Request) => any +}; + export class AppRunner { cwd: string; entry: string; @@ -24,7 +28,7 @@ export class AppRunner { this.messages = []; } - async start() { + async start({ requestInterceptor }: StartOpts = {}) { this.port = await ports.find(3000); this.proc = fork(this.entry, [], { @@ -50,19 +54,10 @@ export class AppRunner { } }); - await this.page.setRequestInterception(true); - - this.page.on('request', interceptedRequest => { - if (/example\.com/.test(interceptedRequest.url())) { - interceptedRequest.respond({ - status: 200, - contentType: 'text/html', - body: `

external

` - }); - } else { - interceptedRequest.continue(); - } - }); + if (requestInterceptor) { + await this.page.setRequestInterception(true); + this.page.on('request', requestInterceptor); + } return { page: this.page, diff --git a/test/apps/redirects/test.ts b/test/apps/redirects/test.ts index 719a92c..50d6507 100644 --- a/test/apps/redirects/test.ts +++ b/test/apps/redirects/test.ts @@ -21,7 +21,19 @@ describe('redirects', function() { await build({ cwd: __dirname }); runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, prefetchRoutes, title } = await runner.start()); + ({ base, page, start, prefetchRoutes, title } = await runner.start({ + requestInterceptor: (interceptedRequest) => { + if (/example\.com/.test(interceptedRequest.url())) { + interceptedRequest.respond({ + status: 200, + contentType: 'text/html', + body: `

external

` + }); + } else { + interceptedRequest.continue(); + } + } + })); }); after(() => runner.end()); From 02cef046aa490accb0c30582ac36829f35fa89a8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 9 Dec 2018 10:30:34 -0500 Subject: [PATCH 171/178] -> v0.24.1 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0b5d8f..d692da9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # sapper changelog +## 0.24.1 + +* Include CSS chunks in webpack build info to avoid duplication ([#529](https://github.com/sveltejs/sapper/pull/529)) +* Fix preload `as` for styles ([#530](https://github.com/sveltejs/sapper/pull/530)) + ## 0.24.0 * Handle external URLs in `this.redirect` ([#490](https://github.com/sveltejs/sapper/issues/490)) diff --git a/package.json b/package.json index 31a9f1c..d753c9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.24.0", + "version": "0.24.1", "description": "Military-grade apps, engineered by Svelte", "bin": { "sapper": "./sapper" From 03af9b1a167a8513af44bdf04f27912f91daf956 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sat, 1 Dec 2018 15:48:39 -0800 Subject: [PATCH 172/178] add service-worker-index.html fixes #422 --- src/api/export.ts | 12 +++- src/core/create_manifests.ts | 6 +- .../src/server/middleware/get_page_handler.ts | 68 ++++++++++++------- test/apps/export/test.ts | 1 + test/apps/with-basepath/test.ts | 1 + 5 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/api/export.ts b/src/api/export.ts index 482ba51..6129eaf 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -94,7 +94,9 @@ async function _export({ const is_html = type === 'text/html'; if (is_html) { - file = file === '' ? 'index.html' : `${file}/index.html`; + if (pathname !== '/service-worker-index.html') { + file = file === '' ? 'index.html' : `${file}/index.html`; + } body = minify_html(body); } @@ -113,7 +115,10 @@ async function _export({ }); async function handle(url: URL) { - const pathname = (url.pathname.replace(root.pathname, '') || '/'); + let pathname = url.pathname; + if (pathname !== '/service-worker-index.html') { + pathname = pathname.replace(root.pathname, '') || '/' + } if (seen.has(pathname)) return; seen.add(pathname); @@ -138,7 +143,7 @@ async function _export({ const range = ~~(r.status / 100); if (range === 2) { - if (type === 'text/html') { + if (type === 'text/html' && pathname !== '/service-worker-index.html') { const urls: URL[] = []; const cleaned = clean_html(body); @@ -181,6 +186,7 @@ async function _export({ return ports.wait(port) .then(() => handle(root)) + .then(() => handle(resolve(root.href, 'service-worker-index.html'))) .then(() => proc.kill()) .catch(err => { proc.kill(); diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index 3ff7a95..ebe4cf4 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -45,17 +45,15 @@ export function create_serviceworker_manifest({ manifest_data, output, client_fi client_files: string[]; static_files: string; }) { - let files: string[]; + let files: string[] = ['/service-worker-index.html']; if (fs.existsSync(static_files)) { - files = walk(static_files); + files = files.concat(walk(static_files)); } else { // TODO remove in a future version if (fs.existsSync('assets')) { throw new Error(`As of Sapper 0.21, the assets/ directory should become static/`); } - - files = []; } let code = ` diff --git a/templates/src/server/middleware/get_page_handler.ts b/templates/src/server/middleware/get_page_handler.ts index 802ee0f..67405ba 100644 --- a/templates/src/server/middleware/get_page_handler.ts +++ b/templates/src/server/middleware/get_page_handler.ts @@ -34,6 +34,7 @@ export function get_page_handler( } async function handle_page(page: Page, req: Req, res: Res, status = 200, error: Error | string = null) { + const isSWIndexHtml = req.path === '/service-worker-index.html'; const build_info: { bundler: 'rollup' | 'webpack', shimport: string | null, @@ -47,7 +48,7 @@ export function get_page_handler( // preload main.js and current route // TODO detect other stuff we can preload? images, CSS, fonts? let preloaded_chunks = Array.isArray(build_info.assets.main) ? build_info.assets.main : [build_info.assets.main]; - if (!error) { + if (!error && !isSWIndexHtml) { page.parts.forEach(part => { if (!part) return; @@ -145,17 +146,22 @@ export function get_page_handler( match = error ? null : page.pattern.exec(req.path); - preloaded = await Promise.all([root_preloaded].concat(page.parts.map(part => { - if (!part) return null; + let toPreload = [root_preloaded]; + if (!isSWIndexHtml) { + toPreload = toPreload.concat(page.parts.map(part => { + if (!part) return null; - return part.component.preload - ? part.component.preload.call(preload_context, { - path: req.path, - query: req.query, - params: part.params ? part.params(match) : {} - }) - : {}; - }))); + return part.component.preload + ? part.component.preload.call(preload_context, { + path: req.path, + query: req.query, + params: part.params ? part.params(match) : {} + }) + : {}; + })) + } + + preloaded = await Promise.all(toPreload); } catch (err) { preload_error = { statusCode: 500, message: err }; preloaded = []; // appease TypeScript @@ -204,23 +210,29 @@ export function get_page_handler( }); let level = data.child; - for (let i = 0; i < page.parts.length; i += 1) { - const part = page.parts[i]; - if (!part) continue; + if (isSWIndexHtml) { + level.props = Object.assign({}, props, { + params: {} + }) + } else { + for (let i = 0; i < page.parts.length; i += 1) { + const part = page.parts[i]; + if (!part) continue; - const get_params = part.params || (() => ({})); + const get_params = part.params || (() => ({})); - Object.assign(level, { - component: part.component, - props: Object.assign({}, props, { - params: get_params(match) - }, preloaded[i + 1]) - }); + Object.assign(level, { + component: part.component, + props: Object.assign({}, props, { + params: get_params(match) + }, preloaded[i + 1]) + }); - level.props.child = { - segment: segments[i + 1] - }; - level = level.props.child; + level.props.child = { + segment: segments[i + 1] + }; + level = level.props.child; + } } const { html, head, css } = manifest.root.render(data, { @@ -303,6 +315,12 @@ export function get_page_handler( return function find_route(req: Req, res: Res, next: () => void) { if (req[IGNORE]) return next(); + if (req.path === '/service-worker-index.html') { + const homePage = pages.find(page => page.pattern.test('/')); + handle_page(homePage, req, res); + return; + } + if (!server_routes.some(route => route.pattern.test(req.path))) { for (const page of pages) { if (page.pattern.test(req.path)) { diff --git a/test/apps/export/test.ts b/test/apps/export/test.ts index 9bb8339..897bc5f 100644 --- a/test/apps/export/test.ts +++ b/test/apps/export/test.ts @@ -30,6 +30,7 @@ describe('export', function() { 'blog/index.html', 'global.css', 'index.html', + 'service-worker-index.html', 'service-worker.js' ]); }); diff --git a/test/apps/with-basepath/test.ts b/test/apps/with-basepath/test.ts index 93bbcbf..5741d1b 100644 --- a/test/apps/with-basepath/test.ts +++ b/test/apps/with-basepath/test.ts @@ -56,6 +56,7 @@ describe('with-basepath', function() { assert.deepEqual(non_client_assets, [ 'custom-basepath/global.css', 'custom-basepath/index.html', + 'custom-basepath/service-worker-index.html', 'custom-basepath/service-worker.js' ]); }); From f97400caaa5126ff6c4f7dd4495149dce8fdeeff Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 16 Dec 2018 11:46:44 -0800 Subject: [PATCH 173/178] Do not include sourcemap files in Service Worker shell Fixes #534 --- src/api/build.ts | 7 +- .../with-sourcemaps-webpack/src/client.js | 9 ++ .../src/routes/_error.html | 3 + .../src/routes/index.html | 3 + .../with-sourcemaps-webpack/src/server.js | 8 ++ .../src/service-worker.js | 82 +++++++++++++++++++ .../with-sourcemaps-webpack/src/template.html | 14 ++++ test/apps/with-sourcemaps-webpack/test.ts | 43 ++++++++++ .../with-sourcemaps-webpack/webpack.config.js | 73 +++++++++++++++++ test/apps/with-sourcemaps/rollup.config.js | 64 +++++++++++++++ test/apps/with-sourcemaps/src/client.js | 9 ++ .../with-sourcemaps/src/routes/_error.html | 3 + .../with-sourcemaps/src/routes/index.html | 3 + test/apps/with-sourcemaps/src/server.js | 8 ++ .../with-sourcemaps/src/service-worker.js | 82 +++++++++++++++++++ test/apps/with-sourcemaps/src/template.html | 14 ++++ test/apps/with-sourcemaps/test.ts | 43 ++++++++++ 17 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 test/apps/with-sourcemaps-webpack/src/client.js create mode 100644 test/apps/with-sourcemaps-webpack/src/routes/_error.html create mode 100644 test/apps/with-sourcemaps-webpack/src/routes/index.html create mode 100644 test/apps/with-sourcemaps-webpack/src/server.js create mode 100644 test/apps/with-sourcemaps-webpack/src/service-worker.js create mode 100644 test/apps/with-sourcemaps-webpack/src/template.html create mode 100644 test/apps/with-sourcemaps-webpack/test.ts create mode 100644 test/apps/with-sourcemaps-webpack/webpack.config.js create mode 100644 test/apps/with-sourcemaps/rollup.config.js create mode 100644 test/apps/with-sourcemaps/src/client.js create mode 100644 test/apps/with-sourcemaps/src/routes/_error.html create mode 100644 test/apps/with-sourcemaps/src/routes/index.html create mode 100644 test/apps/with-sourcemaps/src/server.js create mode 100644 test/apps/with-sourcemaps/src/service-worker.js create mode 100644 test/apps/with-sourcemaps/src/template.html create mode 100644 test/apps/with-sourcemaps/test.ts diff --git a/src/api/build.ts b/src/api/build.ts index ac563de..73680b2 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -116,10 +116,15 @@ export async function build({ let serviceworker_stats; if (serviceworker) { + + const client_files = client_result.chunks + .filter(chunk => !chunk.file.endsWith('.map')) // SW does not need to cache sourcemap files + .map(chunk => `client/${chunk.file}`); + create_serviceworker_manifest({ manifest_data, output, - client_files: client_result.chunks.map(chunk => `client/${chunk.file}`), + client_files, static_files }); diff --git a/test/apps/with-sourcemaps-webpack/src/client.js b/test/apps/with-sourcemaps-webpack/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/with-sourcemaps-webpack/src/routes/_error.html b/test/apps/with-sourcemaps-webpack/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/with-sourcemaps-webpack/src/routes/index.html b/test/apps/with-sourcemaps-webpack/src/routes/index.html new file mode 100644 index 0000000..abaff72 --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/routes/index.html @@ -0,0 +1,3 @@ +

Great success!

+ +

Woot!

\ No newline at end of file diff --git a/test/apps/with-sourcemaps-webpack/src/server.js b/test/apps/with-sourcemaps-webpack/src/server.js new file mode 100644 index 0000000..0e7741c --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/server.js @@ -0,0 +1,8 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use(sapper.middleware()) + .listen(PORT); diff --git a/test/apps/with-sourcemaps-webpack/src/service-worker.js b/test/apps/with-sourcemaps-webpack/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/with-sourcemaps-webpack/src/template.html b/test/apps/with-sourcemaps-webpack/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/with-sourcemaps-webpack/test.ts b/test/apps/with-sourcemaps-webpack/test.ts new file mode 100644 index 0000000..cb0a3ca --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/test.ts @@ -0,0 +1,43 @@ +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import * as assert from "assert"; +import { AppRunner } from '../AppRunner'; +import * as fs from "fs"; +import * as path from "path"; + +describe('with-sourcemaps-webpack', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + let prefetchRoutes: () => Promise; + let prefetch: (href: string) => Promise; + let goto: (href: string) => Promise; + + // hooks + before(async () => { + await build({ cwd: __dirname, bundler: 'webpack' }); + + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start, prefetchRoutes, prefetch, goto } = await runner.start()); + }); + + it('does not put sourcemap files in service worker shell', async () => { + const serviceWorker = await import(`${__dirname}/__sapper__/service-worker.js`); + const shell: string[] = serviceWorker.shell; + + assert.equal(shell.filter(_ => _.endsWith('.map')).length, 0, + 'sourcemap files are not cached in SW'); + + const clientShellDir = path.resolve(`${__dirname}/__sapper__/build`, path.dirname(shell[0])); + const sourcemapFiles = fs.readdirSync(clientShellDir).filter(_ => _.endsWith('.map')); + assert.ok(sourcemapFiles.length > 0, 'sourcemap files exist'); + }); + + after(() => runner.end()); + +}); \ No newline at end of file diff --git a/test/apps/with-sourcemaps-webpack/webpack.config.js b/test/apps/with-sourcemaps-webpack/webpack.config.js new file mode 100644 index 0000000..40b612d --- /dev/null +++ b/test/apps/with-sourcemaps-webpack/webpack.config.js @@ -0,0 +1,73 @@ +const webpack = require('webpack'); +const config = require('../../../config/webpack.js'); + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +module.exports = { + client: { + entry: config.client.entry(), + output: config.client.output(), + resolve: { + extensions: ['.js', '.json', '.html'], + mainFields: ['svelte', 'module', 'browser', 'main'] + }, + module: { + rules: [ + { + test: /\.html$/, + use: { + loader: 'svelte-loader', + options: { + dev, + hydratable: true, + hotReload: true + } + } + } + ] + }, + mode, + plugins: [ + dev && new webpack.HotModuleReplacementPlugin(), + new webpack.DefinePlugin({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + ].filter(Boolean), + devtool: dev ? 'inline-source-map' : 'source-map' + }, + + server: { + entry: config.server.entry(), + output: config.server.output(), + target: 'node', + resolve: { + extensions: ['.js', '.json', '.html'], + mainFields: ['svelte', 'module', 'browser', 'main'] + }, + module: { + rules: [ + { + test: /\.html$/, + use: { + loader: 'svelte-loader', + options: { + css: false, + generate: 'ssr', + dev + } + } + } + ] + }, + mode: process.env.NODE_ENV + }, + + serviceworker: { + entry: config.serviceworker.entry(), + output: config.serviceworker.output(), + mode: process.env.NODE_ENV, + devtool: 'sourcemap' + } +}; diff --git a/test/apps/with-sourcemaps/rollup.config.js b/test/apps/with-sourcemaps/rollup.config.js new file mode 100644 index 0000000..c057e2e --- /dev/null +++ b/test/apps/with-sourcemaps/rollup.config.js @@ -0,0 +1,64 @@ +import resolve from 'rollup-plugin-node-resolve'; +import replace from 'rollup-plugin-replace'; +import svelte from 'rollup-plugin-svelte'; + +const mode = process.env.NODE_ENV; +const dev = mode === 'development'; + +const config = require('../../../config/rollup.js'); + +export default { + client: { + input: config.client.input(), + output: Object.assign({}, config.client.output(), { sourcemap: true }), + plugins: [ + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + dev, + hydratable: true, + emitCss: true + }), + resolve() + ], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + server: { + input: config.server.input(), + output: config.server.output(), + plugins: [ + replace({ + 'process.browser': false, + 'process.env.NODE_ENV': JSON.stringify(mode) + }), + svelte({ + generate: 'ssr', + dev + }), + resolve({ + preferBuiltins: true + }) + ], + external: ['sirv', 'polka'], + + // temporary, pending Rollup 1.0 + experimentalCodeSplitting: true + }, + + serviceworker: { + input: config.serviceworker.input(), + output: config.serviceworker.output(), + plugins: [ + resolve(), + replace({ + 'process.browser': true, + 'process.env.NODE_ENV': JSON.stringify(mode) + }) + ] + } +}; \ No newline at end of file diff --git a/test/apps/with-sourcemaps/src/client.js b/test/apps/with-sourcemaps/src/client.js new file mode 100644 index 0000000..0865a4a --- /dev/null +++ b/test/apps/with-sourcemaps/src/client.js @@ -0,0 +1,9 @@ +import * as sapper from '../__sapper__/client.js'; + +window.start = () => sapper.start({ + target: document.querySelector('#sapper') +}); + +window.prefetchRoutes = () => sapper.prefetchRoutes(); +window.prefetch = href => sapper.prefetch(href); +window.goto = href => sapper.goto(href); \ No newline at end of file diff --git a/test/apps/with-sourcemaps/src/routes/_error.html b/test/apps/with-sourcemaps/src/routes/_error.html new file mode 100644 index 0000000..4cd55d2 --- /dev/null +++ b/test/apps/with-sourcemaps/src/routes/_error.html @@ -0,0 +1,3 @@ +

{status}

+ +

{error.message}

\ No newline at end of file diff --git a/test/apps/with-sourcemaps/src/routes/index.html b/test/apps/with-sourcemaps/src/routes/index.html new file mode 100644 index 0000000..abaff72 --- /dev/null +++ b/test/apps/with-sourcemaps/src/routes/index.html @@ -0,0 +1,3 @@ +

Great success!

+ +

Woot!

\ No newline at end of file diff --git a/test/apps/with-sourcemaps/src/server.js b/test/apps/with-sourcemaps/src/server.js new file mode 100644 index 0000000..0e7741c --- /dev/null +++ b/test/apps/with-sourcemaps/src/server.js @@ -0,0 +1,8 @@ +import polka from 'polka'; +import * as sapper from '../__sapper__/server.js'; + +const { PORT } = process.env; + +polka() + .use(sapper.middleware()) + .listen(PORT); diff --git a/test/apps/with-sourcemaps/src/service-worker.js b/test/apps/with-sourcemaps/src/service-worker.js new file mode 100644 index 0000000..9d2ac9d --- /dev/null +++ b/test/apps/with-sourcemaps/src/service-worker.js @@ -0,0 +1,82 @@ +import { timestamp, files, shell, routes } from '../__sapper__/service-worker.js'; + +const ASSETS = `cache${timestamp}`; + +// `shell` is an array of all the files generated by webpack, +// `files` is an array of everything in the `static` directory +const to_cache = shell.concat(ASSETS); +const cached = new Set(to_cache); + +self.addEventListener('install', event => { + event.waitUntil( + caches + .open(ASSETS) + .then(cache => cache.addAll(to_cache)) + .then(() => { + self.skipWaiting(); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(async keys => { + // delete old caches + for (const key of keys) { + if (key !== ASSETS) await caches.delete(key); + } + + self.clients.claim(); + }) + ); +}); + +self.addEventListener('fetch', event => { + if (event.request.method !== 'GET') return; + + const url = new URL(event.request.url); + + // don't try to handle e.g. data: URIs + if (!url.protocol.startsWith('http')) return; + + // ignore dev server requests + if (url.hostname === self.location.hostname && url.port !== self.location.port) return; + + // always serve assets and webpack-generated files from cache + if (url.host === self.location.host && cached.has(url.pathname)) { + event.respondWith(caches.match(event.request)); + return; + } + + // for pages, you might want to serve a shell `index.html` file, + // which Sapper has generated for you. It's not right for every + // app, but if it's right for yours then uncomment this section + /* + if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) { + event.respondWith(caches.match('/index.html')); + return; + } + */ + + if (event.request.cache === 'only-if-cached') return; + + // for everything else, try the network first, falling back to + // cache if the user is offline. (If the pages never change, you + // might prefer a cache-first approach to a network-first one.) + event.respondWith( + caches + .open(`offline${timestamp}`) + .then(async cache => { + try { + const response = await fetch(event.request); + cache.put(event.request, response.clone()); + return response; + } catch(err) { + const response = await cache.match(event.request); + if (response) return response; + + throw err; + } + }) + ); +}); diff --git a/test/apps/with-sourcemaps/src/template.html b/test/apps/with-sourcemaps/src/template.html new file mode 100644 index 0000000..0eb1f3b --- /dev/null +++ b/test/apps/with-sourcemaps/src/template.html @@ -0,0 +1,14 @@ + + + + + + %sapper.base% + %sapper.styles% + %sapper.head% + + +
%sapper.html%
+ %sapper.scripts% + + diff --git a/test/apps/with-sourcemaps/test.ts b/test/apps/with-sourcemaps/test.ts new file mode 100644 index 0000000..1b61c28 --- /dev/null +++ b/test/apps/with-sourcemaps/test.ts @@ -0,0 +1,43 @@ +import * as puppeteer from 'puppeteer'; +import { build } from '../../../api'; +import * as assert from "assert"; +import { AppRunner } from '../AppRunner'; +import * as fs from 'fs'; +import * as path from "path"; + +describe('with-sourcemaps', function() { + this.timeout(10000); + + let runner: AppRunner; + let page: puppeteer.Page; + let base: string; + + // helpers + let start: () => Promise; + let prefetchRoutes: () => Promise; + let prefetch: (href: string) => Promise; + let goto: (href: string) => Promise; + + // hooks + before(async () => { + await build({ cwd: __dirname }); + + runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); + ({ base, page, start, prefetchRoutes, prefetch, goto } = await runner.start()); + }); + + it('does not put sourcemap files in service worker shell', async () => { + const serviceWorker = await import(`${__dirname}/__sapper__/service-worker.js`); + const shell: string[] = serviceWorker.shell; + + assert.equal(shell.filter(_ => _.endsWith('.map')).length, 0, + 'sourcemap files are not cached in SW'); + + const clientShellDir = path.resolve(`${__dirname}/__sapper__/build`, path.dirname(shell[0])); + const sourcemapFiles = fs.readdirSync(clientShellDir).filter(_ => _.endsWith('.map')); + assert.ok(sourcemapFiles.length > 0, 'sourcemap files exist'); + }); + + after(() => runner.end()); + +}); \ No newline at end of file From 7726325b4b037aaa8425f5d4e1c2fd6204b81658 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 31 Dec 2018 11:14:49 -0500 Subject: [PATCH 174/178] support Rollup 1.0 --- src/core/create_compilers/RollupCompiler.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/create_compilers/RollupCompiler.ts b/src/core/create_compilers/RollupCompiler.ts index f750e42..f8dc627 100644 --- a/src/core/create_compilers/RollupCompiler.ts +++ b/src/core/create_compilers/RollupCompiler.ts @@ -142,12 +142,14 @@ export default class RollupCompiler { const bundle = await rollup.rollup({ input, + inlineDynamicImports: true, external: (id: string) => { return (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5, id.length) === '.json'; } }); - const { code } = await bundle.generate({ format: 'cjs' }); + const resp = await bundle.generate({ format: 'cjs' }); + const { code } = resp.output ? resp.output[0] : resp; // temporarily override require const defaultLoader = require.extensions['.js']; From f66c7dcb0db2ef8699419c222e1c95a4b8058114 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Mon, 31 Dec 2018 11:45:20 -0500 Subject: [PATCH 175/178] -> v0.24.2 --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d692da9..f4ed581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # sapper changelog +## 0.24.2 + +* Support Rollup 1.0 ([#541](https://github.com/sveltejs/sapper/pull/541)) + ## 0.24.1 * Include CSS chunks in webpack build info to avoid duplication ([#529](https://github.com/sveltejs/sapper/pull/529)) diff --git a/package.json b/package.json index d753c9e..7830c73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.24.1", + "version": "0.24.2", "description": "Military-grade apps, engineered by Svelte", "bin": { "sapper": "./sapper" From e2193a608076df5ac9186c66464816e737f6d5e5 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Mon, 31 Dec 2018 11:46:08 -0500 Subject: [PATCH 176/178] -> v0.24.2 --- package-lock.json | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6b895e..3b7fd57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.24.0", + "version": "0.24.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2362,14 +2362,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2384,20 +2382,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -2514,8 +2509,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2527,7 +2521,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2542,7 +2535,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2550,14 +2542,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -2576,7 +2566,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -2657,8 +2646,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2670,7 +2658,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2792,7 +2779,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", From 969169ae206fa58232678dc74f5ab2226efaedcc Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Fri, 1 Feb 2019 05:40:58 -0500 Subject: [PATCH 177/178] -> v0.24.3 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4ed581..fe6dc02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # sapper changelog +## 0.24.3 + +* Add service-worker-index.html shell file for offline support ([#422](https://github.com/sveltejs/sapper/issues/422)) +* Don't cache .map files ([#534](https://github.com/sveltejs/sapper/issues/534)) + ## 0.24.2 * Support Rollup 1.0 ([#541](https://github.com/sveltejs/sapper/pull/541)) diff --git a/package.json b/package.json index 7830c73..4d7e9fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.24.2", + "version": "0.24.3", "description": "Military-grade apps, engineered by Svelte", "bin": { "sapper": "./sapper" From 44bcbeb7d619a3b2e963716e1a1dca8201453219 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Fri, 1 Feb 2019 05:57:17 -0500 Subject: [PATCH 178/178] remove redundant line --- src/api/build.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/build.ts b/src/api/build.ts index 73680b2..c8287d6 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -42,7 +42,6 @@ export async function build({ routes = path.resolve(cwd, routes); output = path.resolve(cwd, output); static_files = path.resolve(cwd, static_files); - dest = path.resolve(cwd, dest); if (legacy && bundler === 'webpack') { throw new Error(`Legacy builds are not supported for projects using webpack`);