mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-13 11:35:28 +00:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdacbb93bb | ||
|
|
fb8bf5c9b0 | ||
|
|
8baf694e6d | ||
|
|
b6c693934d | ||
|
|
c42de4cc44 | ||
|
|
ff6db7d8af | ||
|
|
6c9a90e87d | ||
|
|
a1d3927958 | ||
|
|
d0caad5303 | ||
|
|
b1e84687c0 | ||
|
|
993bd6cc5b | ||
|
|
d4ee7ab040 | ||
|
|
43e7f0d30d | ||
|
|
d7e2662298 | ||
|
|
a86b613e12 | ||
|
|
fcd66fb736 | ||
|
|
b6e2736eb0 | ||
|
|
aa31e3ca6a | ||
|
|
8b310dd458 | ||
|
|
5460896228 | ||
|
|
bcf6ac3a6f | ||
|
|
b8d99aaa90 | ||
|
|
2bb173f140 | ||
|
|
594cb0a356 | ||
|
|
9ff87af23b | ||
|
|
75afc691f4 | ||
|
|
9dd63ab760 | ||
|
|
e7b1aa373a | ||
|
|
ce50c2ff98 | ||
|
|
6add2518aa | ||
|
|
ab939b8cb5 | ||
|
|
b59d9003f9 | ||
|
|
1fc169e7b8 | ||
|
|
7aa3e90f87 | ||
|
|
b27c9ecd59 | ||
|
|
8653a799eb | ||
|
|
62969d59f6 | ||
|
|
5c07080207 | ||
|
|
c2ed73f103 | ||
|
|
133ac07ed2 | ||
|
|
7417de101e | ||
|
|
2792b7c5d1 | ||
|
|
cf8d5ee717 | ||
|
|
8e4517a1ad | ||
|
|
91894722ee | ||
|
|
933b3b76a6 | ||
|
|
3ed4d1d887 | ||
|
|
dda936e53b | ||
|
|
ac4eb84f3d | ||
|
|
9e70e68c0c | ||
|
|
22389eab99 | ||
|
|
fe6b7976ef | ||
|
|
54e92c3b99 | ||
|
|
e6c1a54164 | ||
|
|
629b5601c8 | ||
|
|
8bdd363a19 | ||
|
|
8fcc27d44f | ||
|
|
f6e72a0432 | ||
|
|
f886a12bd7 | ||
|
|
6c03cfd46a | ||
|
|
78480fe5e8 | ||
|
|
5cba40b7e0 | ||
|
|
c99b787632 | ||
|
|
99a25308fc | ||
|
|
c0ada5c52f | ||
|
|
d51e1a0af8 | ||
|
|
5e5a8c4c69 | ||
|
|
49f8b2c4bd | ||
|
|
2754ba0ee4 | ||
|
|
1ad27573c6 | ||
|
|
f10b941c4e | ||
|
|
6ca869a3b1 | ||
|
|
a2204a9d2e |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,11 +3,7 @@ yarn.lock
|
||||
yarn-error.log
|
||||
node_modules
|
||||
cypress/screenshots
|
||||
test/app/.sapper
|
||||
test/app/src/manifest
|
||||
__sapper__
|
||||
test/app/export
|
||||
test/app/build
|
||||
sapper
|
||||
runtime.js
|
||||
dist
|
||||
|
||||
@@ -3,7 +3,7 @@ sudo: false
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- "11"
|
||||
- "stable"
|
||||
|
||||
env:
|
||||
global:
|
||||
|
||||
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,5 +1,37 @@
|
||||
# sapper changelog
|
||||
|
||||
## 0.27.3
|
||||
|
||||
* Accommodate Svelte 3.5.0
|
||||
|
||||
|
||||
## 0.27.2
|
||||
|
||||
* Fix routes with regular expressions ([#707](https://github.com/sveltejs/sapper/issues/707))
|
||||
* Fix `sapper build --output` option ([#723](https://github.com/sveltejs/sapper/pull/723))
|
||||
|
||||
|
||||
## 0.27.1
|
||||
|
||||
* Prevent infinite loop if `preload` errors ([#677](https://github.com/sveltejs/sapper/pull/677))
|
||||
* Allow disabling of live reload ([#683](https://github.com/sveltejs/sapper/pull/683))
|
||||
* Let browser handle initial scroll ([#331](https://github.com/sveltejs/sapper/issues/331))
|
||||
* Allow custom route file extensions via `--ext` ([#632](https://github.com/sveltejs/sapper/pull/632))
|
||||
* Wait for server to restart before attaching debugger ([#694](https://github.com/sveltejs/sapper/pull/694))
|
||||
* Fix export queue ([#698](https://github.com/sveltejs/sapper/pull/698))
|
||||
* Rerun `preload` functions when query changes ([#701](https://github.com/sveltejs/sapper/issues/701))
|
||||
* Navigate when spread route changes ([#688](https://github.com/sveltejs/sapper/issues/688))
|
||||
|
||||
|
||||
## 0.27.0
|
||||
|
||||
* Change license from LIL to MIT ([#652](https://github.com/sveltejs/sapper/pull/652))
|
||||
* Fix index server route mapping ([#624](https://github.com/sveltejs/sapper/issues/624))
|
||||
|
||||
## 0.26.1
|
||||
|
||||
* Handle skipped segments ([#663](https://github.com/sveltejs/sapper/pull/663))
|
||||
|
||||
## 0.26.0
|
||||
|
||||
* Update to Svelte 3
|
||||
@@ -602,4 +634,4 @@
|
||||
|
||||
## 0.1.0
|
||||
|
||||
* First public preview
|
||||
* First public preview
|
||||
|
||||
10
LICENSE
10
LICENSE
@@ -1,9 +1,7 @@
|
||||
Copyright (c) 2017 [these people](https://github.com/sveltejs/sapper/graphs/contributors).
|
||||
Copyright (c) 2016-19 [these people](https://github.com/sveltejs/sapper/graphs/contributors)
|
||||
|
||||
Permission is hereby granted by the authors of this software, to any person, to use the software for any purpose, free of charge, including the rights to run, read, copy, change, distribute and sell it, and including usage rights to any patents the authors may hold on it, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
This license, or a link to its text, must be included with all copies of the software and any derivative works.
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
Any modification to the software submitted to the authors may be incorporated into the software under the terms of this license.
|
||||
|
||||
The software is provided "as is", without warranty of any kind, including but not limited to the warranties of title, fitness, merchantability and non-infringement. The authors have no obligation to provide support or updates for the software, and may not be held liable for any damages, claims or other liability arising from its use.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# sapper
|
||||
|
||||
[Military-grade progressive web apps, powered by Svelte.](https://sapper.svelte.technology)
|
||||
[Military-grade progressive web apps, powered by Svelte.](https://sapper.svelte.dev)
|
||||
|
||||
|
||||
## What is Sapper?
|
||||
|
||||
Sapper is a framework for building high-performance universal web apps. [Read the guide](https://sapper.svelte.technology/guide) or the [introductory blog post](https://svelte.technology/blog/sapper-towards-the-ideal-web-app-framework) to learn more.
|
||||
Sapper is a framework for building high-performance universal web apps. [Read the guide](https://sapper.svelte.dev/docs) or the [introductory blog post](https://svelte.dev/blog/sapper-towards-the-ideal-web-app-framework) to learn more.
|
||||
|
||||
|
||||
## Get started
|
||||
@@ -74,4 +74,4 @@ npm run test
|
||||
|
||||
## License
|
||||
|
||||
[LIL](LICENSE)
|
||||
[MIT](LICENSE)
|
||||
|
||||
1609
package-lock.json
generated
1609
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
58
package.json
58
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sapper",
|
||||
"version": "0.26.0",
|
||||
"version": "0.27.3",
|
||||
"description": "Military-grade apps, engineered by Svelte",
|
||||
"bin": {
|
||||
"sapper": "./sapper"
|
||||
@@ -18,50 +18,50 @@
|
||||
"test": "test"
|
||||
},
|
||||
"dependencies": {
|
||||
"html-minifier": "^3.5.21",
|
||||
"html-minifier": "^4.0.0",
|
||||
"http-link-header": "^1.0.2",
|
||||
"shimport": "^1.0.0",
|
||||
"sourcemap-codec": "^1.4.4",
|
||||
"string-hash": "^1.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^10.12.21",
|
||||
"@types/puppeteer": "^1.11.3",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^12.0.7",
|
||||
"@types/puppeteer": "^1.12.4",
|
||||
"agadoo": "^1.0.1",
|
||||
"cheap-watch": "^1.0.2",
|
||||
"cookie": "^0.3.1",
|
||||
"cookie": "^0.4.0",
|
||||
"devalue": "^1.1.0",
|
||||
"eslint": "^5.12.1",
|
||||
"eslint-plugin-import": "^2.16.0",
|
||||
"kleur": "^3.0.1",
|
||||
"mocha": "^5.2.0",
|
||||
"node-fetch": "^2.3.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-import": "^2.17.3",
|
||||
"kleur": "^3.0.3",
|
||||
"mocha": "^6.1.4",
|
||||
"node-fetch": "^2.6.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"polka": "^0.5.1",
|
||||
"polka": "^0.5.2",
|
||||
"port-authority": "^1.0.5",
|
||||
"pretty-bytes": "^5.1.0",
|
||||
"puppeteer": "^1.12.0",
|
||||
"pretty-bytes": "^5.2.0",
|
||||
"puppeteer": "^1.17.0",
|
||||
"require-relative": "^0.8.7",
|
||||
"rollup": "^1.1.2",
|
||||
"rollup-plugin-commonjs": "^9.2.0",
|
||||
"rollup-plugin-json": "^3.1.0",
|
||||
"rollup-plugin-node-resolve": "^4.0.0",
|
||||
"rollup-plugin-replace": "^2.1.0",
|
||||
"rollup-plugin-string": "^2.0.2",
|
||||
"rollup": "^1.14.5",
|
||||
"rollup-plugin-commonjs": "^10.0.0",
|
||||
"rollup-plugin-json": "^4.0.0",
|
||||
"rollup-plugin-node-resolve": "^5.0.1",
|
||||
"rollup-plugin-replace": "^2.2.0",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
"rollup-plugin-sucrase": "^2.1.0",
|
||||
"rollup-plugin-svelte": "^5.0.3",
|
||||
"sade": "^1.4.2",
|
||||
"sirv": "^0.2.2",
|
||||
"sucrase": "^3.9.5",
|
||||
"svelte": "^3.0.0-beta.11",
|
||||
"svelte-loader": "^2.13.3",
|
||||
"webpack": "^4.29.0",
|
||||
"sade": "^1.5.0",
|
||||
"sirv": "^0.4.2",
|
||||
"sucrase": "^3.10.1",
|
||||
"svelte": "^3.5.0",
|
||||
"svelte-loader": "^2.13.4",
|
||||
"webpack": "^4.33.0",
|
||||
"webpack-format-messages": "^2.0.5",
|
||||
"yootils": "0.0.15"
|
||||
"yootils": "0.0.16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.0.0"
|
||||
"svelte": "^3.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha --opts mocha.opts",
|
||||
@@ -81,7 +81,7 @@
|
||||
"express"
|
||||
],
|
||||
"author": "Rich Harris",
|
||||
"license": "LIL",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/sveltejs/sapper/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import sucrase from 'rollup-plugin-sucrase';
|
||||
import string from 'rollup-plugin-string';
|
||||
import { string } from 'rollup-plugin-string';
|
||||
import json from 'rollup-plugin-json';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
@@ -52,7 +52,8 @@ export default [
|
||||
output: {
|
||||
dir: 'dist',
|
||||
format: 'cjs',
|
||||
sourcemap: true
|
||||
sourcemap: true,
|
||||
chunkFileNames: '[name].js'
|
||||
},
|
||||
external,
|
||||
plugins: [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { writable } from 'svelte/store.mjs';
|
||||
import { writable } from 'svelte/store';
|
||||
import App from '@sapper/internal/App.svelte';
|
||||
import { root_preload, ErrorComponent, ignore, components, routes } from '@sapper/internal/manifest-client';
|
||||
import {
|
||||
@@ -22,6 +22,7 @@ let root_component: Component;
|
||||
let current_token: {};
|
||||
let root_preloaded: Promise<any>;
|
||||
let current_branch = [];
|
||||
let current_query = '{}';
|
||||
|
||||
const stores = {
|
||||
page: writable({}),
|
||||
@@ -101,7 +102,11 @@ export function select_target(url: URL): Target {
|
||||
if (url.origin !== location.origin) return null;
|
||||
if (!url.pathname.startsWith(initial_data.baseUrl)) return null;
|
||||
|
||||
const path = url.pathname.slice(initial_data.baseUrl.length);
|
||||
let path = url.pathname.slice(initial_data.baseUrl.length);
|
||||
|
||||
if (path === '') {
|
||||
path = '/';
|
||||
}
|
||||
|
||||
// avoid accidental clashes between server routes and page routes
|
||||
if (ignore.some(pattern => pattern.test(path))) return;
|
||||
@@ -246,10 +251,28 @@ async function render(redirect: Redirect, branch: any[], props: any, page: Page)
|
||||
}
|
||||
|
||||
current_branch = branch;
|
||||
current_query = JSON.stringify(page.query);
|
||||
ready = true;
|
||||
session_dirty = false;
|
||||
}
|
||||
|
||||
function part_changed(i, segment, match, stringified_query) {
|
||||
// TODO only check query string changes for preload functions
|
||||
// that do in fact depend on it (using static analysis or
|
||||
// runtime instrumentation)
|
||||
if (stringified_query !== current_query) return true;
|
||||
|
||||
const previous = current_branch[i];
|
||||
|
||||
if (!previous) return false;
|
||||
if (segment !== previous.segment) return true;
|
||||
if (previous.match) {
|
||||
if (JSON.stringify(previous.match.slice(1, i + 2)) !== JSON.stringify(match.slice(1, i + 2))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function hydrate_target(target: Target): Promise<{
|
||||
redirect?: Redirect;
|
||||
props?: any;
|
||||
@@ -288,14 +311,26 @@ export async function hydrate_target(target: Target): Promise<{
|
||||
let l = 1;
|
||||
|
||||
try {
|
||||
const stringified_query = JSON.stringify(page.query);
|
||||
const match = route.pattern.exec(page.path);
|
||||
|
||||
let segment_dirty = false;
|
||||
|
||||
branch = await Promise.all(route.parts.map(async (part, i) => {
|
||||
const segment = segments[i];
|
||||
|
||||
if (part_changed(i, segment, match, stringified_query)) segment_dirty = true;
|
||||
|
||||
props.segments[l] = segments[i + 1]; // TODO make this less confusing
|
||||
if (!part) return null;
|
||||
if (!part) return { segment };
|
||||
|
||||
const j = l++;
|
||||
|
||||
const segment = segments[i];
|
||||
if (!session_dirty && current_branch[i] && current_branch[i].segment === segment && current_branch[i].part === part.i) return current_branch[i];
|
||||
if (!session_dirty && !segment_dirty && current_branch[i] && current_branch[i].part === part.i) {
|
||||
return current_branch[i];
|
||||
}
|
||||
|
||||
segment_dirty = false;
|
||||
|
||||
const { default: component, preload } = await load_component(components[part.i]);
|
||||
|
||||
@@ -312,7 +347,7 @@ export async function hydrate_target(target: Target): Promise<{
|
||||
preloaded = initial_data.preloaded[i + 1];
|
||||
}
|
||||
|
||||
return (props[`level${j}`] = { component, props: preloaded, segment, part: part.i });
|
||||
return (props[`level${j}`] = { component, props: preloaded, segment, match, part: part.i });
|
||||
}));
|
||||
} catch (error) {
|
||||
props.error = error;
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function start(opts: {
|
||||
if (initial_data.error) return handle_error(url);
|
||||
|
||||
const target = select_target(url);
|
||||
if (target) return navigate(target, uid, false, hash);
|
||||
if (target) return navigate(target, uid, true, hash);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const IGNORE = '__SAPPER__IGNORE__';
|
||||
@@ -1,14 +1,12 @@
|
||||
import { writable } from 'svelte/store.mjs';
|
||||
import { writable } from 'svelte/store';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import cookie from 'cookie';
|
||||
import devalue from 'devalue';
|
||||
import fetch from 'node-fetch';
|
||||
import URL from 'url';
|
||||
import { IGNORE } from '../constants';
|
||||
import { Manifest, Page, Props, Req, Res } from './types';
|
||||
import { Manifest, Page, Req, Res } from './types';
|
||||
import { build_dir, dev, src_dir } from '@sapper/internal/manifest-server';
|
||||
import { stores } from '@sapper/internal/shared';
|
||||
import App from '@sapper/internal/App.svelte';
|
||||
|
||||
export function get_page_handler(
|
||||
@@ -28,6 +26,15 @@ export function get_page_handler(
|
||||
const { server_routes, pages } = manifest;
|
||||
const error_route = manifest.error;
|
||||
|
||||
function bail(req: Req, res: Res, err: Error) {
|
||||
console.error(err);
|
||||
|
||||
const message = dev ? escape_html(err.message) : 'Internal server error';
|
||||
|
||||
res.statusCode = 500;
|
||||
res.end(`<pre>${message}</pre>`);
|
||||
}
|
||||
|
||||
function handle_error(req: Req, res: Res, statusCode: number, error: Error | string) {
|
||||
handle_page({
|
||||
pattern: null,
|
||||
@@ -171,6 +178,10 @@ export function get_page_handler(
|
||||
|
||||
preloaded = await Promise.all(toPreload);
|
||||
} catch (err) {
|
||||
if (error) {
|
||||
return bail(req, res, err)
|
||||
}
|
||||
|
||||
preload_error = { statusCode: 500, message: err };
|
||||
preloaded = []; // appease TypeScript
|
||||
}
|
||||
@@ -316,11 +327,8 @@ export function get_page_handler(
|
||||
res.statusCode = status;
|
||||
res.end(body);
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
if (error) {
|
||||
// we encountered an error while rendering the error page — oops
|
||||
res.statusCode = 500;
|
||||
res.end(`<pre>${escape_html(err.message)}</pre>`);
|
||||
bail(req, res, err)
|
||||
} else {
|
||||
handle_error(req, res, 500, err);
|
||||
}
|
||||
@@ -328,8 +336,6 @@ 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);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { IGNORE } from '../constants';
|
||||
import { Req, Res, ServerRoute } from './types';
|
||||
|
||||
export function get_server_route_handler(routes: ServerRoute[]) {
|
||||
@@ -64,8 +63,6 @@ export function get_server_route_handler(routes: ServerRoute[]) {
|
||||
}
|
||||
|
||||
return function find_route(req: Req, res: Res, next: () => void) {
|
||||
if (req[IGNORE]) return next();
|
||||
|
||||
for (const route of routes) {
|
||||
if (route.pattern.test(req.path)) {
|
||||
handle_route(route, req, res, next);
|
||||
@@ -75,4 +72,4 @@ export function get_server_route_handler(routes: ServerRoute[]) {
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Handler, Req, Res } from './types';
|
||||
import { get_server_route_handler } from './get_server_route_handler';
|
||||
import { get_page_handler } from './get_page_handler';
|
||||
import { lookup } from './mime';
|
||||
import { IGNORE } from '../constants';
|
||||
|
||||
export default function middleware(opts: {
|
||||
session?: (req: Req, res: Res) => any,
|
||||
@@ -15,15 +14,8 @@ export default function middleware(opts: {
|
||||
|
||||
let emitted_basepath = false;
|
||||
|
||||
return compose_handlers([
|
||||
ignore && ((req: Req, res: Res, next: () => void) => {
|
||||
req[IGNORE] = should_ignore(req.path, ignore);
|
||||
next();
|
||||
}),
|
||||
|
||||
return compose_handlers(ignore, [
|
||||
(req: Req, res: Res, next: () => void) => {
|
||||
if (req[IGNORE]) return next();
|
||||
|
||||
if (req.baseUrl === undefined) {
|
||||
let { originalUrl } = req;
|
||||
if (req.url === '/' && originalUrl[originalUrl.length - 1] !== '/') {
|
||||
@@ -73,24 +65,26 @@ export default function middleware(opts: {
|
||||
].filter(Boolean));
|
||||
}
|
||||
|
||||
export function compose_handlers(handlers: Handler[]) {
|
||||
return (req: Req, res: Res, next: () => void) => {
|
||||
let i = 0;
|
||||
function go() {
|
||||
const handler = handlers[i];
|
||||
export function compose_handlers(ignore: any, handlers: Handler[]): Handler {
|
||||
const total = handlers.length;
|
||||
|
||||
if (handler) {
|
||||
handler(req, res, () => {
|
||||
i += 1;
|
||||
go();
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
function nth_handler(n: number, req: Req, res: Res, next: () => void) {
|
||||
if (n >= total) {
|
||||
return next();
|
||||
}
|
||||
|
||||
go();
|
||||
};
|
||||
handlers[n](req, res, () => nth_handler(n+1, req, res, next));
|
||||
}
|
||||
|
||||
return !ignore
|
||||
? (req, res, next) => nth_handler(0, req, res, next)
|
||||
: (req, res, next) => {
|
||||
if (should_ignore(req.path, ignore)) {
|
||||
next();
|
||||
} else {
|
||||
nth_handler(0, req, res, next);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function should_ignore(uri: string, val: any) {
|
||||
@@ -116,8 +110,6 @@ export function serve({ prefix, pathname, cache_control }: {
|
||||
: (file: string) => (cache.has(file) ? cache : cache.set(file, fs.readFileSync(path.resolve(build_dir, file)))).get(file)
|
||||
|
||||
return (req: Req, res: Res, next: () => void) => {
|
||||
if (req[IGNORE]) return next();
|
||||
|
||||
if (filter(req)) {
|
||||
const type = lookup(req.path);
|
||||
|
||||
|
||||
@@ -1,88 +1,17 @@
|
||||
# sapper-template
|
||||
## Running locally
|
||||
|
||||
The default [Sapper](https://github.com/sveltejs/sapper) template, with branches for Rollup and webpack. To clone it and get started:
|
||||
Set up the project:
|
||||
|
||||
```bash
|
||||
# for Rollup
|
||||
npx degit sveltejs/sapper-template#rollup my-app
|
||||
# for webpack
|
||||
npx degit sveltejs/sapper-template#webpack my-app
|
||||
cd my-app
|
||||
npm install # or yarn!
|
||||
npm run dev
|
||||
git clone https://github.com/sveltejs/sapper.git
|
||||
cd sapper/site
|
||||
npm ci
|
||||
```
|
||||
|
||||
Open up [localhost:3000](http://localhost:3000) and start clicking around.
|
||||
Start the server with `npm run dev`, and navigate to [localhost:3000](http://localhost:3000).
|
||||
|
||||
Consult [sapper.svelte.technology](https://sapper.svelte.technology) for help getting started.
|
||||
## Translating the API docs
|
||||
|
||||
Anchors are automatically generated using headings in the documentation and by default (for the english language) they are latinised to make sure the URL is always conforming to RFC3986.
|
||||
|
||||
## Structure
|
||||
|
||||
Sapper expects to find two directories in the root of your project — `src` and `static`.
|
||||
|
||||
|
||||
### src
|
||||
|
||||
The [src](src) directory contains the entry points for your app — `client.js`, `server.js` and (optionally) a `service-worker.js` — along with a `template.html` file and a `routes` directory.
|
||||
|
||||
|
||||
#### src/routes
|
||||
|
||||
This is the heart of your Sapper app. There are two kinds of routes — *pages*, and *server routes*.
|
||||
|
||||
**Pages** are Svelte components written in `.html` files. When a user first visits the application, they will be served a server-rendered version of the route in question, plus some JavaScript that 'hydrates' the page and initialises a client-side router. From that point forward, navigating to other pages is handled entirely on the client for a fast, app-like feel. (Sapper will preload and cache the code for these subsequent pages, so that navigation is instantaneous.)
|
||||
|
||||
**Server routes** are modules written in `.js` files, that export functions corresponding to HTTP methods. Each function receives Express `request` and `response` objects as arguments, plus a `next` function. This is useful for creating a JSON API, for example.
|
||||
|
||||
There are three simple rules for naming the files that define your routes:
|
||||
|
||||
* A file called `src/routes/about.html` corresponds to the `/about` route. A file called `src/routes/blog/[slug].html` corresponds to the `/blog/:slug` route, in which case `params.slug` is available to the route
|
||||
* The file `src/routes/index.html` (or `src/routes/index.js`) corresponds to the root of your app. `src/routes/about/index.html` is treated the same as `src/routes/about.html`.
|
||||
* Files and directories with a leading underscore do *not* create routes. This allows you to colocate helper modules and components with the routes that depend on them — for example you could have a file called `src/routes/_helpers/datetime.js` and it would *not* create a `/_helpers/datetime` route
|
||||
|
||||
|
||||
### static
|
||||
|
||||
The [static](static) directory contains any static assets that should be available. These are served using [sirv](https://github.com/lukeed/sirv).
|
||||
|
||||
In your [service-worker.js](app/service-worker.js) file, you can import these as `files` from the generated manifest...
|
||||
|
||||
```js
|
||||
import { files } from '../__sapper__/service-worker.js';
|
||||
```
|
||||
|
||||
...so that you can cache them (though you can choose not to, for example if you don't want to cache very large files).
|
||||
|
||||
|
||||
## Bundler config
|
||||
|
||||
Sapper uses Rollup or webpack to provide code-splitting and dynamic imports, as well as compiling your Svelte components. With webpack, it also provides hot module reloading. As long as you don't do anything daft, you can edit the configuration files to add whatever plugins you'd like.
|
||||
|
||||
|
||||
## Production mode and deployment
|
||||
|
||||
To start a production version of your app, run `npm run build && npm start`. This will disable live reloading, and activate the appropriate bundler plugins.
|
||||
|
||||
You can deploy your application to any environment that supports Node 8 or above. As an example, to deploy to [Now](https://zeit.co/now), run these commands:
|
||||
|
||||
```bash
|
||||
npm install -g now
|
||||
now
|
||||
```
|
||||
|
||||
|
||||
## Using external components with webpack
|
||||
|
||||
When using Svelte components installed from npm, such as [@sveltejs/svelte-virtual-list](https://github.com/sveltejs/svelte-virtual-list), Svelte needs the original component source (rather than any precompiled JavaScript that ships with the component). This allows the component to be rendered server-side, and also keeps your client-side app smaller.
|
||||
|
||||
Because of that, it's essential that webpack doesn't treat the package as an *external dependency*. You can either modify the `externals` option in [webpack/server.config.js](webpack/server.config.js), or simply install the package to `devDependencies` rather than `dependencies`, which will cause it to get bundled (and therefore compiled) with your app:
|
||||
|
||||
```bash
|
||||
yarn add -D @sveltejs/svelte-virtual-list
|
||||
```
|
||||
|
||||
|
||||
## Bugs and feedback
|
||||
|
||||
Sapper is in early development, and may have the odd rough edge here and there. Please be vocal over on the [Sapper issue tracker](https://github.com/sveltejs/sapper/issues).
|
||||
If we need to translate the API documentation to a language using unicode chars, we can setup this app to export the correct anchors by setting up `SLUG_PRESERVE_UNICODE` to `true` in `config.js`.
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Introduction
|
||||
|
||||
### Before we begin
|
||||
|
||||
> Sapper is in early development, and some things may change before we hit version 1.0. This document is a work-in-progress. If you get stuck, reach out for help in the [Discord chatroom](https://discord.gg/yy75DKs).
|
||||
> Sapper is in early development, and some things may change before we hit version 1.0. This document is a work-in-progress. If you get stuck, reach out for help in the [Discord chatroom](https://svelte.dev/chat).
|
||||
>
|
||||
> See the [migration guides](migrating) for help upgrading from older versions.
|
||||
|
||||
@@ -32,7 +32,7 @@ For web developers, the stakes are generally lower than for combat engineers. Bu
|
||||
[Next.js](https://github.com/zeit/next.js) is a React framework from [Zeit](https://zeit.co), and is the inspiration for Sapper. There are a few notable differences, however:
|
||||
|
||||
* Sapper is 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 (see the [routing](docs#routing) section below)
|
||||
* Instead of route masking, we encode route parameters in filenames (see the [routing](docs#Routing) section below)
|
||||
* As well as *pages*, you can create *server routes* in your `src/routes` directory. This makes it very easy to, for example, add a JSON API such as the one powering this very page (try visiting [/docs.json](/docs.json))
|
||||
* 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 markdown, works with the router as you'd expect
|
||||
|
||||
@@ -42,8 +42,8 @@ For web developers, the stakes are generally lower than for combat engineers. Bu
|
||||
The easiest way to start building a Sapper app is to clone the [sapper-template](https://github.com/sveltejs/sapper-template) repo with [degit](https://github.com/Rich-Harris/degit):
|
||||
|
||||
```bash
|
||||
npx degit sveltejs/sapper-template#rollup my-app
|
||||
# or: npx degit sveltejs/sapper-template#webpack my-app
|
||||
npx degit "sveltejs/sapper-template#rollup" my-app
|
||||
# or: npx degit "sveltejs/sapper-template#webpack" my-app
|
||||
cd my-app
|
||||
npm install
|
||||
npm run dev
|
||||
|
||||
@@ -24,9 +24,9 @@ If you take a look inside the [sapper-template](https://github.com/sveltejs/sapp
|
||||
|
||||
When you first run Sapper, it will create an additional `__sapper__` directory containing generated files.
|
||||
|
||||
You'll notice a few extra files and a `cypress` directory which relates to [testing](docs#testing) — we don't need to worry about those right now.
|
||||
You'll notice a few extra files and a `cypress` directory which relates to [testing](docs#Testing) — we don't need to worry about those right now.
|
||||
|
||||
> You *can* create these files from scratch, but it's much better to use the template. See [getting started](docs#getting-started) for instructions on how to easily clone it
|
||||
> You *can* create these files from scratch, but it's much better to use the template. See [getting started](docs#Getting_started) for instructions on how to easily clone it
|
||||
|
||||
|
||||
### package.json
|
||||
@@ -35,9 +35,9 @@ Your package.json contains your app's dependencies and defines a number of scrip
|
||||
|
||||
* `npm run dev` — start the app in development mode, and watch source files for changes
|
||||
* `npm run build` — build the app in production mode
|
||||
* `npm run export` — bake out a static version, if applicable (see [exporting](docs#exporting))
|
||||
* `npm run export` — bake out a static version, if applicable (see [exporting](docs#Exporting))
|
||||
* `npm start` — start the app in production mode after you've built it
|
||||
* `npm test` — run the tests (see [testing](docs#testing))
|
||||
* `npm test` — run the tests (see [testing](docs#Testing))
|
||||
|
||||
|
||||
### src
|
||||
@@ -56,7 +56,7 @@ sapper.start({
|
||||
});
|
||||
```
|
||||
|
||||
In many cases, that's the entirety of your entry module, though you can do as much or as little here as you wish. See the [client API](docs#client-api) section for more information on functions you can import.
|
||||
In many cases, that's the entirety of your entry module, though you can do as much or as little here as you wish. See the [client API](docs#Client_API) section for more information on functions you can import.
|
||||
|
||||
|
||||
#### src/server.js
|
||||
@@ -88,7 +88,7 @@ Because every app needs a slightly different service worker (sometimes it's appr
|
||||
|
||||
This file is a template for responses from the server. Sapper will inject content that replaces the following tags:
|
||||
|
||||
* `%sapper.base%` — a `<base>` element (see [base URLs](docs#base-urls))
|
||||
* `%sapper.base%` — a `<base>` element (see [base URLs](docs#Base_URLs))
|
||||
* `%sapper.styles%` — critical CSS for the page being requested
|
||||
* `%sapper.head%` — HTML representing page-specific `<head>` contents, like `<title>`
|
||||
* `%sapper.html%` — HTML representing the body of the page being rendered
|
||||
@@ -97,7 +97,7 @@ This file is a template for responses from the server. Sapper will inject conten
|
||||
|
||||
### src/routes
|
||||
|
||||
This is the meat of your app — the pages and server routes. See the section on [routing](docs#routing) for the juicy details.
|
||||
This is the meat of your app — the pages and server routes. See the section on [routing](docs#Routing) for the juicy details.
|
||||
|
||||
|
||||
### static
|
||||
|
||||
@@ -70,7 +70,7 @@ Dynamic parameters are encoded using `[brackets]`. For example, here's how you c
|
||||
</div>
|
||||
```
|
||||
|
||||
> See the section on [preloading](docs#preloading) for more info about `preload` and `this.fetch`
|
||||
> See the section on [preloading](docs#Preloading) for more info about `preload` and `this.fetch`
|
||||
|
||||
|
||||
### Server routes
|
||||
|
||||
@@ -36,7 +36,7 @@ Programmatically navigates to the given `href`. If the destination is a Sapper r
|
||||
|
||||
* `href` — the page to prefetch
|
||||
|
||||
Programmatically prefetches the given page, which means a) ensuring that the code for the page is loaded, and b) calling the page's `preload` method with the appropriate options. This is the same behaviour that Sapper triggers when the user taps or mouses over an `<a>` element with [rel=prefetch](docs#prefetching).
|
||||
Programmatically prefetches the given page, which means a) ensuring that the code for the page is loaded, and b) calling the page's `preload` method with the appropriate options. This is the same behaviour that Sapper triggers when the user taps or mouses over an `<a>` element with [rel=prefetch](docs#Prefetching).
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Preloading
|
||||
---
|
||||
|
||||
As seen in the [routing](docs#routing) section, page components can have an optional `preload` function that will load some data that the page depends on. This is similar to `getInitialProps` in Next.js or `asyncData` in Nuxt.js.
|
||||
As seen in the [routing](docs#Routing) section, page components can have an optional `preload` function that will load some data that the page depends on. This is similar to `getInitialProps` in Next.js or `asyncData` in Nuxt.js.
|
||||
|
||||
```html
|
||||
<script context="module">
|
||||
|
||||
@@ -44,7 +44,7 @@ You can also add a script to your package.json...
|
||||
|
||||
When you run `sapper export`, Sapper first builds a production version of your app, as though you had run `sapper build`, and copies the contents of your `assets` folder to the destination. It then starts the server, and navigates to the root of your app. From there, it follows any `<a>` elements it finds, and captures any data served by the app.
|
||||
|
||||
Because of this, any pages you want to be included in the exported site must be reachable by `<a>` elements. Additionally, any non-page routes should be requested in `preload`, *not* in `oncreate` or elsewhere.
|
||||
Because of this, any pages you want to be included in the exported site must be reachable by `<a>` elements. Additionally, any non-page routes should be requested in `preload`, *not* in `onMount` or elsewhere.
|
||||
|
||||
|
||||
### When not to export
|
||||
@@ -60,4 +60,4 @@ Because `sapper export` writes to the filesystem, it isn't possible to have two
|
||||
|
||||
The solution is to rename one of the routes to avoid conflict — for example, `src/routes/foo-bar.js`. (Note that you would also need to update any code that fetches data from `/foo/bar` to reference `/foo-bar` instead.)
|
||||
|
||||
For *pages*, we skirt around this problem by writing `export/foo/index.html` instead of `export/foo`.
|
||||
For *pages*, we skirt around this problem by writing `export/foo/index.html` instead of `export/foo`.
|
||||
|
||||
@@ -21,7 +21,7 @@ express() // or Polka, or a similar framework
|
||||
|
||||
Sapper will detect the base path and configure both the server-side and client-side routers accordingly.
|
||||
|
||||
If you're [exporting](docs#exporting) your app, you will need to tell the exporter where to begin crawling:
|
||||
If you're [exporting](docs#Exporting) your app, you will need to tell the exporter where to begin crawling:
|
||||
|
||||
```bash
|
||||
sapper export --basepath my-base-path
|
||||
|
||||
@@ -292,7 +292,7 @@ Once your `App.html` has been created and your server and client apps updated, y
|
||||
|
||||
##### app/template.html
|
||||
|
||||
* Your `<head>` element must contain `%sapper.base%` (see ([base URLs](docs#base-urls))
|
||||
* Your `<head>` element must contain `%sapper.base%` (see ([base URLs](docs#Base_URLs))
|
||||
* Remove references to your service worker; this is now handled by `%sapper.scripts%`
|
||||
|
||||
##### Pages
|
||||
|
||||
795
site/package-lock.json
generated
795
site/package-lock.json
generated
@@ -846,9 +846,9 @@
|
||||
}
|
||||
},
|
||||
"@sveltejs/site-kit": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-1.0.1.tgz",
|
||||
"integrity": "sha512-S5mZG2PkWGDXGZaIJQh5JtF7RLG2uIO36f9juUkUHhjqlz6FaBl2D4zyofmvGergSd6m9NgQ+iXLiFIK9WNmBg==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-1.0.4.tgz",
|
||||
"integrity": "sha512-BaQhIL1iPhCF+iDXfy9psDvRdFzfyMPkWnoZHfVz+INpHsU2aJmRZOPl9rykXmPyiPo+AwTTNK5vjIvmtwHLPQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sindresorhus/slugify": "^0.9.1",
|
||||
@@ -862,9 +862,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "11.13.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.8.tgz",
|
||||
"integrity": "sha512-szA3x/3miL90ZJxUCzx9haNbK5/zmPieGraZEe4WI+3srN0eGLiT22NXeMHmyhNEopn+IrxqMc7wdVwvPl8meg==",
|
||||
"version": "11.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.10.tgz",
|
||||
"integrity": "sha512-leUNzbFTMX94TWaIKz8N15Chu55F9QSH+INKayQr5xpkasBQBRF3qQXfo3/dOnMU/dEIit+Y/SU8HyOjq++GwA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/resolve": {
|
||||
@@ -900,27 +900,6 @@
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
|
||||
"integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"micromatch": "^3.1.4",
|
||||
"normalize-path": "^2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"normalize-path": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
|
||||
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"remove-trailing-separator": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"arr-diff": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
|
||||
@@ -969,12 +948,6 @@
|
||||
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
|
||||
"dev": true
|
||||
},
|
||||
"async-each": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
|
||||
"integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
|
||||
"dev": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -1042,12 +1015,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
|
||||
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -1088,14 +1055,14 @@
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.5.5",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.5.5.tgz",
|
||||
"integrity": "sha512-0QFO1r/2c792Ohkit5XI8Cm8pDtZxgNl2H6HU4mHrpYz7314pEYcsAVVatM0l/YmxPnEzh9VygXouj4gkFUTKA==",
|
||||
"version": "4.5.6",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.5.6.tgz",
|
||||
"integrity": "sha512-o/hPOtbU9oX507lIqon+UvPYqpx3mHc8cV3QemSBTXwkG8gSQSK6UKvXcE/DcleU3+A59XTUHyCvZ5qGy8xVAg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30000960",
|
||||
"electron-to-chromium": "^1.3.124",
|
||||
"node-releases": "^1.1.14"
|
||||
"caniuse-lite": "^1.0.30000963",
|
||||
"electron-to-chromium": "^1.3.127",
|
||||
"node-releases": "^1.1.17"
|
||||
}
|
||||
},
|
||||
"buffer-from": {
|
||||
@@ -1143,9 +1110,9 @@
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30000963",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000963.tgz",
|
||||
"integrity": "sha512-n4HUiullc7Lw0LyzpeLa2ffP8KxFBGdxqD/8G3bSL6oB758hZ2UE2CVK+tQN958tJIi0/tfpjAc67aAtoHgnrQ==",
|
||||
"version": "1.0.30000967",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000967.tgz",
|
||||
"integrity": "sha512-rUBIbap+VJfxTzrM4akJ00lkvVb5/n5v3EGXfWzSH5zT8aJmGzjA8HWhJ4U6kCpzxozUSnB+yvAYDRPY6mRpgQ==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
@@ -1159,26 +1126,6 @@
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz",
|
||||
"integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "^2.0.0",
|
||||
"async-each": "^1.0.1",
|
||||
"braces": "^2.3.2",
|
||||
"fsevents": "^1.2.7",
|
||||
"glob-parent": "^3.1.0",
|
||||
"inherits": "^2.0.3",
|
||||
"is-binary-path": "^1.0.0",
|
||||
"is-glob": "^4.0.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"path-is-absolute": "^1.0.0",
|
||||
"readdirp": "^2.2.1",
|
||||
"upath": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"class-utils": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
|
||||
@@ -1432,9 +1379,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.127",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.127.tgz",
|
||||
"integrity": "sha512-1o25iFRf/dbgauTWalEzmD1EmRN3a2CzP/K7UVpYLEBduk96LF0FyUdCcf4Ry2mAWJ1VxyblFjC93q6qlLwA2A==",
|
||||
"version": "1.3.133",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.133.tgz",
|
||||
"integrity": "sha512-lyoC8aoqbbDqsprb6aPdt9n3DpOZZzdz/T4IZKsR0/dkZIxnJVUjjcpOSwA66jPRIOyDAamCTAUqweU05kKNSg==",
|
||||
"dev": true
|
||||
},
|
||||
"error-ex": {
|
||||
@@ -1648,535 +1595,6 @@
|
||||
"map-cache": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz",
|
||||
"integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"nan": "^2.12.1",
|
||||
"node-pre-gyp": "^0.12.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"are-we-there-yet": {
|
||||
"version": "1.1.5",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^2.0.6"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "1.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"delegates": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "1.2.5",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minipass": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"aproba": "^1.0.3",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^1.0.1",
|
||||
"strip-ansi": "^3.0.1",
|
||||
"wide-align": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": 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"
|
||||
}
|
||||
},
|
||||
"has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"ignore-walk": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "1.2.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minipass": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"needle": {
|
||||
"version": "2.3.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"debug": "^4.1.0",
|
||||
"iconv-lite": "^0.4.4",
|
||||
"sax": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.12.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"detect-libc": "^1.0.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
"needle": "^2.2.1",
|
||||
"nopt": "^4.0.1",
|
||||
"npm-packlist": "^1.1.6",
|
||||
"npmlog": "^4.0.2",
|
||||
"rc": "^1.2.7",
|
||||
"rimraf": "^2.6.1",
|
||||
"semver": "^5.3.0",
|
||||
"tar": "^4"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "4.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"abbrev": "1",
|
||||
"osenv": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"npm-bundled": {
|
||||
"version": "1.0.6",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"npm-packlist": {
|
||||
"version": "1.4.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ignore-walk": "^3.0.1",
|
||||
"npm-bundled": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"npmlog": {
|
||||
"version": "4.1.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"are-we-there-yet": "~1.1.2",
|
||||
"console-control-strings": "~1.1.0",
|
||||
"gauge": "~2.7.3",
|
||||
"set-blocking": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"os-homedir": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"osenv": {
|
||||
"version": "0.1.5",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"os-homedir": "^1.0.0",
|
||||
"os-tmpdir": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.6",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
"strip-ansi": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"tar": {
|
||||
"version": "4.4.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"fs-minipass": "^1.2.5",
|
||||
"minipass": "^2.3.4",
|
||||
"minizlib": "^1.1.1",
|
||||
"mkdirp": "^0.5.0",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"string-width": "^1.0.2 || 2"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
@@ -2189,27 +1607,6 @@
|
||||
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
|
||||
"dev": true
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
@@ -2308,26 +1705,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"html-minifier": {
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz",
|
||||
"integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
|
||||
"integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camel-case": "3.0.x",
|
||||
"clean-css": "4.2.x",
|
||||
"commander": "2.17.x",
|
||||
"he": "1.2.x",
|
||||
"param-case": "2.1.x",
|
||||
"relateurl": "0.2.x",
|
||||
"uglify-js": "3.4.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.17.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
|
||||
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
|
||||
"dev": true
|
||||
}
|
||||
"camel-case": "^3.0.0",
|
||||
"clean-css": "^4.2.1",
|
||||
"commander": "^2.19.0",
|
||||
"he": "^1.2.0",
|
||||
"param-case": "^2.1.1",
|
||||
"relateurl": "^0.2.7",
|
||||
"uglify-js": "^3.5.1"
|
||||
}
|
||||
},
|
||||
"http-link-header": {
|
||||
@@ -2377,15 +1766,6 @@
|
||||
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
|
||||
"dev": true
|
||||
},
|
||||
"is-binary-path": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
|
||||
"integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"binary-extensions": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-buffer": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||
@@ -2449,21 +1829,6 @@
|
||||
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
|
||||
"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=",
|
||||
"dev": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
@@ -2778,13 +2143,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.13.2",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
|
||||
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"nanomatch": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||
@@ -2825,9 +2183,9 @@
|
||||
}
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.17",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.17.tgz",
|
||||
"integrity": "sha512-/SCjetyta1m7YXLgtACZGDYJdCSIBAWorDWkGCGZlydP2Ll7J48l7j/JxNYZ+xsgSPbWfdulVS/aY+GdjUsQ7Q==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.18.tgz",
|
||||
"integrity": "sha512-/mnVgm6u/8OwlIsoyRXtTI0RfQcxZoAZbdwyXap0EeWwcOpDDymyCHM2/aR9XKmHXrvizHoPAOs0pcbiJ6RUaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"semver": "^5.3.0"
|
||||
@@ -2845,12 +2203,6 @@
|
||||
"validate-npm-package-license": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
},
|
||||
"npm-run-all": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz",
|
||||
@@ -2953,18 +2305,6 @@
|
||||
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
|
||||
"dev": true
|
||||
},
|
||||
"path-dirname": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
|
||||
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
|
||||
"dev": true
|
||||
},
|
||||
"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=",
|
||||
"dev": true
|
||||
},
|
||||
"path-key": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
|
||||
@@ -3059,17 +2399,6 @@
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
|
||||
"integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.11",
|
||||
"micromatch": "^3.1.10",
|
||||
"readable-stream": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"regenerate": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
|
||||
@@ -3159,12 +2488,6 @@
|
||||
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
|
||||
"dev": true
|
||||
},
|
||||
"remove-trailing-separator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
|
||||
"integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
|
||||
"dev": true
|
||||
},
|
||||
"repeat-element": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
|
||||
@@ -3205,13 +2528,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"rollup": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.10.1.tgz",
|
||||
"integrity": "sha512-pW353tmBE7QP622ITkGxtqF0d5gSRCVPD9xqM+fcPjudeZfoXMFW2sCzsTe2TU/zU1xamIjiS9xuFCPVT9fESw==",
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.11.3.tgz",
|
||||
"integrity": "sha512-81MR7alHcFKxgWzGfG7jSdv+JQxSOIOD/Fa3iNUmpzbd7p+V19e1l9uffqT8/7YAHgGOzmoPGN3Fx3L2ptOf5g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "0.0.39",
|
||||
"@types/node": "^11.13.5",
|
||||
"@types/node": "^11.13.9",
|
||||
"acorn": "^6.1.1"
|
||||
}
|
||||
},
|
||||
@@ -3307,14 +2630,14 @@
|
||||
}
|
||||
},
|
||||
"sapper": {
|
||||
"version": "0.26.0-alpha.12",
|
||||
"resolved": "https://registry.npmjs.org/sapper/-/sapper-0.26.0-alpha.12.tgz",
|
||||
"integrity": "sha512-NEXr6Eu5jawY76N5IEQhKMKhcZW6+42E2alH4J8DxFMmOI7Gi2nlwCQ2jcZv7q/S+zMP+OSuqE44c94A5u1H8Q==",
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/sapper/-/sapper-0.27.1.tgz",
|
||||
"integrity": "sha512-RH0K1uQ3zJ1IXvowxr2SuboGXV69q22KaPMhhoM5VNDv9fsUlVHtluZE8WTcGxckiO2L1xFfgM7v/aINkSZpcw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"html-minifier": "^3.5.21",
|
||||
"html-minifier": "^4.0.0",
|
||||
"http-link-header": "^1.0.2",
|
||||
"shimport": "0.0.14",
|
||||
"shimport": "^1.0.0",
|
||||
"sourcemap-codec": "^1.4.4",
|
||||
"string-hash": "^1.1.3"
|
||||
}
|
||||
@@ -3388,15 +2711,15 @@
|
||||
}
|
||||
},
|
||||
"shimport": {
|
||||
"version": "0.0.14",
|
||||
"resolved": "https://registry.npmjs.org/shimport/-/shimport-0.0.14.tgz",
|
||||
"integrity": "sha512-JfzpHhyZ6CYuPYUSitFNw2/IxWEABLN1chs9xDHnn4tPXXCmfIn1METCmIl8Vwvm2FZGW7b8WB4zMCKaJEhY6A==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shimport/-/shimport-1.0.0.tgz",
|
||||
"integrity": "sha512-XKd+39voZT1rFR1ct+pr8sFSYAW6IvM3LeF87FrgcGHc/uSZ4GfOZVA42LE5LXFOpTWgmDC5sS8DNDtiV4Vd4Q==",
|
||||
"dev": true
|
||||
},
|
||||
"sirv": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-0.2.5.tgz",
|
||||
"integrity": "sha512-q7F1VElkj/WPrXwWsdHK9gqi2rd94oaMQc3VtN8N1TNHrKxlsd7hGsPFiOIodRxre8eHCV1XK1iqC1BDaZ+IKA==",
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-0.4.2.tgz",
|
||||
"integrity": "sha512-dQbZnsMaIiTQPZmbGmktz+c74zt/hyrJEB4tdp2Jj0RNv9J6B/OWR5RyrZEvIn9fyh9Zlg2OlE2XzKz6wMKGAw==",
|
||||
"requires": {
|
||||
"@polka/url": "^0.5.0",
|
||||
"mime": "^2.3.1"
|
||||
@@ -3662,9 +2985,9 @@
|
||||
}
|
||||
},
|
||||
"svelte": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.1.0.tgz",
|
||||
"integrity": "sha512-b5TyzV7Dx1ijN4QPNarhKq5rX98QHDmi18nF0G8KV3d5KX3Jj98Yu4+tzM97ktnXcfoVJmvONvPaX1ZI0mr8Dw==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.2.2.tgz",
|
||||
"integrity": "sha512-B6ePpPodCQodVFDwv/uByaWs0KvXojaXZiB/db1x7WXbyxgLJ8xZF3j1lk5M83Ox0AWlX0fQnCTAaZmfT6xqdw==",
|
||||
"dev": true
|
||||
},
|
||||
"terser": {
|
||||
@@ -3755,21 +3078,15 @@
|
||||
}
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.4.10",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
||||
"integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==",
|
||||
"version": "3.5.15",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.15.tgz",
|
||||
"integrity": "sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "~2.19.0",
|
||||
"commander": "~2.20.0",
|
||||
"source-map": "~0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
|
||||
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -3881,12 +3198,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"upath": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz",
|
||||
"integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"upper-case": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
|
||||
|
||||
@@ -15,30 +15,29 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@polka/send": "^0.4.0",
|
||||
"compression": "^1.7.1",
|
||||
"compression": "^1.7.4",
|
||||
"highlight.js": "^9.15.6",
|
||||
"marked": "^0.6.2",
|
||||
"polka": "^0.5.2",
|
||||
"prismjs": "^1.16.0",
|
||||
"sirv": "^0.2.0"
|
||||
"sirv": "^0.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-transform-runtime": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"@sveltejs/site-kit": "^1.0.1",
|
||||
"chokidar": "^2.0.4",
|
||||
"@babel/core": "^7.4.4",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-transform-runtime": "^7.4.4",
|
||||
"@babel/preset-env": "^7.4.4",
|
||||
"@babel/runtime": "^7.4.4",
|
||||
"@sveltejs/site-kit": "^1.0.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"rollup": "^1.0.0",
|
||||
"rollup-plugin-babel": "^4.0.2",
|
||||
"rollup-plugin-commonjs": "^9.1.6",
|
||||
"rollup-plugin-node-resolve": "^4.0.0",
|
||||
"rollup-plugin-replace": "^2.0.0",
|
||||
"rollup-plugin-svelte": "^5.0.1",
|
||||
"rollup": "^1.11.3",
|
||||
"rollup-plugin-babel": "^4.3.2",
|
||||
"rollup-plugin-commonjs": "^9.3.4",
|
||||
"rollup-plugin-node-resolve": "^4.2.3",
|
||||
"rollup-plugin-replace": "^2.2.0",
|
||||
"rollup-plugin-svelte": "^5.0.3",
|
||||
"rollup-plugin-terser": "^4.0.4",
|
||||
"sapper": "alpha",
|
||||
"svelte": "^3.1.0"
|
||||
"sapper": "^0.27.1",
|
||||
"svelte": "^3.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<p>Please try reloading the page.</p>
|
||||
{/if}
|
||||
|
||||
<p>If the error persists, please drop by <a href="https://discord.gg/yy75DKs">Discord chatroom</a> and let us know, or raise an issue on <a href="https://github.com/sveltejs/svelte">GitHub</a>. Thanks!</p>
|
||||
<p>If the error persists, please drop by <a href="https://svelte.dev/chat">Discord chatroom</a> and let us know, or raise an issue on <a href="https://github.com/sveltejs/svelte">GitHub</a>. Thanks!</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<h1>It looks like you're offline</h1>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script>
|
||||
import { page } from '@sapper/app';
|
||||
import { stores } from '@sapper/app';
|
||||
import { Icons, Icon, Nav, NavItem } from '@sveltejs/site-kit';
|
||||
|
||||
export let segment;
|
||||
|
||||
const { page } = stores();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -26,7 +28,7 @@
|
||||
|
||||
<NavItem external="https://svelte.dev">Svelte</NavItem>
|
||||
|
||||
<NavItem external="https://discord.gg/yy75DKs" title="Discord Chat">
|
||||
<NavItem external="https://svelte.dev/chat" title="Discord Chat">
|
||||
<Icon name="message-square"/>
|
||||
</NavItem>
|
||||
|
||||
@@ -37,4 +39,4 @@
|
||||
|
||||
<main>
|
||||
<slot></slot>
|
||||
</main>
|
||||
</main>
|
||||
|
||||
@@ -49,13 +49,16 @@
|
||||
|
||||
<div style="grid-area: start; display: flex; flex-direction: column; min-width: 0" slot="how">
|
||||
<pre class="language-bash" style="margin: 0 0 1em 0; min-width: 0; min-height: 0">
|
||||
npx degit sveltejs/template my-svelte-project
|
||||
cd my-svelte-project
|
||||
# for Rollup
|
||||
npx degit "sveltejs/sapper-template#rollup" my-app
|
||||
# for webpack
|
||||
npx degit "sveltejs/sapper-template#webpack" my-app
|
||||
cd my-app
|
||||
|
||||
npm install
|
||||
npm run dev & open http://localhost:5000
|
||||
npm run dev & open http://localhost:3000
|
||||
</pre>
|
||||
|
||||
<p class="cta"><a rel="prefetch" href="docs">Learn Sapper</a></p>
|
||||
</div>
|
||||
</Blurb>
|
||||
</Blurb>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<html lang='en' class="theme-default typo-default">
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width;initial-scale=1.0'>
|
||||
<meta name='theme-color' content='#ff3e00'>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1.0'>
|
||||
<meta name='theme-color' content='#159794'>
|
||||
|
||||
%sapper.base%
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#ff3e00",
|
||||
"theme_color": "#159794",
|
||||
"name": "Sapper",
|
||||
"short_name": "Sapper",
|
||||
"display": "minimal-ui",
|
||||
|
||||
@@ -19,6 +19,7 @@ type Opts = {
|
||||
static?: string;
|
||||
legacy?: boolean;
|
||||
bundler?: 'rollup' | 'webpack';
|
||||
ext?: string;
|
||||
oncompile?: ({ type, result }: { type: string, result: CompileResult }) => void;
|
||||
};
|
||||
|
||||
@@ -32,6 +33,7 @@ export async function build({
|
||||
|
||||
bundler,
|
||||
legacy = false,
|
||||
ext,
|
||||
oncompile = noop
|
||||
}: Opts = {}) {
|
||||
bundler = validate_bundler(bundler);
|
||||
@@ -68,7 +70,7 @@ export async function build({
|
||||
|
||||
fs.writeFileSync(`${dest}/template.html`, minify_html(template));
|
||||
|
||||
const manifest_data = create_manifest_data(routes);
|
||||
const manifest_data = create_manifest_data(routes, ext);
|
||||
|
||||
// create src/node_modules/@sapper/app.mjs and server.mjs
|
||||
create_app({
|
||||
|
||||
113
src/api/dev.ts
113
src/api/dev.ts
@@ -28,7 +28,8 @@ type Opts = {
|
||||
hot?: boolean,
|
||||
'devtools-port'?: number,
|
||||
bundler?: 'rollup' | 'webpack',
|
||||
port?: number
|
||||
port?: number,
|
||||
ext: string
|
||||
};
|
||||
|
||||
export function dev(opts: Opts) {
|
||||
@@ -47,7 +48,7 @@ class Watcher extends EventEmitter {
|
||||
}
|
||||
port: number;
|
||||
closed: boolean;
|
||||
|
||||
|
||||
dev_port: number;
|
||||
live: boolean;
|
||||
hot: boolean;
|
||||
@@ -67,6 +68,7 @@ class Watcher extends EventEmitter {
|
||||
unique_warnings: Set<string>;
|
||||
unique_errors: Set<string>;
|
||||
}
|
||||
ext: string;
|
||||
|
||||
constructor({
|
||||
cwd = '.',
|
||||
@@ -80,7 +82,8 @@ class Watcher extends EventEmitter {
|
||||
hot,
|
||||
'devtools-port': devtools_port,
|
||||
bundler,
|
||||
port = +process.env.PORT
|
||||
port = +process.env.PORT,
|
||||
ext
|
||||
}: Opts) {
|
||||
super();
|
||||
|
||||
@@ -95,7 +98,7 @@ class Watcher extends EventEmitter {
|
||||
output: path.resolve(cwd, output),
|
||||
static: path.resolve(cwd, static_files)
|
||||
};
|
||||
|
||||
this.ext = ext;
|
||||
this.port = port;
|
||||
this.closed = false;
|
||||
|
||||
@@ -161,7 +164,7 @@ class Watcher extends EventEmitter {
|
||||
let manifest_data: ManifestData;
|
||||
|
||||
try {
|
||||
manifest_data = create_manifest_data(routes);
|
||||
manifest_data = create_manifest_data(routes, this.ext);
|
||||
create_app({
|
||||
bundler: this.bundler,
|
||||
manifest_data,
|
||||
@@ -189,7 +192,7 @@ class Watcher extends EventEmitter {
|
||||
},
|
||||
() => {
|
||||
try {
|
||||
const new_manifest_data = create_manifest_data(routes);
|
||||
const new_manifest_data = create_manifest_data(routes, this.ext);
|
||||
create_app({
|
||||
bundler: this.bundler,
|
||||
manifest_data, // TODO is this right? not new_manifest_data?
|
||||
@@ -206,15 +209,19 @@ class Watcher extends EventEmitter {
|
||||
});
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
fs.watch(`${src}/template.html`, () => {
|
||||
this.dev_server.send({
|
||||
action: 'reload'
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
if (this.live) {
|
||||
this.filewatchers.push(
|
||||
fs.watch(`${src}/template.html`, () => {
|
||||
this.dev_server.send({
|
||||
action: 'reload'
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
let deferred = new Deferred();
|
||||
|
||||
// TODO watch the configs themselves?
|
||||
@@ -252,7 +259,7 @@ class Watcher extends EventEmitter {
|
||||
this.dev_server.send({
|
||||
status: 'completed'
|
||||
});
|
||||
} else {
|
||||
} else if (this.live) {
|
||||
this.dev_server.send({
|
||||
action: 'reload'
|
||||
});
|
||||
@@ -267,48 +274,54 @@ class Watcher extends EventEmitter {
|
||||
});
|
||||
};
|
||||
|
||||
const start_server = () => {
|
||||
// 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/server.js`, [], {
|
||||
cwd: process.cwd(),
|
||||
env: Object.assign({
|
||||
PORT: this.port
|
||||
}, process.env),
|
||||
stdio: ['ipc'],
|
||||
execArgv
|
||||
});
|
||||
|
||||
this.proc.stdout.on('data', chunk => {
|
||||
this.emit('stdout', chunk);
|
||||
});
|
||||
|
||||
this.proc.stderr.on('data', chunk => {
|
||||
this.emit('stderr', chunk);
|
||||
});
|
||||
|
||||
this.proc.on('message', message => {
|
||||
if (message.__sapper__ && message.event === 'basepath') {
|
||||
this.emit('basepath', {
|
||||
basepath: message.basepath
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.proc.on('exit', emitFatal);
|
||||
};
|
||||
|
||||
if (this.proc) {
|
||||
this.proc.removeListener('exit', emitFatal);
|
||||
this.proc.kill();
|
||||
this.proc.on('exit', restart);
|
||||
this.proc.on('exit', () => {
|
||||
start_server();
|
||||
restart();
|
||||
});
|
||||
} else {
|
||||
start_server();
|
||||
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/server.js`, [], {
|
||||
cwd: process.cwd(),
|
||||
env: Object.assign({
|
||||
PORT: this.port
|
||||
}, process.env),
|
||||
stdio: ['ipc'],
|
||||
execArgv
|
||||
});
|
||||
|
||||
this.proc.stdout.on('data', chunk => {
|
||||
this.emit('stdout', chunk);
|
||||
});
|
||||
|
||||
this.proc.stderr.on('data', chunk => {
|
||||
this.emit('stderr', chunk);
|
||||
});
|
||||
|
||||
this.proc.on('message', message => {
|
||||
if (message.__sapper__ && message.event === 'basepath') {
|
||||
this.emit('basepath', {
|
||||
basepath: message.basepath
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.proc.on('exit', emitFatal);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -132,19 +132,23 @@ async function _export({
|
||||
if (seen.has(pathname)) return;
|
||||
seen.add(pathname);
|
||||
|
||||
const timeout_deferred = new Deferred();
|
||||
const the_timeout = setTimeout(() => {
|
||||
timeout_deferred.reject(new Error(`Timed out waiting for ${url.href}`));
|
||||
}, timeout);
|
||||
const r = await q.add(async () => {
|
||||
const timeout_deferred = new Deferred();
|
||||
const the_timeout = setTimeout(() => {
|
||||
timeout_deferred.reject(new Error(`Timed out waiting for ${url.href}`));
|
||||
}, timeout);
|
||||
|
||||
const r = await Promise.race([
|
||||
q.add(() => fetch(url.href, {
|
||||
redirect: 'manual'
|
||||
})),
|
||||
timeout_deferred.promise
|
||||
]);
|
||||
const r = await Promise.race([
|
||||
fetch(url.href, {
|
||||
redirect: 'manual'
|
||||
}),
|
||||
timeout_deferred.promise
|
||||
]);
|
||||
|
||||
clearTimeout(the_timeout); // prevent it hanging at the end
|
||||
clearTimeout(the_timeout); // prevent it hanging at the end
|
||||
|
||||
return r;
|
||||
}) as Response;
|
||||
|
||||
let type = r.headers.get('Content-Type');
|
||||
|
||||
@@ -152,6 +156,8 @@ async function _export({
|
||||
|
||||
const range = ~~(r.status / 100);
|
||||
|
||||
let tasks = [];
|
||||
|
||||
if (range === 2) {
|
||||
if (type === 'text/html') {
|
||||
// parse link rel=preload headers and embed them in the HTML
|
||||
@@ -173,8 +179,6 @@ async function _export({
|
||||
let match;
|
||||
let pattern = /<a ([\s\S]+?)>/gm;
|
||||
|
||||
let promise;
|
||||
|
||||
while (match = pattern.exec(cleaned)) {
|
||||
const attrs = match[1];
|
||||
const href = get_href(attrs);
|
||||
@@ -183,12 +187,10 @@ async function _export({
|
||||
const url = resolve(base.href, href);
|
||||
|
||||
if (url.protocol === protocol && url.host === host) {
|
||||
promise = handle(url);
|
||||
tasks.push(handle(url));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await promise;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,10 +201,12 @@ async function _export({
|
||||
type = 'text/html';
|
||||
body = `<script>window.location.href = "${location.replace(origin, '')}"</script>`;
|
||||
|
||||
await handle(resolve(root.href, location));
|
||||
tasks.push(handle(resolve(root.href, location)));
|
||||
}
|
||||
|
||||
save(pathname, r.status, type, body);
|
||||
|
||||
await Promise.all(tasks);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
23
src/cli.ts
23
src/cli.ts
@@ -31,6 +31,7 @@ prog.command('dev')
|
||||
.option('--static', 'Static files directory', 'static')
|
||||
.option('--output', 'Sapper output directory', 'src/node_modules/@sapper')
|
||||
.option('--build-dir', 'Development build directory', '__sapper__/dev')
|
||||
.option('--ext', 'Custom Route Extension', '.svelte .html')
|
||||
.action(async (opts: {
|
||||
port: number,
|
||||
open: boolean,
|
||||
@@ -43,7 +44,8 @@ prog.command('dev')
|
||||
routes: string,
|
||||
static: string,
|
||||
output: string,
|
||||
'build-dir': string
|
||||
'build-dir': string,
|
||||
ext: string
|
||||
}) => {
|
||||
const { dev } = await import('./api/dev');
|
||||
|
||||
@@ -59,7 +61,8 @@ prog.command('dev')
|
||||
'dev-port': opts['dev-port'],
|
||||
live: opts.live,
|
||||
hot: opts.hot,
|
||||
bundler: opts.bundler
|
||||
bundler: opts.bundler,
|
||||
ext: opts.ext
|
||||
});
|
||||
|
||||
let first = true;
|
||||
@@ -151,6 +154,7 @@ prog.command('build [dest]')
|
||||
.option('--src', 'Source directory', 'src')
|
||||
.option('--routes', 'Routes directory', 'src/routes')
|
||||
.option('--output', 'Sapper output directory', 'src/node_modules/@sapper')
|
||||
.option('--ext', 'Custom Route Extension', '.svelte .html')
|
||||
.example(`build custom-dir -p 4567`)
|
||||
.action(async (dest = '__sapper__/build', opts: {
|
||||
port: string,
|
||||
@@ -159,12 +163,13 @@ prog.command('build [dest]')
|
||||
cwd: string,
|
||||
src: string,
|
||||
routes: string,
|
||||
output: string
|
||||
output: string,
|
||||
ext: string
|
||||
}) => {
|
||||
console.log(`> Building...`);
|
||||
|
||||
try {
|
||||
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, dest);
|
||||
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, dest, opts.ext);
|
||||
|
||||
const launcher = path.resolve(dest, 'index.js');
|
||||
|
||||
@@ -199,6 +204,7 @@ prog.command('export [dest]')
|
||||
.option('--static', 'Static files directory', 'static')
|
||||
.option('--output', 'Sapper output directory', 'src/node_modules/@sapper')
|
||||
.option('--build-dir', 'Intermediate build directory', '__sapper__/build')
|
||||
.option('--ext', 'Custom Route Extension', '.svelte .html')
|
||||
.action(async (dest = '__sapper__/export', opts: {
|
||||
build: boolean,
|
||||
legacy: boolean,
|
||||
@@ -212,11 +218,12 @@ prog.command('export [dest]')
|
||||
static: string,
|
||||
output: string,
|
||||
'build-dir': string,
|
||||
ext: string
|
||||
}) => {
|
||||
try {
|
||||
if (opts.build) {
|
||||
console.log(`> Building...`);
|
||||
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, opts['build-dir']);
|
||||
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, opts['build-dir'], opts.ext);
|
||||
console.error(`\n> Built in ${elapsed(start)}`);
|
||||
}
|
||||
|
||||
@@ -265,7 +272,8 @@ async function _build(
|
||||
src: string,
|
||||
routes: string,
|
||||
output: string,
|
||||
dest: string
|
||||
dest: string,
|
||||
ext: string
|
||||
) {
|
||||
const { build } = await import('./api/build');
|
||||
|
||||
@@ -276,7 +284,8 @@ async function _build(
|
||||
src,
|
||||
routes,
|
||||
dest,
|
||||
|
||||
ext,
|
||||
output,
|
||||
oncompile: event => {
|
||||
let banner = `built ${event.type}`;
|
||||
let c = (txt: string) => colors.cyan(txt);
|
||||
|
||||
@@ -251,11 +251,11 @@ function generate_app(manifest_data: ManifestData, path_to_routes: string) {
|
||||
|
||||
let l = max_depth;
|
||||
|
||||
let pyramid = `<svelte:component this={level${l}.component} {...level${l}.props}/>`;
|
||||
let pyramid = `<svelte:component this="{level${l}.component}" {...level${l}.props}/>`;
|
||||
|
||||
while (l-- > 1) {
|
||||
pyramid = `
|
||||
<svelte:component this={level${l}.component} segment={segments[${l}]} {...level${l}.props}>
|
||||
<svelte:component this="{level${l}.component}" segment="{segments[${l}]}" {...level${l}.props}>
|
||||
{#if level${l + 1}}
|
||||
${pyramid.replace(/\n/g, '\n\t\t\t\t\t')}
|
||||
{/if}
|
||||
@@ -281,7 +281,7 @@ function generate_app(manifest_data: ManifestData, path_to_routes: string) {
|
||||
setContext(CONTEXT_KEY, stores);
|
||||
</script>
|
||||
|
||||
<Layout segment={segments[0]} {...level0.props}>
|
||||
<Layout segment="{segments[0]}" {...level0.props}>
|
||||
{#if error}
|
||||
<Error {error} {status}/>
|
||||
{:else}
|
||||
|
||||
@@ -4,9 +4,10 @@ import svelte from 'svelte/compiler';
|
||||
import { Page, PageComponent, ServerRoute, ManifestData } from '../interfaces';
|
||||
import { posixify, reserved_words } from '../utils';
|
||||
|
||||
const component_extensions = ['.svelte', '.html']; // TODO make this configurable (to include e.g. .svelte.md?)
|
||||
export default function create_manifest_data(cwd: string, extensions: string = '.svelte .html'): ManifestData {
|
||||
|
||||
const component_extensions = extensions.split(' ');
|
||||
|
||||
export default function create_manifest_data(cwd: string): ManifestData {
|
||||
// TODO remove in a future version
|
||||
if (!fs.existsSync(cwd)) {
|
||||
throw new Error(`As of Sapper 0.21, the routes/ directory should become src/routes/`);
|
||||
@@ -25,6 +26,19 @@ export default function create_manifest_data(cwd: string): ManifestData {
|
||||
return false;
|
||||
}
|
||||
|
||||
function find_layout(file_name: string, component_name: string, dir: string = '') {
|
||||
const ext = component_extensions.find((ext) => fs.existsSync(path.join(cwd, dir, `${file_name}${ext}`)));
|
||||
const file = posixify(path.join(dir, `${file_name}${ext}`))
|
||||
|
||||
return ext
|
||||
? {
|
||||
name: component_name,
|
||||
file: file,
|
||||
has_preload: has_preload(file)
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
const components: PageComponent[] = [];
|
||||
const pages: Page[] = [];
|
||||
const server_routes: ServerRoute[] = [];
|
||||
@@ -61,15 +75,19 @@ export default function create_manifest_data(cwd: string): ManifestData {
|
||||
const is_dir = fs.statSync(resolved).isDirectory();
|
||||
|
||||
const ext = path.extname(basename);
|
||||
|
||||
if (basename[0] === '_') return null;
|
||||
if (basename[0] === '.' && basename !== '.well-known') return null;
|
||||
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);
|
||||
: basename.slice(0, -ext.length);
|
||||
|
||||
const parts = get_parts(segment);
|
||||
const is_index = is_dir ? false : basename.startsWith('index.');
|
||||
const is_page = component_extensions.indexOf(ext) !== -1;
|
||||
const route_suffix = basename.slice(basename.indexOf('.'), -ext.length);
|
||||
|
||||
parts.forEach(part => {
|
||||
if (/\]\[/.test(part.content)) {
|
||||
@@ -88,39 +106,35 @@ export default function create_manifest_data(cwd: string): ManifestData {
|
||||
file: posixify(file),
|
||||
is_dir,
|
||||
is_index,
|
||||
is_page
|
||||
is_page,
|
||||
route_suffix
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort(comparator);
|
||||
|
||||
items.forEach(item => {
|
||||
if (item.basename[0] === '_') return;
|
||||
|
||||
if (item.basename[0] === '.') {
|
||||
if (item.file !== '.well-known') return;
|
||||
}
|
||||
|
||||
const segments = parent_segments.slice();
|
||||
|
||||
if (item.is_index && segments.length > 0) {
|
||||
const last_segment = segments[segments.length - 1].slice();
|
||||
const suffix = item.basename
|
||||
.slice(0, -path.extname(item.basename).length).
|
||||
replace('index', '');
|
||||
if (item.is_index) {
|
||||
if (item.route_suffix) {
|
||||
if (segments.length > 0) {
|
||||
const last_segment = segments[segments.length - 1].slice();
|
||||
const last_part = last_segment[last_segment.length - 1];
|
||||
|
||||
if (suffix) {
|
||||
const last_part = last_segment[last_segment.length - 1];
|
||||
if (last_part.dynamic) {
|
||||
last_segment.push({ dynamic: false, content: suffix });
|
||||
if (last_part.dynamic) {
|
||||
last_segment.push({ dynamic: false, content: item.route_suffix });
|
||||
} else {
|
||||
last_segment[last_segment.length - 1] = {
|
||||
dynamic: false,
|
||||
content: `${last_part.content}${item.route_suffix}`
|
||||
};
|
||||
}
|
||||
|
||||
segments[segments.length - 1] = last_segment;
|
||||
} else {
|
||||
last_segment[last_segment.length - 1] = {
|
||||
dynamic: false,
|
||||
content: `${last_part.content}${suffix}`
|
||||
};
|
||||
segments.push(item.parts);
|
||||
}
|
||||
|
||||
segments[segments.length - 1] = last_segment;
|
||||
}
|
||||
} else {
|
||||
segments.push(item.parts);
|
||||
@@ -130,16 +144,7 @@ export default function create_manifest_data(cwd: string): ManifestData {
|
||||
params.push(...item.parts.filter(p => p.dynamic).map(p => p.content));
|
||||
|
||||
if (item.is_dir) {
|
||||
const ext = component_extensions.find((ext: string) => {
|
||||
const index = path.join(dir, item.basename, `_layout${ext}`);
|
||||
return fs.existsSync(index);
|
||||
});
|
||||
|
||||
const component = ext && {
|
||||
name: `${get_slug(item.file)}__layout`,
|
||||
file: `${item.file}/_layout${ext}`,
|
||||
has_preload: has_preload(`${item.file}/_layout${ext}`)
|
||||
};
|
||||
const component = find_layout('_layout', `${get_slug(item.file)}__layout`, item.file);
|
||||
|
||||
if (component) components.push(component);
|
||||
|
||||
@@ -154,8 +159,6 @@ export default function create_manifest_data(cwd: string): ManifestData {
|
||||
}
|
||||
|
||||
else if (item.is_page) {
|
||||
const is_index = item.basename === `index${item.ext}`;
|
||||
|
||||
const component = {
|
||||
name: get_slug(item.file),
|
||||
file: item.file,
|
||||
@@ -164,22 +167,20 @@ export default function create_manifest_data(cwd: string): ManifestData {
|
||||
|
||||
components.push(component);
|
||||
|
||||
const parts = (is_index && stack[stack.length - 1] === null)
|
||||
const parts = (item.is_index && stack[stack.length - 1] === null)
|
||||
? stack.slice(0, -1).concat({ component, params })
|
||||
: stack.concat({ component, params })
|
||||
|
||||
const page = {
|
||||
pattern: get_pattern(is_index ? parent_segments : segments, true),
|
||||
pages.push({
|
||||
pattern: get_pattern(segments, true),
|
||||
parts
|
||||
};
|
||||
|
||||
pages.push(page);
|
||||
});
|
||||
}
|
||||
|
||||
else {
|
||||
server_routes.push({
|
||||
name: `route_${get_slug(item.file)}`,
|
||||
pattern: get_pattern(segments, false),
|
||||
pattern: get_pattern(segments, !item.route_suffix),
|
||||
file: item.file,
|
||||
params: params
|
||||
});
|
||||
@@ -187,23 +188,8 @@ export default function create_manifest_data(cwd: string): ManifestData {
|
||||
});
|
||||
}
|
||||
|
||||
const root_ext = component_extensions.find(ext => fs.existsSync(path.join(cwd, `_layout${ext}`)));
|
||||
const root = root_ext
|
||||
? {
|
||||
name: 'main',
|
||||
file: `_layout${root_ext}`,
|
||||
has_preload: has_preload(`_layout${root_ext}`)
|
||||
}
|
||||
: default_layout;
|
||||
|
||||
const error_ext = component_extensions.find(ext => fs.existsSync(path.join(cwd, `_error${ext}`)));
|
||||
const error = error_ext
|
||||
? {
|
||||
name: 'error',
|
||||
file: `_error${error_ext}`,
|
||||
has_preload: has_preload(`_error${error_ext}`)
|
||||
}
|
||||
: default_error;
|
||||
const root = find_layout('_layout', 'main') || default_layout;
|
||||
const error = find_layout('_error', 'error') || default_error;
|
||||
|
||||
walk(cwd, [], [], []);
|
||||
|
||||
@@ -249,7 +235,7 @@ type Part = {
|
||||
spread?: boolean;
|
||||
};
|
||||
|
||||
function is_spead(path: string) {
|
||||
function is_spread(path: string) {
|
||||
const spread_pattern = /\[\.{3}/g;
|
||||
return spread_pattern.test(path)
|
||||
}
|
||||
@@ -259,9 +245,9 @@ function comparator(
|
||||
b: { basename: string, parts: Part[], file: string, is_index: boolean }
|
||||
) {
|
||||
if (a.is_index !== b.is_index) {
|
||||
if (a.is_index) return is_spead(a.file) ? 1 : -1;
|
||||
if (a.is_index) return is_spread(a.file) ? 1 : -1;
|
||||
|
||||
return is_spead(b.file) ? -1 : 1;
|
||||
return is_spread(b.file) ? -1 : 1;
|
||||
}
|
||||
|
||||
const max = Math.max(a.parts.length, b.parts.length);
|
||||
@@ -334,7 +320,6 @@ function get_parts(part: string): Part[] {
|
||||
function get_slug(file: string) {
|
||||
let name = file
|
||||
.replace(/[\\\/]index/, '')
|
||||
.replace(/_default([\/\\index])?\.html$/, 'index')
|
||||
.replace(/[\/\\]/g, '_')
|
||||
.replace(/\.\w+$/, '')
|
||||
.replace(/\[([^(]+)(?:\([^(]+\))?\]/, '$$$1')
|
||||
@@ -347,19 +332,19 @@ function get_slug(file: string) {
|
||||
}
|
||||
|
||||
function get_pattern(segments: Part[][], add_trailing_slash: boolean) {
|
||||
return new RegExp(
|
||||
`^` +
|
||||
segments.map(segment => {
|
||||
return '\\/' + segment.map(part => {
|
||||
return part.dynamic
|
||||
? part.qualifier || part.spread ? '(.+)' : '([^\\/]+?)'
|
||||
: encodeURI(part.content.normalize())
|
||||
.replace(/\?/g, '%3F')
|
||||
.replace(/#/g, '%23')
|
||||
.replace(/%5B/g, '[')
|
||||
.replace(/%5D/g, ']');
|
||||
}).join('');
|
||||
}).join('') +
|
||||
(add_trailing_slash ? '\\\/?$' : '$')
|
||||
);
|
||||
const path = segments.map(segment => {
|
||||
return segment.map(part => {
|
||||
return part.dynamic
|
||||
? part.qualifier || (part.spread ? '(.+)' : '([^\\/]+?)')
|
||||
: encodeURI(part.content.normalize())
|
||||
.replace(/\?/g, '%3F')
|
||||
.replace(/#/g, '%23')
|
||||
.replace(/%5B/g, '[')
|
||||
.replace(/%5D/g, ']');
|
||||
}).join('');
|
||||
}).join('\\/');
|
||||
|
||||
const trailing = add_trailing_slash && segments.length ? '\\/?$' : '$';
|
||||
|
||||
return new RegExp(`^\\/${path}${trailing}`);
|
||||
}
|
||||
|
||||
@@ -1,49 +1,77 @@
|
||||
import * as path from 'path';
|
||||
import puppeteer from 'puppeteer';
|
||||
import * as ports from 'port-authority';
|
||||
import { fork, ChildProcess } from 'child_process';
|
||||
import { AddressInfo } from 'net';
|
||||
|
||||
import { wait } from '../utils'
|
||||
|
||||
const DEFAULT_ENTRY = '__sapper__/build/server/server.js';
|
||||
const DELAY = parseInt(process.env.SAPPER_TEST_DELAY) || 50;
|
||||
|
||||
declare const start: () => Promise<void>;
|
||||
declare const prefetchRoutes: () => Promise<void>;
|
||||
declare const prefetch: (href: string) => Promise<void>;
|
||||
declare const goto: (href: string) => Promise<void>;
|
||||
|
||||
type StartOpts = {
|
||||
requestInterceptor?: (interceptedRequst: puppeteer.Request) => any
|
||||
};
|
||||
|
||||
export class AppRunner {
|
||||
cwd: string;
|
||||
entry: string;
|
||||
port: number;
|
||||
proc: ChildProcess;
|
||||
exiting: boolean;
|
||||
terminate: Promise<any>;
|
||||
|
||||
server: ChildProcess;
|
||||
address: AddressInfo;
|
||||
base: string;
|
||||
messages: any[];
|
||||
errors: Error[];
|
||||
|
||||
browser: puppeteer.Browser;
|
||||
page: puppeteer.Page;
|
||||
|
||||
constructor(cwd: string, entry: string) {
|
||||
this.cwd = cwd;
|
||||
this.entry = path.join(cwd, entry);
|
||||
sapper = {
|
||||
start: () => this.page.evaluate(() => start()).then(() => void 0),
|
||||
prefetchRoutes: () => this.page.evaluate(() => prefetchRoutes()).then(() => void 0),
|
||||
prefetch: (href: string) => this.page.evaluate((href: string) => prefetch(href), href).then(() => void 0),
|
||||
goto: (href: string) => this.page.evaluate((href: string) => goto(href), href).then(() => void 0)
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.messages = [];
|
||||
this.errors = [];
|
||||
}
|
||||
|
||||
async start({ requestInterceptor }: StartOpts = {}) {
|
||||
this.port = await ports.find(3000);
|
||||
async start(cwd: string, entry: string = DEFAULT_ENTRY) {
|
||||
const server_listening = deferred();
|
||||
const server_closed = deferred();
|
||||
const browser_closed = deferred();
|
||||
|
||||
this.proc = fork(this.entry, [], {
|
||||
cwd: this.cwd,
|
||||
env: {
|
||||
PORT: String(this.port)
|
||||
this.terminate = Promise.all([server_closed, browser_closed]);
|
||||
|
||||
this.server = fork(path.join(cwd, entry), [], { cwd });
|
||||
this.server.on('exit', () => {
|
||||
server_listening.reject();
|
||||
server_closed.settle(this.exiting);
|
||||
});
|
||||
this.server.on('message', message => {
|
||||
if (!message.__sapper__) return;
|
||||
|
||||
switch (message.event) {
|
||||
case 'listening':
|
||||
this.address = message.address;
|
||||
this.base = `http://localhost:${this.address.port}`;
|
||||
|
||||
server_listening.resolve();
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
this.errors.push(Object.assign(new Error(), message.error));
|
||||
break;
|
||||
|
||||
default:
|
||||
this.messages.push(message);
|
||||
}
|
||||
});
|
||||
|
||||
this.proc.on('message', message => {
|
||||
if (!message.__sapper__) return;
|
||||
this.messages.push(message);
|
||||
});
|
||||
|
||||
this.browser = await puppeteer.launch({ args: ['--no-sandbox'] });
|
||||
this.browser.on('disconnected', () => browser_closed.settle(this.exiting));
|
||||
|
||||
this.page = await this.browser.newPage();
|
||||
this.page.on('console', msg => {
|
||||
@@ -54,25 +82,28 @@ export class AppRunner {
|
||||
}
|
||||
});
|
||||
|
||||
if (requestInterceptor) {
|
||||
await this.page.setRequestInterception(true);
|
||||
this.page.on('request', requestInterceptor);
|
||||
}
|
||||
await server_listening;
|
||||
|
||||
return {
|
||||
page: this.page,
|
||||
base: `http://localhost:${this.port}`,
|
||||
|
||||
// helpers
|
||||
start: () => this.page.evaluate(() => start()).then(() => void 0),
|
||||
prefetchRoutes: () => this.page.evaluate(() => prefetchRoutes()).then(() => void 0),
|
||||
prefetch: (href: string) => this.page.evaluate((href: string) => prefetch(href), href).then(() => void 0),
|
||||
goto: (href: string) => this.page.evaluate((href: string) => goto(href), href).then(() => void 0),
|
||||
title: () => this.page.$eval('h1', node => node.textContent).then(serializable => String(serializable))
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
capture(fn: () => any): Promise<string[]> {
|
||||
load(url: string) {
|
||||
if (url[0] === '/') {
|
||||
url = `${this.base}${url}`;
|
||||
}
|
||||
|
||||
return this.page.goto(url);
|
||||
}
|
||||
|
||||
text(selector: string) {
|
||||
return this.page.$eval(selector, node => node.textContent);
|
||||
}
|
||||
|
||||
wait(extra_ms: number = 0) {
|
||||
return wait(DELAY + extra_ms);
|
||||
}
|
||||
|
||||
capture_requests(fn: () => any): Promise<string[]> {
|
||||
return new Promise((fulfil, reject) => {
|
||||
const requests: string[] = [];
|
||||
const pending: Set<string> = new Set();
|
||||
@@ -120,13 +151,55 @@ export class AppRunner {
|
||||
});
|
||||
}
|
||||
|
||||
end() {
|
||||
return Promise.all([
|
||||
this.browser.close(),
|
||||
new Promise(fulfil => {
|
||||
this.proc.once('exit', fulfil);
|
||||
this.proc.kill();
|
||||
})
|
||||
]);
|
||||
async intercept_requests(interceptor: (request: puppeteer.Request) => void, fn: () => any): Promise<void> {
|
||||
const unique_interceptor = request => interceptor(request);
|
||||
|
||||
this.page.prependListener('request', unique_interceptor);
|
||||
await this.page.setRequestInterception(true);
|
||||
|
||||
const result = await Promise.resolve(fn());
|
||||
|
||||
await this.page.setRequestInterception(false);
|
||||
this.page.removeListener('request', unique_interceptor);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
end() {
|
||||
this.exiting = true;
|
||||
|
||||
this.server.kill();
|
||||
this.browser.close();
|
||||
|
||||
return this.terminate;
|
||||
}
|
||||
}
|
||||
|
||||
interface Deferred<T> extends Promise<T> {
|
||||
resolve: (value?: T | PromiseLike<T>) => void;
|
||||
reject: (reason?: any) => void;
|
||||
settle: (result?: boolean) => void;
|
||||
}
|
||||
|
||||
function settle<T>(this: Deferred<T>, result: boolean) {
|
||||
if (result) {
|
||||
this.resolve();
|
||||
} else {
|
||||
this.reject();
|
||||
}
|
||||
}
|
||||
|
||||
function deferred<T>() {
|
||||
let resolve, reject;
|
||||
|
||||
const deferred = new Promise((_resolve, _reject) => {
|
||||
resolve = _resolve;
|
||||
reject = _reject;
|
||||
}) as Deferred<T>;
|
||||
|
||||
deferred.resolve = resolve;
|
||||
deferred.reject = reject;
|
||||
deferred.settle = settle;
|
||||
|
||||
return deferred;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
<script context="module">
|
||||
export function preload({ query, params }) {
|
||||
const { rest } = params;
|
||||
return { rest };
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { stores } from '@sapper/app';
|
||||
const { page } = stores();
|
||||
export let rest;
|
||||
</script>
|
||||
|
||||
<h1>{$page.params.rest.join(',')}</h1>
|
||||
<h2>{rest.join(',')}</h2>
|
||||
|
||||
<a href="xyz/abc/qwe/deep.json">deep</a>
|
||||
<a href="xyz/abc">back</a>
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
<script context="module">
|
||||
export function preload({ query, params }) {
|
||||
const { rest } = params;
|
||||
return { rest };
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { stores } from '@sapper/app';
|
||||
const { page } = stores();
|
||||
export let rest;
|
||||
</script>
|
||||
|
||||
<h1>{$page.params.rest.join(',')}</h1>
|
||||
<h2>{rest.join(',')}</h2>
|
||||
|
||||
<a href="xyz/abc/deep">deep</a>
|
||||
<a href="xyz/abc">deep</a>
|
||||
<a href="xyz/abc/def">deep</a>
|
||||
<a href="xyz/abc/def/ghi">deep</a>
|
||||
|
||||
8
test/apps/basics/src/routes/[id([0-9]+)].svelte
Normal file
8
test/apps/basics/src/routes/[id([0-9]+)].svelte
Normal file
@@ -0,0 +1,8 @@
|
||||
<script>
|
||||
import { stores } from '@sapper/app';
|
||||
const { page } = stores();
|
||||
</script>
|
||||
|
||||
<h1>Regexp page {$page.params.id}</h1>
|
||||
|
||||
<a href="regexp/234">nested regexp route</a>
|
||||
@@ -3,4 +3,6 @@
|
||||
const { page } = stores();
|
||||
</script>
|
||||
|
||||
<h1>{$page.params.slug.toUpperCase()}</h1>
|
||||
<h1>{$page.params.slug}</h1>
|
||||
|
||||
<a href="234">regexp route</a>
|
||||
|
||||
6
test/apps/basics/src/routes/regexp/[id([0-9]+)].svelte
Normal file
6
test/apps/basics/src/routes/regexp/[id([0-9]+)].svelte
Normal file
@@ -0,0 +1,6 @@
|
||||
<script>
|
||||
import { stores } from '@sapper/app';
|
||||
const { page } = stores();
|
||||
</script>
|
||||
|
||||
<h1>Nested regexp page {$page.params.id}</h1>
|
||||
14
test/apps/basics/src/routes/skipped/[one]/[two].svelte
Normal file
14
test/apps/basics/src/routes/skipped/[one]/[two].svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script context="module">
|
||||
export function preload({ params }) {
|
||||
return params;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export let one;
|
||||
export let two;
|
||||
</script>
|
||||
|
||||
<h1>{one}:{two}</h1>
|
||||
|
||||
<a href="skipped/y/1">skipped/y/1</a>
|
||||
@@ -1,8 +1,9 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
polka()
|
||||
const app = polka()
|
||||
.use(sapper.middleware())
|
||||
.listen(PORT);
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
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';
|
||||
import {build} from '../../../api';
|
||||
import {AppRunner} from '../AppRunner';
|
||||
|
||||
declare let deleted: { id: number };
|
||||
declare let el: any;
|
||||
|
||||
function get(url: string, opts?: any): Promise<{ headers: Record<string, string>, body: string }> {
|
||||
type Response = { headers: http.IncomingHttpHeaders, body: string };
|
||||
|
||||
function get(url: string, opts: http.RequestOptions = {}): Promise<Response> {
|
||||
return new Promise((fulfil, reject) => {
|
||||
const req = http.get(url, opts || {}, res => {
|
||||
const req = http.get(url, opts, res => {
|
||||
res.on('error', reject);
|
||||
|
||||
let body = '';
|
||||
res.on('data', chunk => body += chunk);
|
||||
res.on('end', () => {
|
||||
fulfil({
|
||||
headers: res.headers as Record<string, string>,
|
||||
headers: res.headers,
|
||||
body
|
||||
});
|
||||
});
|
||||
@@ -30,114 +30,104 @@ function get(url: string, opts?: any): Promise<{ headers: Record<string, string>
|
||||
describe('basics', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
let runner: AppRunner;
|
||||
let page: puppeteer.Page;
|
||||
let base: string;
|
||||
|
||||
// helpers
|
||||
let start: () => Promise<void>;
|
||||
let prefetchRoutes: () => Promise<void>;
|
||||
let prefetch: (href: string) => Promise<void>;
|
||||
let goto: (href: string) => Promise<void>;
|
||||
let title: () => Promise<string>;
|
||||
let r: AppRunner;
|
||||
|
||||
// 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());
|
||||
before('build app', () => build({ cwd: __dirname }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('serves /', async () => {
|
||||
await page.goto(base);
|
||||
await r.load('/');
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'Great success!'
|
||||
);
|
||||
});
|
||||
|
||||
it('serves /?', async () => {
|
||||
await page.goto(`${base}?`);
|
||||
await r.load('/?');
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'Great success!'
|
||||
);
|
||||
});
|
||||
|
||||
it('serves static route', async () => {
|
||||
await page.goto(`${base}/a`);
|
||||
await r.load('/a');
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'a'
|
||||
);
|
||||
});
|
||||
|
||||
it('serves static route from dir/index.html file', async () => {
|
||||
await page.goto(`${base}/b`);
|
||||
await r.load('/b');
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'b'
|
||||
);
|
||||
});
|
||||
|
||||
it('serves dynamic route', async () => {
|
||||
await page.goto(`${base}/test-slug`);
|
||||
await r.load('/test-slug');
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
'TEST-SLUG'
|
||||
await r.text('h1'),
|
||||
'test-slug'
|
||||
);
|
||||
});
|
||||
|
||||
it('navigates to a new page without reloading', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
const requests: string[] = await runner.capture(async () => {
|
||||
await page.click('a[href="a"]');
|
||||
const requests: string[] = await r.capture_requests(async () => {
|
||||
await r.page.click('a[href="a"]');
|
||||
await r.wait();
|
||||
});
|
||||
|
||||
assert.deepEqual(requests, []);
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'a'
|
||||
);
|
||||
});
|
||||
|
||||
it('navigates programmatically', async () => {
|
||||
await page.goto(`${base}/a`);
|
||||
await start();
|
||||
|
||||
await goto('b');
|
||||
await r.load('/a');
|
||||
await r.sapper.start();
|
||||
await r.sapper.goto('b');
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'b'
|
||||
);
|
||||
});
|
||||
|
||||
it('prefetches programmatically', async () => {
|
||||
await page.goto(`${base}/a`);
|
||||
await start();
|
||||
await r.load(`/a`);
|
||||
await r.sapper.start();
|
||||
|
||||
const requests = await runner.capture(() => prefetch('b'));
|
||||
const requests = await r.capture_requests(() => r.sapper.prefetch('b'));
|
||||
|
||||
assert.equal(requests.length, 2);
|
||||
assert.equal(requests[1], `${base}/b.json`);
|
||||
assert.equal(requests[1], `${r.base}/b.json`);
|
||||
});
|
||||
|
||||
// TODO equivalent test for a webpack app
|
||||
it('sets Content-Type, Link...modulepreload, and Cache-Control headers', async () => {
|
||||
const { headers } = await get(base);
|
||||
const { headers } = await get(r.base);
|
||||
|
||||
assert.equal(
|
||||
headers['content-type'],
|
||||
@@ -157,162 +147,193 @@ describe('basics', function() {
|
||||
});
|
||||
|
||||
it('calls a delete handler', async () => {
|
||||
await page.goto(`${base}/delete-test`);
|
||||
await start();
|
||||
await r.load('/delete-test');
|
||||
await r.sapper.start();
|
||||
|
||||
await page.click('.del');
|
||||
await page.waitForFunction(() => deleted);
|
||||
await r.page.click('.del');
|
||||
await r.page.waitForFunction(() => deleted);
|
||||
|
||||
assert.equal(await page.evaluate(() => deleted.id), 42);
|
||||
assert.equal(await r.page.evaluate(() => deleted.id), 42);
|
||||
});
|
||||
|
||||
it('hydrates initial route', async () => {
|
||||
await page.goto(base);
|
||||
await r.load('/');
|
||||
|
||||
await page.evaluate(() => {
|
||||
await r.page.evaluate(() => {
|
||||
el = document.querySelector('.hydrate-test');
|
||||
});
|
||||
|
||||
await start();
|
||||
await r.sapper.start();
|
||||
|
||||
assert.ok(await page.evaluate(() => {
|
||||
assert.ok(await r.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 r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click(`[href="ambiguous/ok.json"]`);
|
||||
await wait(50);
|
||||
await r.page.click('[href="ambiguous/ok.json"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.body.textContent),
|
||||
await r.text('body'),
|
||||
'ok'
|
||||
);
|
||||
});
|
||||
|
||||
it('allows reserved words as route names', async () => {
|
||||
await page.goto(`${base}/const`);
|
||||
await start();
|
||||
await r.load('/const');
|
||||
await r.sapper.start();
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'reserved words are okay as routes'
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts value-less query string parameter on server', async () => {
|
||||
await page.goto(`${base}/echo-query?message`);
|
||||
await r.load('/echo-query?message');
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'{"message":""}'
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts value-less query string parameter on client', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('a[href="echo-query?message"]')
|
||||
await r.page.click('a[href="echo-query?message"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'{"message":""}'
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts duplicated query string parameter on server', async () => {
|
||||
await page.goto(`${base}/echo-query?p=one&p=two`);
|
||||
await r.load('/echo-query?p=one&p=two');
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'{"p":["one","two"]}'
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts duplicated query string parameter on client', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('a[href="echo-query?p=one&p=two"]')
|
||||
await r.page.click('a[href="echo-query?p=one&p=two"]')
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'{"p":["one","two"]}'
|
||||
);
|
||||
});
|
||||
|
||||
// skipped because Nightmare doesn't seem to focus the <a> correctly
|
||||
it('resets the active element after navigation', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="a"]');
|
||||
await wait(50);
|
||||
await r.page.click('[href="a"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.activeElement.nodeName),
|
||||
await r.page.evaluate(() => document.activeElement.nodeName),
|
||||
'BODY'
|
||||
);
|
||||
});
|
||||
|
||||
it('replaces %sapper.xxx% tags safely', async () => {
|
||||
await page.goto(`${base}/unsafe-replacement`);
|
||||
await start();
|
||||
await r.load('/unsafe-replacement');
|
||||
await r.sapper.start();
|
||||
|
||||
const html = String(await page.evaluate(() => document.body.innerHTML));
|
||||
const html = String(await r.page.evaluate(() => document.body.innerHTML));
|
||||
assert.equal(html.indexOf('%sapper'), -1);
|
||||
});
|
||||
|
||||
it('navigates between routes with empty parts', async () => {
|
||||
await page.goto(`${base}/dirs/foo`);
|
||||
await start();
|
||||
await r.load('/dirs/foo');
|
||||
await r.sapper.start();
|
||||
|
||||
assert.equal(await title(), 'foo');
|
||||
|
||||
await page.click('[href="dirs/bar"]');
|
||||
await wait(50);
|
||||
assert.equal(await title(), 'bar');
|
||||
assert.equal(await r.text('h1'), 'foo');
|
||||
await r.page.click('[href="dirs/bar"]');
|
||||
await r.wait();
|
||||
assert.equal(await r.text('h1'), 'bar');
|
||||
});
|
||||
|
||||
it('navigates to ...rest', async () => {
|
||||
await page.goto(`${base}/abc/xyz`);
|
||||
await start();
|
||||
it('navigates to ...rest', async () => {
|
||||
await r.load('/abc/xyz');
|
||||
await r.sapper.start();
|
||||
|
||||
assert.equal(await title(), 'abc,xyz');
|
||||
|
||||
await page.click('[href="xyz/abc/deep"]');
|
||||
await wait(50);
|
||||
assert.equal(await title(), 'xyz,abc');
|
||||
|
||||
await page.click(`[href="xyz/abc/qwe/deep.json"]`);
|
||||
await wait(50);
|
||||
assert.equal(await r.text('h1'), 'abc,xyz');
|
||||
await r.page.click('[href="xyz/abc/def/ghi"]');
|
||||
await r.wait();
|
||||
assert.equal(await r.text('h1'), 'xyz,abc,def,ghi');
|
||||
assert.equal(await r.text('h2'), 'xyz,abc,def,ghi');
|
||||
await r.page.click('[href="xyz/abc/def"]');
|
||||
await r.wait();
|
||||
assert.equal(await r.text('h1'), 'xyz,abc,def');
|
||||
assert.equal(await r.text('h2'), 'xyz,abc,def');
|
||||
await r.page.click('[href="xyz/abc/def"]');
|
||||
await r.wait();
|
||||
assert.equal(await r.text('h1'), 'xyz,abc,def');
|
||||
assert.equal(await r.text('h2'), 'xyz,abc,def');
|
||||
await r.page.click('[href="xyz/abc"]');
|
||||
await r.wait();
|
||||
assert.equal(await r.text('h1'), 'xyz,abc');
|
||||
assert.equal(await r.text('h2'), 'xyz,abc');
|
||||
await r.page.click('[href="xyz/abc/deep"]');
|
||||
await r.wait();
|
||||
assert.equal(await r.text('h1'), 'xyz,abc');
|
||||
assert.equal(await r.text('h2'), 'xyz,abc');
|
||||
await r.page.click('[href="xyz/abc/qwe/deep.json"]');
|
||||
await r.wait();
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.body.textContent),
|
||||
await r.text('body'),
|
||||
'xyz,abc,qwe'
|
||||
);
|
||||
});
|
||||
|
||||
it('navigates between dynamic routes with same segments', async () => {
|
||||
await page.goto(`${base}/dirs/bar/xyz`);
|
||||
await start();
|
||||
|
||||
assert.equal(await title(), 'A page');
|
||||
|
||||
await page.click('[href="dirs/foo/xyz"]');
|
||||
await wait(50);
|
||||
assert.equal(await title(), 'B page');
|
||||
});
|
||||
|
||||
|
||||
it('navigates between dynamic routes with same segments', async () => {
|
||||
await r.load('/dirs/bar/xyz');
|
||||
await r.sapper.start();
|
||||
|
||||
assert.equal(await r.text('h1'), 'A page');
|
||||
|
||||
await r.page.click('[href="dirs/foo/xyz"]');
|
||||
await r.wait();
|
||||
assert.equal(await r.text('h1'), 'B page');
|
||||
});
|
||||
|
||||
it('find regexp routes', async () => {
|
||||
await r.load('/qwe');
|
||||
await r.sapper.start();
|
||||
|
||||
assert.equal(await r.text('h1'), 'qwe');
|
||||
|
||||
await r.page.click('[href="234"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(await r.text('h1'), 'Regexp page 234');
|
||||
|
||||
await r.page.click('[href="regexp/234"]');
|
||||
await r.wait();
|
||||
assert.equal(await r.text('h1'), 'Nested regexp page 234');
|
||||
});
|
||||
|
||||
it('runs server route handlers before page handlers, if they match', async () => {
|
||||
const json = await get(`${base}/middleware`, {
|
||||
const json = await get(`${r.base}/middleware`, {
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
@@ -320,8 +341,26 @@ describe('basics', function() {
|
||||
|
||||
assert.equal(json.body, '{"json":true}');
|
||||
|
||||
const html = await get(`${base}/middleware`);
|
||||
const html = await get(`${r.base}/middleware`);
|
||||
|
||||
assert.ok(html.body.indexOf('<h1>HTML</h1>') !== -1);
|
||||
});
|
||||
|
||||
it('invalidates page when a segment is skipped', async () => {
|
||||
await r.load('/skipped/x/1');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await r.page.click('a[href="skipped/y/1"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await r.text('h1'),
|
||||
'y:1'
|
||||
);
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
});
|
||||
|
||||
42
test/apps/common.js
Normal file
42
test/apps/common.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const { NODE_ENV, PORT } = process.env;
|
||||
|
||||
export const dev = NODE_ENV === 'development';
|
||||
|
||||
export function start(app) {
|
||||
const port = parseInt(PORT) || 0;
|
||||
|
||||
app.listen(port, () => {
|
||||
const address = app.server.address();
|
||||
|
||||
process.env.PORT = address.port;
|
||||
|
||||
send({
|
||||
__sapper__: true,
|
||||
event: 'listening',
|
||||
address
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const properties = ['name', 'message', 'stack', 'code', 'lineNumber', 'fileName'];
|
||||
|
||||
function send(message) {
|
||||
process.send && process.send(message);
|
||||
}
|
||||
|
||||
function send_error(error) {
|
||||
send({
|
||||
__sapper__: true,
|
||||
event: 'error',
|
||||
error: properties.reduce((object, key) => ({...object, [key]: error[key]}), {})
|
||||
})
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
send_error(reason);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
send_error(err);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -1,13 +1,14 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
polka()
|
||||
const app = 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);
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -1,59 +1,54 @@
|
||||
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<void>;
|
||||
let prefetchRoutes: () => Promise<void>;
|
||||
let r: AppRunner;
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await build({ cwd: __dirname });
|
||||
|
||||
runner = new AppRunner(__dirname, '__sapper__/build/server/server.js');
|
||||
({ base, page, start, prefetchRoutes } = await runner.start());
|
||||
before('build app', () => build({ cwd: __dirname }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('sends cookies when using this.fetch with credentials: "include"', async () => {
|
||||
await page.goto(`${base}/credentials?creds=include`);
|
||||
await r.load('/credentials?creds=include');
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'a: 1, b: 2, max-age: undefined'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not send cookies when using this.fetch without credentials', async () => {
|
||||
await page.goto(`${base}/credentials`);
|
||||
await r.load('/credentials');
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'unauthorized'
|
||||
);
|
||||
});
|
||||
|
||||
it('delegates to fetch on the client', async () => {
|
||||
await page.goto(base)
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/')
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="credentials?creds=include"]');
|
||||
await wait(50);
|
||||
await r.page.click('[href="credentials?creds=include"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'a: 1, b: 2, max-age: undefined'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
polka()
|
||||
.use(sapper.middleware())
|
||||
.listen(PORT);
|
||||
const app = polka()
|
||||
.use(sapper.middleware());
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -1,78 +1,61 @@
|
||||
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<void>;
|
||||
let prefetchRoutes: () => Promise<void>;
|
||||
let prefetch: (href: string) => Promise<void>;
|
||||
let goto: (href: string) => Promise<void>;
|
||||
let title: () => Promise<string>;
|
||||
let r: AppRunner;
|
||||
|
||||
// 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());
|
||||
before('build app', () => build({ cwd: __dirname }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('includes critical CSS with server render', async () => {
|
||||
await page.goto(base);
|
||||
await r.load('/');
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => {
|
||||
const h1 = document.querySelector('h1');
|
||||
return getComputedStyle(h1).color;
|
||||
}),
|
||||
await r.page.$eval('h1', node => getComputedStyle(node).color),
|
||||
'rgb(255, 0, 0)'
|
||||
);
|
||||
});
|
||||
|
||||
it('loads CSS when navigating client-side', async () => {
|
||||
await page.goto(base);
|
||||
await r.load('/');
|
||||
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click(`[href="foo"]`);
|
||||
await wait(50);
|
||||
await r.page.click(`[href="foo"]`);
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => {
|
||||
const h1 = document.querySelector('h1');
|
||||
return getComputedStyle(h1).color;
|
||||
}),
|
||||
await r.page.$eval('h1', node => getComputedStyle(node).color),
|
||||
'rgb(0, 0, 255)'
|
||||
);
|
||||
});
|
||||
|
||||
it('loads CSS for a lazily-rendered component', async () => {
|
||||
await page.goto(base);
|
||||
await r.load('/');
|
||||
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click(`[href="bar"]`);
|
||||
await wait(50);
|
||||
await r.page.click(`[href="bar"]`);
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => {
|
||||
const h1 = document.querySelector('h1');
|
||||
return getComputedStyle(h1).color;
|
||||
}),
|
||||
await r.page.$eval('h1', node => getComputedStyle(node).color),
|
||||
'rgb(0, 128, 0)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
});
|
||||
|
||||
60
test/apps/custom-extension/rollup.config.js
Normal file
60
test/apps/custom-extension/rollup.config.js
Normal file
@@ -0,0 +1,60 @@
|
||||
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({
|
||||
extensions: ['.whokilledthemuffinman', '.jesuslivesineveryone', '.mdx', '.svelte', '.html'],
|
||||
dev,
|
||||
hydratable: true,
|
||||
emitCss: true
|
||||
}),
|
||||
resolve()
|
||||
]
|
||||
},
|
||||
|
||||
server: {
|
||||
input: config.server.input(),
|
||||
output: config.server.output(),
|
||||
plugins: [
|
||||
replace({
|
||||
'process.browser': false,
|
||||
'process.env.NODE_ENV': JSON.stringify(mode)
|
||||
}),
|
||||
svelte({
|
||||
extensions: ['.whokilledthemuffinman', '.jesuslivesineveryone', '.mdx', '.svelte', '.html'],
|
||||
generate: 'ssr',
|
||||
dev
|
||||
}),
|
||||
resolve({
|
||||
preferBuiltins: true
|
||||
})
|
||||
],
|
||||
external: ['sirv', 'polka']
|
||||
},
|
||||
|
||||
serviceworker: {
|
||||
input: config.serviceworker.input(),
|
||||
output: config.serviceworker.output(),
|
||||
plugins: [
|
||||
resolve(),
|
||||
replace({
|
||||
'process.browser': true,
|
||||
'process.env.NODE_ENV': JSON.stringify(mode)
|
||||
})
|
||||
]
|
||||
}
|
||||
};
|
||||
9
test/apps/custom-extension/src/client.js
Normal file
9
test/apps/custom-extension/src/client.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as sapper from '@sapper/app';
|
||||
|
||||
window.start = () => sapper.start({
|
||||
target: document.querySelector('#sapper')
|
||||
});
|
||||
|
||||
window.prefetchRoutes = () => sapper.prefetchRoutes();
|
||||
window.prefetch = href => sapper.prefetch(href);
|
||||
window.goto = href => sapper.goto(href);
|
||||
6
test/apps/custom-extension/src/routes/[slug].mdx
Normal file
6
test/apps/custom-extension/src/routes/[slug].mdx
Normal file
@@ -0,0 +1,6 @@
|
||||
<script>
|
||||
import { stores } from '@sapper/app';
|
||||
const { page } = stores();
|
||||
</script>
|
||||
|
||||
<h1>{$page.params.slug.toUpperCase()}</h1>
|
||||
3
test/apps/custom-extension/src/routes/_error.svelte
Normal file
3
test/apps/custom-extension/src/routes/_error.svelte
Normal file
@@ -0,0 +1,3 @@
|
||||
<h1>hi</h1>
|
||||
|
||||
<p>hi</p>
|
||||
1
test/apps/custom-extension/src/routes/a.svelte
Normal file
1
test/apps/custom-extension/src/routes/a.svelte
Normal file
@@ -0,0 +1 @@
|
||||
<h1>a</h1>
|
||||
@@ -0,0 +1 @@
|
||||
<h1>Tremendous!</h1>
|
||||
@@ -0,0 +1,8 @@
|
||||
<h1>Great success!</h1>
|
||||
|
||||
<a href="a">a</a>
|
||||
<a href="ambiguous/ok.json">ok</a>
|
||||
<a href="echo-query?message">ok</a>
|
||||
<a href="echo-query?p=one&p=two">ok</a>
|
||||
|
||||
<div class='hydrate-test'></div>
|
||||
@@ -0,0 +1 @@
|
||||
<h1>Bazooom!</h1>
|
||||
9
test/apps/custom-extension/src/server.js
Normal file
9
test/apps/custom-extension/src/server.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
import { start } from '../../common.js';
|
||||
|
||||
const app = polka()
|
||||
.use(sapper.middleware())
|
||||
|
||||
start(app);
|
||||
81
test/apps/custom-extension/src/service-worker.js
Normal file
81
test/apps/custom-extension/src/service-worker.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import * as sapper from '@sapper/service-worker';
|
||||
|
||||
const ASSETS = `cache${sapper.timestamp}`;
|
||||
|
||||
// `app.shell` is an array of all the files generated by webpack,
|
||||
// `app.files` is an array of everything in the `static` directory
|
||||
const to_cache = sapper.shell.concat(sapper.files);
|
||||
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
|
||||
/*
|
||||
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${sapper.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;
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
14
test/apps/custom-extension/src/template.html
Normal file
14
test/apps/custom-extension/src/template.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
|
||||
%sapper.base%
|
||||
%sapper.styles%
|
||||
%sapper.head%
|
||||
</head>
|
||||
<body>
|
||||
<div id='sapper'>%sapper.html%</div>
|
||||
%sapper.scripts%
|
||||
</body>
|
||||
</html>
|
||||
61
test/apps/custom-extension/test.ts
Normal file
61
test/apps/custom-extension/test.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import * as assert from 'assert';
|
||||
import { build } from '../../../api';
|
||||
import { AppRunner } from '../AppRunner';
|
||||
|
||||
describe('custom extensions', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
let r: AppRunner;
|
||||
|
||||
// hooks
|
||||
before('build app', () => build({ cwd: __dirname , ext: '.jesuslivesineveryone .whokilledthemuffinman .mdx .svelte' }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => r && r.end());
|
||||
|
||||
|
||||
|
||||
it('works with arbitrary extensions', async () => {
|
||||
await r.load(`/`);
|
||||
|
||||
|
||||
assert.equal(
|
||||
await r.text('h1'),
|
||||
'Great success!'
|
||||
);
|
||||
});
|
||||
|
||||
it('works with other arbitrary extensions', async () => {
|
||||
await r.load(`/const`);
|
||||
|
||||
assert.equal(
|
||||
await r.text('h1'),
|
||||
'Tremendous!'
|
||||
);
|
||||
|
||||
await r.load(`/a`);
|
||||
|
||||
assert.equal(
|
||||
await r.text('h1'),
|
||||
'a'
|
||||
);
|
||||
|
||||
await r.load(`/test-slug`);
|
||||
|
||||
assert.equal(
|
||||
await r.text('h1'),
|
||||
'TEST-SLUG'
|
||||
);
|
||||
|
||||
await r.load(`/unsafe-replacement`);
|
||||
|
||||
assert.equal(
|
||||
await r.text('h1'),
|
||||
'Bazooom!'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
polka()
|
||||
.use(sapper.middleware())
|
||||
.listen(PORT);
|
||||
const app = polka()
|
||||
.use(sapper.middleware());
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -1,68 +1,63 @@
|
||||
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<void>;
|
||||
let prefetchRoutes: () => Promise<void>;
|
||||
let r: AppRunner;
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await build({ cwd: __dirname });
|
||||
|
||||
runner = new AppRunner(__dirname, '__sapper__/build/server/server.js');
|
||||
({ base, page, start, prefetchRoutes } = await runner.start());
|
||||
before('build app', () => build({ cwd: __dirname }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('encodes routes', async () => {
|
||||
await page.goto(`${base}/fünke`);
|
||||
await r.load('/fünke');
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
`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&föo=bar&=baz&tel=%2B123456789`);
|
||||
await r.load('/echo/page/encöded?message=hëllö+wörld&föo=bar&=baz&tel=%2B123456789');
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'encöded {"message":"hëllö wörld","föo":"bar","":"baz","tel":"+123456789"}'
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes req.params and req.query for client-rendered pages', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('a');
|
||||
await wait(50);
|
||||
await r.page.click('a');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'encöded {"message":"hëllö wörld","föo":"bar","":"baz","tel":"+123456789"}'
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes req.params for server routes', async () => {
|
||||
await page.goto(`${base}/echo/server-route/encöded`);
|
||||
await r.load('/echo/server-route/encöded');
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'encöded'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<h1>{error ? error.message : 'No error here'}</h1>
|
||||
|
||||
<script>
|
||||
export let error;
|
||||
</script>
|
||||
|
||||
<h1>{error ? error.message : 'No error here'}</h1>
|
||||
|
||||
<a href="enhance-your-calm">Enhance your calm</a>
|
||||
@@ -1,8 +1,9 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
polka()
|
||||
.use(sapper.middleware())
|
||||
.listen(PORT);
|
||||
const app = polka()
|
||||
.use(sapper.middleware());
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as assert from 'assert';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { build } from '../../../api';
|
||||
import { AppRunner } from '../AppRunner';
|
||||
import { wait } from '../../utils';
|
||||
@@ -7,142 +6,137 @@ import { wait } from '../../utils';
|
||||
describe('errors', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
let runner: AppRunner;
|
||||
let page: puppeteer.Page;
|
||||
let base: string;
|
||||
|
||||
// helpers
|
||||
let start: () => Promise<void>;
|
||||
let prefetchRoutes: () => Promise<void>;
|
||||
let title: () => Promise<string>;
|
||||
let r: AppRunner;
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await build({ cwd: __dirname });
|
||||
|
||||
runner = new AppRunner(__dirname, '__sapper__/build/server/server.js');
|
||||
({ base, page, start, prefetchRoutes, title } = await runner.start());
|
||||
before('build app', () => build({ cwd: __dirname }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('handles missing route on server', async () => {
|
||||
await page.goto(`${base}/nope`);
|
||||
await r.load('/nope');
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'404'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles missing route on client', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
|
||||
await page.click('[href="nope"]');
|
||||
await wait(50);
|
||||
await r.page.click('[href="nope"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'404'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles explicit 4xx on server', async () => {
|
||||
await page.goto(`${base}/blog/nope`);
|
||||
await r.load('/blog/nope');
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'404'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles explicit 4xx on client', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="blog/nope"]');
|
||||
await wait(50);
|
||||
await r.page.click('[href="blog/nope"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'404'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles error on server', async () => {
|
||||
await page.goto(`${base}/throw`);
|
||||
await r.load('/throw');
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'500'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles error on client', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="throw"]');
|
||||
await wait(50);
|
||||
await r.page.click('[href="throw"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'500'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not serve error page for explicit non-page errors', async () => {
|
||||
await page.goto(`${base}/nope.json`);
|
||||
await r.load('/nope.json');
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.body.textContent),
|
||||
await r.text('body'),
|
||||
'nope'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not serve error page for thrown non-page errors', async () => {
|
||||
await page.goto(`${base}/throw.json`);
|
||||
await r.load('/throw.json');
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.body.textContent),
|
||||
await r.text('body'),
|
||||
'oops'
|
||||
);
|
||||
});
|
||||
|
||||
it('execute error page hooks', async () => {
|
||||
await page.goto(`${base}/some-throw-page`);
|
||||
await start();
|
||||
await wait(50);
|
||||
await r.load('/some-throw-page');
|
||||
await r.sapper.start();
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h2', node => node.textContent),
|
||||
await r.text('h2'),
|
||||
'success'
|
||||
);
|
||||
})
|
||||
|
||||
it('does not serve error page for async non-page error', async () => {
|
||||
await page.goto(`${base}/async-throw.json`);
|
||||
await r.load('/async-throw.json');
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.body.textContent),
|
||||
await r.text('body'),
|
||||
'oops'
|
||||
);
|
||||
});
|
||||
|
||||
it('clears props.error on successful render', async () => {
|
||||
await page.goto(`${base}/no-error`);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/no-error');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="enhance-your-calm"]');
|
||||
await wait(50);
|
||||
assert.equal(await title(), '420');
|
||||
await r.page.click('[href="enhance-your-calm"]');
|
||||
await r.wait();
|
||||
assert.equal(await r.text('h1'), '420');
|
||||
|
||||
await page.goBack();
|
||||
await wait(50);
|
||||
assert.equal(await title(), 'No error here');
|
||||
await r.page.goBack();
|
||||
await r.wait();
|
||||
assert.equal(await r.text('h1'), 'No error here');
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
});
|
||||
|
||||
58
test/apps/export-queue/rollup.config.js
Normal file
58
test/apps/export-queue/rollup.config.js
Normal file
@@ -0,0 +1,58 @@
|
||||
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()
|
||||
]
|
||||
},
|
||||
|
||||
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']
|
||||
},
|
||||
|
||||
serviceworker: {
|
||||
input: config.serviceworker.input(),
|
||||
output: config.serviceworker.output(),
|
||||
plugins: [
|
||||
resolve(),
|
||||
replace({
|
||||
'process.browser': true,
|
||||
'process.env.NODE_ENV': JSON.stringify(mode)
|
||||
})
|
||||
]
|
||||
}
|
||||
};
|
||||
9
test/apps/export-queue/src/client.js
Normal file
9
test/apps/export-queue/src/client.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as sapper from '@sapper/app';
|
||||
|
||||
window.start = () => sapper.start({
|
||||
target: document.querySelector('#sapper')
|
||||
});
|
||||
|
||||
window.prefetchRoutes = () => sapper.prefetchRoutes();
|
||||
window.prefetch = href => sapper.prefetch(href);
|
||||
window.goto = href => sapper.goto(href);
|
||||
3
test/apps/export-queue/src/routes/_error.svelte
Normal file
3
test/apps/export-queue/src/routes/_error.svelte
Normal file
@@ -0,0 +1,3 @@
|
||||
<h1>{status}</h1>
|
||||
|
||||
<p>{error.message}</p>
|
||||
15
test/apps/export-queue/src/routes/a-[x].svelte
Normal file
15
test/apps/export-queue/src/routes/a-[x].svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
<script context="module">
|
||||
export function preload({ params }) {
|
||||
if (params.x === 'a') {
|
||||
return new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export let x;
|
||||
</script>
|
||||
|
||||
<a href="b-{x}">b-{x}</a>
|
||||
5
test/apps/export-queue/src/routes/b-[x].svelte
Normal file
5
test/apps/export-queue/src/routes/b-[x].svelte
Normal file
@@ -0,0 +1,5 @@
|
||||
<script>
|
||||
export let x;
|
||||
</script>
|
||||
|
||||
<p>b-{x}</p>
|
||||
3
test/apps/export-queue/src/routes/index.svelte
Normal file
3
test/apps/export-queue/src/routes/index.svelte
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
<a href="a-a">1</a>
|
||||
<a href="a-b">2</a>
|
||||
13
test/apps/export-queue/src/server.js
Normal file
13
test/apps/export-queue/src/server.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import sirv from 'sirv';
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
import { start, dev } from '../../common.js';
|
||||
|
||||
const app = polka()
|
||||
.use(
|
||||
sirv('static', { dev }),
|
||||
sapper.middleware()
|
||||
);
|
||||
|
||||
start(app);
|
||||
82
test/apps/export-queue/src/service-worker.js
Normal file
82
test/apps/export-queue/src/service-worker.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import * as sapper from '@sapper/service-worker';
|
||||
|
||||
const ASSETS = `cache${sapper.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 = sapper.shell.concat(sapper.files);
|
||||
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${sapper.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;
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
14
test/apps/export-queue/src/template.html
Normal file
14
test/apps/export-queue/src/template.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
|
||||
%sapper.base%
|
||||
%sapper.styles%
|
||||
%sapper.head%
|
||||
</head>
|
||||
<body>
|
||||
<div id='sapper'>%sapper.html%</div>
|
||||
%sapper.scripts%
|
||||
</body>
|
||||
</html>
|
||||
3
test/apps/export-queue/static/global.css
Normal file
3
test/apps/export-queue/static/global.css
Normal file
@@ -0,0 +1,3 @@
|
||||
body {
|
||||
font-family: 'Comic Sans MS';
|
||||
}
|
||||
13
test/apps/export-queue/test.ts
Normal file
13
test/apps/export-queue/test.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as api from '../../../api';
|
||||
|
||||
describe('export-queue', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
// hooks
|
||||
before('build app', () => api.build({ cwd: __dirname }));
|
||||
|
||||
// tests
|
||||
it('exports a site with inconsistent load time', async () => {
|
||||
await api.export({ cwd: __dirname });
|
||||
});
|
||||
});
|
||||
@@ -2,14 +2,12 @@ import sirv from 'sirv';
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT, NODE_ENV } = process.env;
|
||||
const dev = NODE_ENV === 'development';
|
||||
import { start, dev } from '../../common.js';
|
||||
|
||||
polka()
|
||||
const app = polka()
|
||||
.use(
|
||||
sirv('static', { dev }),
|
||||
sapper.middleware()
|
||||
)
|
||||
.listen(PORT, err => {
|
||||
if (err) console.log('error', err);
|
||||
});
|
||||
);
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -6,14 +6,12 @@ describe('export-webpack', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await api.build({ cwd: __dirname, bundler: 'webpack' });
|
||||
await api.export({ cwd: __dirname, bundler: 'webpack' });
|
||||
});
|
||||
before('build app', () => api.build({ cwd: __dirname, bundler: 'webpack' }));
|
||||
before('export app', () => api.export({ cwd: __dirname }));
|
||||
|
||||
// tests
|
||||
it('injects <link rel=preload> tags', () => {
|
||||
const index = fs.readFileSync(`${__dirname}/__sapper__/export/index.html`, 'utf8');
|
||||
assert.ok(/rel=preload/.test(index));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -2,14 +2,12 @@ import sirv from 'sirv';
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT, NODE_ENV } = process.env;
|
||||
const dev = NODE_ENV === 'development';
|
||||
import { start, dev } from '../../common.js';
|
||||
|
||||
polka()
|
||||
const app = polka()
|
||||
.use(
|
||||
sirv('static', { dev }),
|
||||
sapper.middleware()
|
||||
)
|
||||
.listen(PORT, err => {
|
||||
if (err) console.log('error', err);
|
||||
});
|
||||
);
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -6,11 +6,10 @@ describe('export', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await api.build({ cwd: __dirname });
|
||||
await api.export({ cwd: __dirname });
|
||||
});
|
||||
before('build app', () => api.build({ cwd: __dirname }));
|
||||
before('export app', () => api.export({ cwd: __dirname }));
|
||||
|
||||
// tests
|
||||
it('crawls a site', () => {
|
||||
const files = walk(`${__dirname}/__sapper__/export`);
|
||||
|
||||
@@ -45,4 +44,4 @@ describe('export', function() {
|
||||
});
|
||||
|
||||
// TODO test timeout, basepath
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
<h1>{status}</h1>
|
||||
|
||||
<script>
|
||||
export let status;
|
||||
export let error;
|
||||
</script>
|
||||
|
||||
<h1>{status}</h1>
|
||||
|
||||
<p>{error.message}</p>
|
||||
@@ -1,7 +1,7 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
const app = polka().use(sapper.middleware({
|
||||
ignore: [
|
||||
@@ -16,4 +16,4 @@ const app = polka().use(sapper.middleware({
|
||||
app.get('/'+uri, (req, res) => res.end(uri));
|
||||
});
|
||||
|
||||
app.listen(PORT);
|
||||
start(app);
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
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;
|
||||
let r: AppRunner;
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await build({ cwd: __dirname });
|
||||
|
||||
runner = new AppRunner(__dirname, '__sapper__/build/server/server.js');
|
||||
({ base, page } = await runner.start());
|
||||
before('build app', () => build({ cwd: __dirname }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('respects `options.ignore` values (RegExp)', async () => {
|
||||
await page.goto(`${base}/foobar`);
|
||||
await r.load('/foobar');
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.documentElement.textContent),
|
||||
await r.text('body'),
|
||||
'foobar'
|
||||
);
|
||||
});
|
||||
|
||||
it('respects `options.ignore` values (String #1)', async () => {
|
||||
await page.goto(`${base}/buzz`);
|
||||
await r.load('/buzz');
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.documentElement.textContent),
|
||||
await r.text('body'),
|
||||
'buzz'
|
||||
);
|
||||
});
|
||||
|
||||
it('respects `options.ignore` values (String #2)', async () => {
|
||||
await page.goto(`${base}/fizzer`);
|
||||
await r.load('/fizzer');
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.documentElement.textContent),
|
||||
await r.text('body'),
|
||||
'fizzer'
|
||||
);
|
||||
});
|
||||
|
||||
it('respects `options.ignore` values (Function)', async () => {
|
||||
await page.goto(`${base}/hello`);
|
||||
await r.load('/hello');
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.documentElement.textContent),
|
||||
await r.text('body'),
|
||||
'hello'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,4 +16,5 @@
|
||||
</script>
|
||||
|
||||
<span>z: {$page.params.z} {count}</span>
|
||||
<a href="foo/bar/qux">click me</a>
|
||||
<a href="foo/bar/qux">goto foo/bar/qux</a>
|
||||
<a href="foo/abc/def">goto foo/abc/def</a>
|
||||
@@ -1,8 +1,9 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
polka()
|
||||
.use(sapper.middleware())
|
||||
.listen(PORT);
|
||||
const app = polka()
|
||||
.use(sapper.middleware());
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -1,58 +1,69 @@
|
||||
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<void>;
|
||||
let r: AppRunner;
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await build({ cwd: __dirname });
|
||||
|
||||
runner = new AppRunner(__dirname, '__sapper__/build/server/server.js');
|
||||
({ base, page, start } = await runner.start());
|
||||
before('build app', () => build({ cwd: __dirname }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('only recreates components when necessary', async () => {
|
||||
await page.goto(`${base}/foo/bar/baz`);
|
||||
await r.load('/foo/bar/baz');
|
||||
|
||||
const text1 = String(await page.evaluate(() => document.querySelector('#sapper').textContent));
|
||||
const text1 = await r.text('#sapper');
|
||||
assert.deepEqual(text1.split('\n').map(str => str.trim()).filter(Boolean), [
|
||||
'y: bar 1',
|
||||
'z: baz 1',
|
||||
'click me',
|
||||
'goto foo/bar/qux',
|
||||
'goto foo/abc/def',
|
||||
'child segment: baz'
|
||||
]);
|
||||
|
||||
await start();
|
||||
const text2 = String(await page.evaluate(() => document.querySelector('#sapper').textContent));
|
||||
await r.sapper.start();
|
||||
const text2 = await r.text('#sapper');
|
||||
assert.deepEqual(text2.split('\n').map(str => str.trim()).filter(Boolean), [
|
||||
'y: bar 1',
|
||||
'z: baz 1',
|
||||
'click me',
|
||||
'goto foo/bar/qux',
|
||||
'goto foo/abc/def',
|
||||
'child segment: baz'
|
||||
]);
|
||||
|
||||
await page.click('[href="foo/bar/qux"]');
|
||||
await wait(50);
|
||||
await r.page.click('[href="foo/bar/qux"]');
|
||||
await r.wait();
|
||||
|
||||
const text3 = String(await page.evaluate(() => document.querySelector('#sapper').textContent));
|
||||
const text3 = await r.text('#sapper');
|
||||
assert.deepEqual(text3.split('\n').map(str => str.trim()).filter(Boolean), [
|
||||
'y: bar 1',
|
||||
'z: qux 2',
|
||||
'click me',
|
||||
'goto foo/bar/qux',
|
||||
'goto foo/abc/def',
|
||||
'child segment: qux'
|
||||
]);
|
||||
|
||||
await r.page.click('[href="foo/abc/def"]');
|
||||
await r.wait();
|
||||
|
||||
const text4 = await r.text('#sapper');
|
||||
assert.deepEqual(text4.split('\n').map(str => str.trim()).filter(Boolean), [
|
||||
'y: abc 2',
|
||||
'z: def 3',
|
||||
'goto foo/bar/qux',
|
||||
'goto foo/abc/def',
|
||||
'child segment: def'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
});
|
||||
|
||||
17
test/apps/preloading/src/routes/echo/index.svelte
Normal file
17
test/apps/preloading/src/routes/echo/index.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script context="module">
|
||||
export function preload(page) {
|
||||
return {
|
||||
query: page.query
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export let query;
|
||||
</script>
|
||||
|
||||
<pre>{JSON.stringify(query)}</pre>
|
||||
|
||||
<a href="echo?foo=1">foo=1</a>
|
||||
<a href="echo?foo=2">foo=2</a>
|
||||
<a href="echo?foo=3">foo=3</a>
|
||||
@@ -1,8 +1,9 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
polka()
|
||||
.use(sapper.middleware())
|
||||
.listen(PORT);
|
||||
const app = polka()
|
||||
.use(sapper.middleware());
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import * as assert from 'assert';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { build } from '../../../api';
|
||||
import { wait } from '../../utils';
|
||||
import { AppRunner } from '../AppRunner';
|
||||
|
||||
declare const fulfil: () => Promise<void>;
|
||||
@@ -9,112 +7,125 @@ declare const fulfil: () => Promise<void>;
|
||||
describe('preloading', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
let runner: AppRunner;
|
||||
let page: puppeteer.Page;
|
||||
let base: string;
|
||||
|
||||
// helpers
|
||||
let start: () => Promise<void>;
|
||||
let prefetchRoutes: () => Promise<void>;
|
||||
let title: () => Promise<string>;
|
||||
let r: AppRunner;
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await build({ cwd: __dirname });
|
||||
|
||||
runner = new AppRunner(__dirname, '__sapper__/build/server/server.js');
|
||||
({ base, page, start, prefetchRoutes, title } = await runner.start());
|
||||
before('build app', () => build({ cwd: __dirname }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('serializes Set objects returned from preload', async () => {
|
||||
await page.goto(`${base}/preload-values/set`);
|
||||
await r.load('/preload-values/set');
|
||||
|
||||
assert.equal(await title(), 'true');
|
||||
assert.equal(await r.text('h1'), 'true');
|
||||
|
||||
await start();
|
||||
assert.equal(await title(), 'true');
|
||||
await r.sapper.start();
|
||||
assert.equal(await r.text('h1'), 'true');
|
||||
});
|
||||
|
||||
it('prevent crash if preload return nothing', async () => {
|
||||
await page.goto(`${base}/preload-nothing`);
|
||||
await r.load('/preload-nothing');
|
||||
|
||||
await start();
|
||||
await wait(50);
|
||||
await r.sapper.start();
|
||||
|
||||
assert.equal(await title(), 'Page loaded');
|
||||
assert.equal(await r.text('h1'), 'Page loaded');
|
||||
});
|
||||
|
||||
it('bails on custom classes returned from preload', async () => {
|
||||
await page.goto(`${base}/preload-values/custom-class`);
|
||||
await r.load('/preload-values/custom-class');
|
||||
|
||||
assert.equal(await title(), '42');
|
||||
assert.equal(await r.text('h1'), '42');
|
||||
|
||||
await start();
|
||||
assert.equal(await title(), '42');
|
||||
await r.sapper.start();
|
||||
assert.equal(await r.text('h1'), '42');
|
||||
});
|
||||
|
||||
it('sets preloading true when appropriate', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('a[href="slow-preload"]');
|
||||
await r.page.click('a[href="slow-preload"]');
|
||||
|
||||
assert.ok(await page.evaluate(() => !!document.querySelector('progress')));
|
||||
assert.ok(await r.page.evaluate(() => !!document.querySelector('progress')));
|
||||
|
||||
await page.evaluate(() => fulfil());
|
||||
assert.ok(await page.evaluate(() => !document.querySelector('progress')));
|
||||
await r.page.evaluate(() => fulfil());
|
||||
assert.ok(await r.page.evaluate(() => !document.querySelector('progress')));
|
||||
});
|
||||
|
||||
it('runs preload in root component', async () => {
|
||||
await page.goto(`${base}/preload-root`);
|
||||
assert.equal(await title(), 'root preload function ran: true');
|
||||
await r.load('/preload-root');
|
||||
assert.equal(await r.text('h1'), 'root preload function ran: true');
|
||||
});
|
||||
|
||||
it('cancels navigation if subsequent navigation occurs during preload', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('a[href="slow-preload"]');
|
||||
await wait(100);
|
||||
await page.click('a[href="foo"]');
|
||||
await r.page.click('a[href="slow-preload"]');
|
||||
await r.wait();
|
||||
await r.page.click('a[href="foo"]');
|
||||
|
||||
assert.equal(page.url(), `${base}/foo`);
|
||||
assert.equal(await title(), 'foo');
|
||||
assert.equal(r.page.url(), `${r.base}/foo`);
|
||||
assert.equal(await r.text('h1'), 'foo');
|
||||
|
||||
await page.evaluate(() => fulfil());
|
||||
await wait(100);
|
||||
assert.equal(page.url(), `${base}/foo`);
|
||||
assert.equal(await title(), 'foo');
|
||||
await r.page.evaluate(() => fulfil());
|
||||
await r.wait();
|
||||
assert.equal(r.page.url(), `${r.base}/foo`);
|
||||
assert.equal(await r.text('h1'), 'foo');
|
||||
});
|
||||
|
||||
it('navigates to prefetched urls', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.hover('a[href="prefetch/qwe"]');
|
||||
await wait(100);
|
||||
await page.hover('a[href="prefetch/xyz"]');
|
||||
await wait(100);
|
||||
await r.page.hover('a[href="prefetch/qwe"]');
|
||||
await r.wait(50);
|
||||
await r.page.hover('a[href="prefetch/xyz"]');
|
||||
await r.wait(50);
|
||||
|
||||
await page.click('a[href="prefetch/qwe"]');
|
||||
await wait(50);
|
||||
await r.page.click('a[href="prefetch/qwe"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'qwe'
|
||||
);
|
||||
|
||||
await page.goto(`${base}/prefetch`);
|
||||
await wait(50);
|
||||
await r.load('/prefetch');
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'prefetch'
|
||||
);
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
|
||||
it('re-runs preload when page.query changes', async () => {
|
||||
await r.load('/echo?foo=1');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
assert.equal(
|
||||
await r.text('pre'),
|
||||
`{"foo":"1"}`
|
||||
);
|
||||
|
||||
await r.page.click('a[href="echo?foo=2"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await r.text('pre'),
|
||||
`{"foo":"2"}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
polka()
|
||||
.use(sapper.middleware())
|
||||
.listen(PORT);
|
||||
const app = polka()
|
||||
.use(sapper.middleware());
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -2,138 +2,137 @@ import * as assert from 'assert';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { build } from '../../../api';
|
||||
import { AppRunner } from '../AppRunner';
|
||||
import { wait } from '../../utils';
|
||||
|
||||
describe('redirects', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
let runner: AppRunner;
|
||||
let page: puppeteer.Page;
|
||||
let base: string;
|
||||
|
||||
// helpers
|
||||
let start: () => Promise<void>;
|
||||
let prefetchRoutes: () => Promise<void>;
|
||||
let title: () => Promise<string>;
|
||||
let r: AppRunner;
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await build({ cwd: __dirname });
|
||||
|
||||
runner = new AppRunner(__dirname, '__sapper__/build/server/server.js');
|
||||
({ base, page, start, prefetchRoutes, title } = await runner.start({
|
||||
requestInterceptor: (interceptedRequest) => {
|
||||
if (/example\.com/.test(interceptedRequest.url())) {
|
||||
interceptedRequest.respond({
|
||||
status: 200,
|
||||
contentType: 'text/html',
|
||||
body: `<h1>external</h1>`
|
||||
});
|
||||
} else {
|
||||
interceptedRequest.continue();
|
||||
}
|
||||
}
|
||||
}));
|
||||
before('build app', () => build({ cwd: __dirname }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('redirects on server', async () => {
|
||||
await page.goto(`${base}/redirect-from`);
|
||||
await r.load('/redirect-from');
|
||||
|
||||
assert.equal(
|
||||
page.url(),
|
||||
`${base}/redirect-to`
|
||||
r.page.url(),
|
||||
`${r.base}/redirect-to`
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'redirected'
|
||||
);
|
||||
});
|
||||
|
||||
it('redirects in client', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="redirect-from"]');
|
||||
await wait(50);
|
||||
await r.page.click('[href="redirect-from"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
page.url(),
|
||||
`${base}/redirect-to`
|
||||
r.page.url(),
|
||||
`${r.base}/redirect-to`
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'redirected'
|
||||
);
|
||||
});
|
||||
|
||||
it('redirects to root on server', async () => {
|
||||
await page.goto(`${base}/redirect-to-root`);
|
||||
await r.load('/redirect-to-root');
|
||||
|
||||
assert.equal(
|
||||
page.url(),
|
||||
`${base}/`
|
||||
r.page.url(),
|
||||
`${r.base}/`
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'root'
|
||||
);
|
||||
});
|
||||
|
||||
it('redirects to root in client', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="redirect-to-root"]');
|
||||
await wait(50);
|
||||
await r.page.click('[href="redirect-to-root"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
page.url(),
|
||||
`${base}/`
|
||||
r.page.url(),
|
||||
`${r.base}/`
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'root'
|
||||
);
|
||||
});
|
||||
|
||||
const interceptor = (request: puppeteer.Request) => {
|
||||
if (/example\.com/.test(request.url())) {
|
||||
request.respond({
|
||||
status: 200,
|
||||
contentType: 'text/html',
|
||||
body: `<h1>external</h1>`
|
||||
});
|
||||
} else {
|
||||
request.continue();
|
||||
}
|
||||
};
|
||||
|
||||
it('redirects to external URL on server', async () => {
|
||||
await page.goto(`${base}/redirect-to-external`);
|
||||
await r.intercept_requests(interceptor, async () => {
|
||||
await r.load('/redirect-to-external');
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
page.url(),
|
||||
r.page.url(),
|
||||
`https://example.com/`
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'external'
|
||||
);
|
||||
});
|
||||
|
||||
it('redirects to external URL in client', async () => {
|
||||
await page.goto(base);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="redirect-to-external"]');
|
||||
await wait(50);
|
||||
await r.intercept_requests(interceptor, async () => {
|
||||
await r.page.click('[href="redirect-to-external"]');
|
||||
await r.wait();
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
page.url(),
|
||||
r.page.url(),
|
||||
`https://example.com/`
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'external'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
polka()
|
||||
.use(sapper.middleware())
|
||||
.listen(PORT);
|
||||
const app = polka()
|
||||
.use(sapper.middleware());
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -1,93 +1,87 @@
|
||||
import * as assert from 'assert';
|
||||
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<void>;
|
||||
let prefetchRoutes: () => Promise<void>;
|
||||
let title: () => Promise<string>;
|
||||
let r: AppRunner;
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await build({ cwd: __dirname });
|
||||
|
||||
runner = new AppRunner(__dirname, '__sapper__/build/server/server.js');
|
||||
({ base, page, start, prefetchRoutes, title } = await runner.start());
|
||||
before('build app', () => build({ cwd: __dirname }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('scrolls to active deeplink', async () => {
|
||||
await page.goto(`${base}/tall-page#foo`);
|
||||
await start();
|
||||
await r.load('/tall-page#foo');
|
||||
await r.sapper.start();
|
||||
|
||||
const scrollY = await page.evaluate(() => window.scrollY);
|
||||
const scrollY = await r.page.evaluate(() => window.scrollY);
|
||||
assert.ok(scrollY > 0, String(scrollY));
|
||||
});
|
||||
|
||||
it('scrolls to any deeplink if it was already active', async () => {
|
||||
await page.goto(`${base}/tall-page#foo`);
|
||||
await start();
|
||||
await r.load('/tall-page#foo');
|
||||
await r.sapper.start();
|
||||
|
||||
let scrollY = await page.evaluate(() => window.scrollY);
|
||||
let scrollY = await r.page.evaluate(() => window.scrollY);
|
||||
assert.ok(scrollY > 0, String(scrollY));
|
||||
|
||||
scrollY = await page.evaluate(() => {
|
||||
scrollY = await r.page.evaluate(() => {
|
||||
window.scrollTo(0, 0)
|
||||
return window.scrollY
|
||||
});
|
||||
assert.ok(scrollY === 0, String(scrollY));
|
||||
|
||||
await page.click('[href="tall-page#foo"]');
|
||||
scrollY = await page.evaluate(() => window.scrollY);
|
||||
await r.page.click('[href="tall-page#foo"]');
|
||||
scrollY = await r.page.evaluate(() => window.scrollY);
|
||||
assert.ok(scrollY > 0, String(scrollY));
|
||||
});
|
||||
|
||||
it('resets scroll when a link is clicked', async () => {
|
||||
await page.goto(`${base}/tall-page#foo`);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/tall-page#foo');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="another-tall-page"]');
|
||||
await wait(50);
|
||||
await r.page.click('[href="another-tall-page"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
await page.evaluate(() => window.scrollY),
|
||||
await r.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 r.load('/tall-page#foo');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="another-tall-page"][sapper-noscroll]');
|
||||
await wait(50);
|
||||
await r.page.click('[href="another-tall-page"][sapper-noscroll]');
|
||||
await r.wait();
|
||||
|
||||
const scrollY = await page.evaluate(() => window.scrollY);
|
||||
const scrollY = await r.page.evaluate(() => window.scrollY);
|
||||
|
||||
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 r.load('/tall-page#foo');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="another-tall-page#bar"]');
|
||||
await wait(50);
|
||||
assert.equal(await title(), 'Another tall page');
|
||||
const scrollY = await page.evaluate(() => window.scrollY);
|
||||
await r.page.click('[href="another-tall-page#bar"]');
|
||||
await r.wait();
|
||||
assert.equal(await r.text('h1'), 'Another tall page');
|
||||
const scrollY = await r.page.evaluate(() => window.scrollY);
|
||||
assert.ok(scrollY > 0);
|
||||
});
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
polka()
|
||||
const app = polka()
|
||||
.use((req, res, next) => {
|
||||
req.hello = 'hello';
|
||||
res.locals = { name: 'world' };
|
||||
@@ -15,5 +15,6 @@ polka()
|
||||
title: `${req.hello} ${res.locals.name}`
|
||||
})
|
||||
})
|
||||
)
|
||||
.listen(PORT);
|
||||
);
|
||||
|
||||
start(app);
|
||||
|
||||
@@ -1,50 +1,46 @@
|
||||
import * as assert from 'assert';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { build } from '../../../api';
|
||||
import { AppRunner } from '../AppRunner';
|
||||
|
||||
describe('session', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
let runner: AppRunner;
|
||||
let page: puppeteer.Page;
|
||||
let base: string;
|
||||
|
||||
// helpers
|
||||
let start: () => Promise<void>;
|
||||
let title: () => Promise<string>;
|
||||
let r: AppRunner;
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await build({ cwd: __dirname });
|
||||
|
||||
runner = new AppRunner(__dirname, '__sapper__/build/server/server.js');
|
||||
({ base, page, start, title } = await runner.start());
|
||||
before('build app', () => build({ cwd: __dirname }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('renders session props', async () => {
|
||||
await page.goto(`${base}/session`);
|
||||
await r.load('/session');
|
||||
|
||||
assert.equal(await title(), 'hello world');
|
||||
assert.equal(await r.text('h1'), 'hello world');
|
||||
|
||||
await start();
|
||||
assert.equal(await title(), 'hello world');
|
||||
await r.sapper.start();
|
||||
assert.equal(await r.text('h1'), 'hello world');
|
||||
|
||||
await page.click('button');
|
||||
assert.equal(await title(), 'changed');
|
||||
await r.page.click('button');
|
||||
assert.equal(await r.text('h1'), 'changed');
|
||||
});
|
||||
|
||||
it('preloads session props', async () => {
|
||||
await page.goto(`${base}/preloaded`);
|
||||
await r.load('/preloaded');
|
||||
|
||||
assert.equal(await title(), 'hello world');
|
||||
assert.equal(await r.text('h1'), 'hello world');
|
||||
|
||||
await start();
|
||||
assert.equal(await title(), 'hello world');
|
||||
await r.sapper.start();
|
||||
assert.equal(await r.text('h1'), 'hello world');
|
||||
|
||||
await page.click('button');
|
||||
assert.equal(await title(), 'changed');
|
||||
await r.page.click('button');
|
||||
assert.equal(await r.text('h1'), 'changed');
|
||||
});
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,15 +2,14 @@ import sirv from 'sirv';
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT, NODE_ENV } = process.env;
|
||||
const dev = NODE_ENV === 'development';
|
||||
import { start, dev } from '../../common.js';
|
||||
|
||||
polka()
|
||||
const app = polka()
|
||||
.use(
|
||||
'custom-basepath',
|
||||
sirv('static', { dev }),
|
||||
sapper.middleware()
|
||||
)
|
||||
.listen(PORT, err => {
|
||||
if (err) console.log('error', err);
|
||||
});
|
||||
);
|
||||
|
||||
start(app);
|
||||
|
||||
|
||||
@@ -1,51 +1,36 @@
|
||||
import * as assert from 'assert';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import * as api from '../../../api';
|
||||
import { walk } from '../../utils';
|
||||
import { AppRunner } from '../AppRunner';
|
||||
import { wait } from '../../utils';
|
||||
|
||||
|
||||
describe('with-basepath', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
let runner: AppRunner;
|
||||
let page: puppeteer.Page;
|
||||
let base: string;
|
||||
|
||||
// helpers
|
||||
let start: () => Promise<void>;
|
||||
let prefetchRoutes: () => Promise<void>;
|
||||
let title: () => Promise<string>;
|
||||
let r: AppRunner;
|
||||
|
||||
// hooks
|
||||
before(async () => {
|
||||
await api.build({ cwd: __dirname });
|
||||
|
||||
await api.export({
|
||||
cwd: __dirname,
|
||||
basepath: '/custom-basepath'
|
||||
});
|
||||
|
||||
runner = new AppRunner(__dirname, '__sapper__/build/server/server.js');
|
||||
({ base, start, page, prefetchRoutes, title } = await runner.start());
|
||||
before('build app', () => api.build({ cwd: __dirname }));
|
||||
before('export app', () => api.export({ cwd: __dirname, basepath: '/custom-basepath' }));
|
||||
before('start runner', async () => {
|
||||
r = await new AppRunner().start(__dirname);
|
||||
});
|
||||
|
||||
after(() => runner.end());
|
||||
after(() => r && r.end());
|
||||
|
||||
// tests
|
||||
it('serves /custom-basepath', async () => {
|
||||
await page.goto(`${base}/custom-basepath`);
|
||||
await r.load('/custom-basepath');
|
||||
|
||||
assert.equal(
|
||||
await page.$eval('h1', node => node.textContent),
|
||||
await r.text('h1'),
|
||||
'Great success!'
|
||||
);
|
||||
});
|
||||
|
||||
it('emits a basepath message', async () => {
|
||||
await page.goto(`${base}/custom-basepath`);
|
||||
await r.load('/custom-basepath');
|
||||
|
||||
assert.deepEqual(runner.messages, [{
|
||||
assert.deepEqual(r.messages, [{
|
||||
__sapper__: true,
|
||||
event: 'basepath',
|
||||
basepath: '/custom-basepath'
|
||||
@@ -71,35 +56,39 @@ describe('with-basepath', function() {
|
||||
});
|
||||
|
||||
it('redirects on server', async () => {
|
||||
await page.goto(`${base}/custom-basepath/redirect-from`);
|
||||
await r.load('/custom-basepath/redirect-from');
|
||||
|
||||
assert.equal(
|
||||
page.url(),
|
||||
`${base}/custom-basepath/redirect-to`
|
||||
r.page.url(),
|
||||
`${r.base}/custom-basepath/redirect-to`
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'redirected'
|
||||
);
|
||||
});
|
||||
|
||||
it('redirects in client', async () => {
|
||||
await page.goto(`${base}/custom-basepath`);
|
||||
await start();
|
||||
await prefetchRoutes();
|
||||
await r.load('/custom-basepath');
|
||||
await r.sapper.start();
|
||||
await r.sapper.prefetchRoutes();
|
||||
|
||||
await page.click('[href="redirect-from"]');
|
||||
await wait(50);
|
||||
await r.page.click('[href="redirect-from"]');
|
||||
await r.wait();
|
||||
|
||||
assert.equal(
|
||||
page.url(),
|
||||
`${base}/custom-basepath/redirect-to`
|
||||
r.page.url(),
|
||||
`${r.base}/custom-basepath/redirect-to`
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
await title(),
|
||||
await r.text('h1'),
|
||||
'redirected'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('survives the tests with no server errors', () => {
|
||||
assert.deepEqual(r.errors, []);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import polka from 'polka';
|
||||
import * as sapper from '@sapper/server';
|
||||
|
||||
const { PORT } = process.env;
|
||||
import { start } from '../../common.js';
|
||||
|
||||
polka()
|
||||
.use(sapper.middleware())
|
||||
.listen(PORT);
|
||||
const app = polka()
|
||||
.use(sapper.middleware());
|
||||
|
||||
start(app);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user