Compare commits

..

32 Commits

Author SHA1 Message Date
Rich Harris
d13d6209ed use sapper.server() in tests 2019-05-10 08:52:01 -04:00
Rich Harris
05aef8de71 add req.query, add sapper.server(), remove unused code 2019-05-10 08:47:30 -04:00
Rich Harris
429f44e3a6 get tests passing 2019-05-10 08:00:21 -04:00
Rich Harris
f8e853c02b handle missing static folder 2019-05-10 07:38:18 -04:00
Rich Harris
bedbcb834b incorporate Sirv, and add a sapper.start() function 2019-05-09 21:32:13 -04:00
Richard Harris
5c07080207 -> v0.27.0 2019-05-09 09:38:21 -04:00
Richard Harris
c2ed73f103 -> v0.27.0 2019-05-09 09:30:45 -04:00
Richard Harris
133ac07ed2 remove some unused imports 2019-05-09 09:26:49 -04:00
Richard Harris
7417de101e Merge branch 'master' of github.com:sveltejs/sapper 2019-05-09 09:26:13 -04:00
Rich Harris
2792b7c5d1 Merge pull request #676 from mrkishi/server-indexes
Consolidate index handling for page and server routes
2019-05-09 09:18:26 -04:00
Rich Harris
cf8d5ee717 Merge pull request #670 from cristianl/pr-rollup-output-naming
Build rollup config: Don't add hashes to files in dist
2019-05-09 09:15:04 -04:00
Rich Harris
8e4517a1ad Merge pull request #673 from mrkishi/testing-issue
Harden tests (...a bit)
2019-05-09 08:57:18 -04:00
Rich Harris
91894722ee Merge pull request #652 from sveltejs/relicense
switch license to MIT
2019-05-09 08:44:32 -04:00
Rich Harris
933b3b76a6 Merge pull request #665 from mrkishi/node-stable
Switch to node stable
2019-05-09 08:42:07 -04:00
Richard Harris
3ed4d1d887 update deps 2019-05-09 08:39:58 -04:00
Richard Harris
dda936e53b fix theme color 2019-05-09 08:34:41 -04:00
Rich Harris
ac4eb84f3d Merge pull request #662 from thollander/feat/fix-mobile-viewport
Fix mobile viewport KO
2019-05-09 08:32:57 -04:00
Rich Harris
9e70e68c0c Merge pull request #664 from mrkishi/refactor
Tidy a few things up
2019-05-09 08:28:57 -04:00
Rich Harris
22389eab99 Merge pull request #678 from mrkishi/generated-app-quoted-attributes
Add attribute quotes to generated `App.svelte`
2019-05-09 08:24:02 -04:00
mrkishi
fe6b7976ef Add attribute quotes to generated App.svelte 2019-05-08 22:44:07 -03:00
mrkishi
54e92c3b99 Regularize page and server routes
Treats both page and server routes similarly in regards to indexes.

It's a (somewhat) breaking change: server routes will accept trailing
slashes to match page routes behavior, but only if there's no extension.

We still need a stronger support for dealing with different clean urls
rules.
2019-05-08 12:24:45 -03:00
mrkishi
e6c1a54164 Harden tests (...a bit) 2019-05-08 00:57:59 -03:00
Cristian Lorsson
629b5601c8 Don't add hashes to dist output
Default is '[name].[hash].js'
2019-05-06 20:46:32 -03:00
Conduitry
8bdd363a19 site: remove chokidar 2019-05-06 12:50:31 -04:00
mrkishi
8fcc27d44f Update svelte to released v3 2019-05-05 23:19:00 -03:00
mrkishi
f6e72a0432 Update dependencies 2019-05-05 21:25:00 -03:00
mrkishi
f886a12bd7 Group route filters 2019-05-05 14:51:23 -03:00
mrkishi
6c03cfd46a Extract layout file search 2019-05-05 14:51:22 -03:00
mrkishi
78480fe5e8 Fix typo 2019-05-05 14:51:22 -03:00
mrkishi
5cba40b7e0 Refactor compose_handlers and ignore 2019-05-05 14:51:22 -03:00
thollander
5e5a8c4c69 Fix mobile viewport KO
- Changed `;` to `,` which causes the bug
2019-05-05 12:17:41 +02:00
Richard Harris
a2204a9d2e switch license to MIT 2019-05-02 08:32:40 -04:00
67 changed files with 2020 additions and 2937 deletions

4
.gitignore vendored
View File

@@ -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

View File

@@ -3,7 +3,7 @@ sudo: false
language: node_js
node_js:
- "11"
- "stable"
env:
global:

View File

@@ -1,5 +1,10 @@
# sapper changelog
## 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))

10
LICENSE
View File

@@ -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.

1484
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "sapper",
"version": "0.26.1",
"version": "0.27.0",
"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.6",
"@types/node": "^12.0.0",
"@types/puppeteer": "^1.12.4",
"agadoo": "^1.0.1",
"cheap-watch": "^1.0.2",
"cookie": "^0.3.1",
"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.2",
"kleur": "^3.0.3",
"mocha": "^6.1.4",
"node-fetch": "^2.5.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.15.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.11.3",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^4.2.3",
"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",
"sirv": "^0.4.2",
"sucrase": "^3.10.1",
"svelte": "^3.2.2",
"svelte-loader": "^2.13.3",
"webpack": "^4.29.0",
"webpack": "^4.31.0",
"webpack-format-messages": "^2.0.5",
"yootils": "0.0.15"
},
"peerDependencies": {
"svelte": "^3.0.0"
"svelte": "^3.2.2"
},
"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"
},

View File

@@ -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';
@@ -24,8 +24,9 @@ function template(kind, external) {
external,
plugins: [
resolve({
extensions: ['.mjs', '.js', '.ts']
extensions: ['.mjs', '.js', '.json', '.ts']
}),
json(),
commonjs(),
string({
include: '**/*.md'
@@ -52,7 +53,8 @@ export default [
output: {
dir: 'dist',
format: 'cjs',
sourcemap: true
sourcemap: true,
chunkFileNames: '[name].js'
},
external,
plugins: [

View File

@@ -101,7 +101,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;
@@ -332,7 +336,7 @@ export async function hydrate_target(target: Target): Promise<{
}
function load_css(chunk: string) {
const href = `client/${chunk}`;
const href = `sapper/${chunk}`;
if (document.querySelector(`link[href="${href}"]`)) return;
return new Promise((fulfil, reject) => {

View File

@@ -1 +0,0 @@
export const IGNORE = '__SAPPER__IGNORE__';

View File

@@ -1 +1,17 @@
export { default as middleware } from './middleware/index';
import http from 'http';
import middleware from './middleware/index';
import { StartOptions, MiddlewareOptions, Handler } from './types';
export { middleware };
export function server(opts: MiddlewareOptions) {
const handler = middleware(opts) as Handler;
return http.createServer(handler as http.ServerOptions);
}
export function start(opts: StartOptions = {}) {
const s = server(opts);
s.listen(opts.port || process.env.PORT);
return s;
}

View File

@@ -5,10 +5,8 @@ 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(
@@ -23,9 +21,8 @@ export function get_page_handler(
? () => read_template(src_dir)
: (str => () => str)(read_template(build_dir));
const has_service_worker = fs.existsSync(path.join(build_dir, 'service-worker.js'));
const has_service_worker = fs.existsSync(path.join(build_dir, 'service-worker/service-worker.js'));
const { server_routes, pages } = manifest;
const error_route = manifest.error;
function handle_error(req: Req, res: Res, statusCode: number, error: Error | string) {
@@ -65,7 +62,7 @@ export function get_page_handler(
// TODO add dependencies and CSS
const link = preloaded_chunks
.filter(file => file && !file.match(/\.map$/))
.map(file => `<${req.baseUrl}/client/${file}>;rel="modulepreload"`)
.map(file => `<${req.baseUrl}/sapper/${file}>;rel="modulepreload"`)
.join(', ');
res.setHeader('Link', link);
@@ -74,7 +71,7 @@ export function get_page_handler(
.filter(file => file && !file.match(/\.map$/))
.map((file) => {
const as = /\.css$/.test(file) ? 'style' : 'script';
return `<${req.baseUrl}/client/${file}>;rel="preload";as="${as}"`;
return `<${req.baseUrl}/sapper/${file}>;rel="preload";as="${as}"`;
})
.join(', ');
@@ -265,14 +262,14 @@ export function get_page_handler(
}
const file = [].concat(build_info.assets.main).filter(file => file && /\.js$/.test(file))[0];
const main = `${req.baseUrl}/client/${file}`;
const main = `${req.baseUrl}/sapper/${file}`;
if (build_info.bundler === 'rollup') {
if (build_info.legacy_assets) {
const legacy_main = `${req.baseUrl}/client/legacy/${build_info.legacy_assets.main}`;
script += `(function(){try{eval("async function x(){}");var main="${main}"}catch(e){main="${legacy_main}"};var s=document.createElement("script");try{new Function("if(0)import('')")();s.src=main;s.type="module";s.crossOrigin="use-credentials";}catch(e){s.src="${req.baseUrl}/client/shimport@${build_info.shimport}.js";s.setAttribute("data-main",main);}document.head.appendChild(s);}());`;
const legacy_main = `${req.baseUrl}/sapper/legacy/${build_info.legacy_assets.main}`;
script += `(function(){try{eval("async function x(){}");var main="${main}"}catch(e){main="${legacy_main}"};var s=document.createElement("script");try{new Function("if(0)import('')")();s.src=main;s.type="module";s.crossOrigin="use-credentials";}catch(e){s.src="${req.baseUrl}/sapper/shimport@${build_info.shimport}.js";s.setAttribute("data-main",main);}document.head.appendChild(s);}());`;
} else {
script += `var s=document.createElement("script");try{new Function("if(0)import('')")();s.src="${main}";s.type="module";s.crossOrigin="use-credentials";}catch(e){s.src="${req.baseUrl}/client/shimport@${build_info.shimport}.js";s.setAttribute("data-main","${main}")}document.head.appendChild(s)`;
script += `var s=document.createElement("script");try{new Function("if(0)import('')")();s.src="${main}";s.type="module";s.crossOrigin="use-credentials";}catch(e){s.src="${req.baseUrl}/sapper/shimport@${build_info.shimport}.js";s.setAttribute("data-main","${main}")}document.head.appendChild(s)`;
}
} else {
script += `</script><script src="${main}">`;
@@ -297,7 +294,7 @@ export function get_page_handler(
});
styles = Array.from(css_chunks)
.map(href => `<link rel="stylesheet" href="client/${href}">`)
.map(href => `<link rel="stylesheet" href="sapper/${href}">`)
.join('')
} else {
styles = (css && css.code ? `<style>${css.code}</style>` : '');
@@ -328,15 +325,13 @@ 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('/'));
const homePage = manifest.pages.find(page => page.pattern.test('/'));
handle_page(homePage, req, res);
return;
}
for (const page of pages) {
for (const page of manifest.pages) {
if (page.pattern.test(req.path)) {
handle_page(page, req, res);
return;

View File

@@ -1,5 +1,4 @@
import { IGNORE } from '../constants';
import { Req, Res, ServerRoute } from './types';
import { Req, Res, ServerRoute } from '../types';
export function get_server_route_handler(routes: ServerRoute[]) {
async function handle_route(route: ServerRoute, req: Req, res: Res, next: () => void) {
@@ -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();
};
}
}

View File

@@ -1,31 +1,42 @@
import fs from 'fs';
import path from 'path';
import querystring from 'querystring';
import sirv from 'sirv';
import { build_dir, dev, manifest } from '@sapper/internal/manifest-server';
import { Handler, Req, Res } from './types';
import { Handler, Req, Res, MiddlewareOptions } 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,
ignore?: any
} = {}) {
export default function middleware(opts: MiddlewareOptions = {}) {
const { session, ignore } = 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, [
fs.existsSync('static') && sirv('static', {
dev,
setHeaders: opts.static && opts.static.headers && ((res: Response, pathname: string, stats: fs.Stats) => {
const headers = opts.static.headers(pathname, stats);
for (const k in headers) res.setHeader(k, headers[k]);
})
}),
(req: Req, res: Res, next: () => void) => {
if (req[IGNORE]) return next();
sirv(`${build_dir}/client`, {
dev,
maxAge: 31536000,
immutable: true
}),
sirv(`${build_dir}/service-worker`, {
dev,
maxAge: 300
}),
function condition_request(req: Req, res: Res, next: () => void) {
const qi = req.url.indexOf('?');
req.query = ~qi ? querystring.parse(req.url.slice(qi + 1)) : {}
if (req.baseUrl === undefined) {
let { originalUrl } = req;
let originalUrl = req.originalUrl || req.url;
if (req.url === '/' && originalUrl[originalUrl.length - 1] !== '/') {
originalUrl += '/';
}
@@ -52,45 +63,32 @@ export default function middleware(opts: {
next();
},
fs.existsSync(path.join(build_dir, 'service-worker.js')) && serve({
pathname: '/service-worker.js',
cache_control: 'no-cache, no-store, must-revalidate'
}),
fs.existsSync(path.join(build_dir, 'service-worker.js.map')) && serve({
pathname: '/service-worker.js.map',
cache_control: 'no-cache, no-store, must-revalidate'
}),
serve({
prefix: '/client/',
cache_control: dev ? 'no-cache' : 'max-age=31536000, immutable'
}),
get_server_route_handler(manifest.server_routes),
get_page_handler(manifest, session || noop)
].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) {
@@ -100,42 +98,4 @@ export function should_ignore(uri: string, val: any) {
return uri.startsWith(val.charCodeAt(0) === 47 ? val : `/${val}`);
}
export function serve({ prefix, pathname, cache_control }: {
prefix?: string,
pathname?: string,
cache_control: string
}) {
const filter = pathname
? (req: Req) => req.path === pathname
: (req: Req) => req.path.startsWith(prefix);
const cache: Map<string, Buffer> = new Map();
const read = dev
? (file: string) => fs.readFileSync(path.resolve(build_dir, file))
: (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);
try {
const file = decodeURIComponent(req.path.slice(1));
const data = read(file);
res.setHeader('Content-Type', type);
res.setHeader('Cache-Control', cache_control);
res.end(data);
} catch (err) {
res.statusCode = 404;
res.end('not found');
}
} else {
next();
}
};
}
function noop(){}

View File

@@ -1,767 +0,0 @@
application/andrew-inset ez
application/applixware aw
application/atom+xml atom
application/atomcat+xml atomcat
application/atomsvc+xml atomsvc
application/ccxml+xml ccxml
application/cdmi-capability cdmia
application/cdmi-container cdmic
application/cdmi-domain cdmid
application/cdmi-object cdmio
application/cdmi-queue cdmiq
application/cu-seeme cu
application/davmount+xml davmount
application/docbook+xml dbk
application/dssc+der dssc
application/dssc+xml xdssc
application/ecmascript ecma
application/emma+xml emma
application/epub+zip epub
application/exi exi
application/font-tdpfr pfr
application/gml+xml gml
application/gpx+xml gpx
application/gxf gxf
application/hyperstudio stk
application/inkml+xml ink inkml
application/ipfix ipfix
application/java-archive jar
application/java-serialized-object ser
application/java-vm class
application/javascript js
application/json json map
application/jsonml+json jsonml
application/lost+xml lostxml
application/mac-binhex40 hqx
application/mac-compactpro cpt
application/mads+xml mads
application/marc mrc
application/marcxml+xml mrcx
application/mathematica ma nb mb
application/mathml+xml mathml
application/mbox mbox
application/mediaservercontrol+xml mscml
application/metalink+xml metalink
application/metalink4+xml meta4
application/mets+xml mets
application/mods+xml mods
application/mp21 m21 mp21
application/mp4 mp4s
application/msword doc dot
application/mxf mxf
application/octet-stream bin dms lrf mar so dist distz pkg bpk dump elc deploy
application/oda oda
application/oebps-package+xml opf
application/ogg ogx
application/omdoc+xml omdoc
application/onenote onetoc onetoc2 onetmp onepkg
application/oxps oxps
application/patch-ops-error+xml xer
application/pdf pdf
application/pgp-encrypted pgp
application/pgp-signature asc sig
application/pics-rules prf
application/pkcs10 p10
application/pkcs7-mime p7m p7c
application/pkcs7-signature p7s
application/pkcs8 p8
application/pkix-attr-cert ac
application/pkix-cert cer
application/pkix-crl crl
application/pkix-pkipath pkipath
application/pkixcmp pki
application/pls+xml pls
application/postscript ai eps ps
application/prs.cww cww
application/pskc+xml pskcxml
application/rdf+xml rdf
application/reginfo+xml rif
application/relax-ng-compact-syntax rnc
application/resource-lists+xml rl
application/resource-lists-diff+xml rld
application/rls-services+xml rs
application/rpki-ghostbusters gbr
application/rpki-manifest mft
application/rpki-roa roa
application/rsd+xml rsd
application/rss+xml rss
application/rtf rtf
application/sbml+xml sbml
application/scvp-cv-request scq
application/scvp-cv-response scs
application/scvp-vp-request spq
application/scvp-vp-response spp
application/sdp sdp
application/set-payment-initiation setpay
application/set-registration-initiation setreg
application/shf+xml shf
application/smil+xml smi smil
application/sparql-query rq
application/sparql-results+xml srx
application/srgs gram
application/srgs+xml grxml
application/sru+xml sru
application/ssdl+xml ssdl
application/ssml+xml ssml
application/tei+xml tei teicorpus
application/thraud+xml tfi
application/timestamped-data tsd
application/vnd.3gpp.pic-bw-large plb
application/vnd.3gpp.pic-bw-small psb
application/vnd.3gpp.pic-bw-var pvb
application/vnd.3gpp2.tcap tcap
application/vnd.3m.post-it-notes pwn
application/vnd.accpac.simply.aso aso
application/vnd.accpac.simply.imp imp
application/vnd.acucobol acu
application/vnd.acucorp atc acutc
application/vnd.adobe.air-application-installer-package+zip air
application/vnd.adobe.formscentral.fcdt fcdt
application/vnd.adobe.fxp fxp fxpl
application/vnd.adobe.xdp+xml xdp
application/vnd.adobe.xfdf xfdf
application/vnd.ahead.space ahead
application/vnd.airzip.filesecure.azf azf
application/vnd.airzip.filesecure.azs azs
application/vnd.amazon.ebook azw
application/vnd.americandynamics.acc acc
application/vnd.amiga.ami ami
application/vnd.android.package-archive apk
application/vnd.anser-web-certificate-issue-initiation cii
application/vnd.anser-web-funds-transfer-initiation fti
application/vnd.antix.game-component atx
application/vnd.apple.installer+xml mpkg
application/vnd.apple.mpegurl m3u8
application/vnd.aristanetworks.swi swi
application/vnd.astraea-software.iota iota
application/vnd.audiograph aep
application/vnd.blueice.multipass mpm
application/vnd.bmi bmi
application/vnd.businessobjects rep
application/vnd.chemdraw+xml cdxml
application/vnd.chipnuts.karaoke-mmd mmd
application/vnd.cinderella cdy
application/vnd.claymore cla
application/vnd.cloanto.rp9 rp9
application/vnd.clonk.c4group c4g c4d c4f c4p c4u
application/vnd.cluetrust.cartomobile-config c11amc
application/vnd.cluetrust.cartomobile-config-pkg c11amz
application/vnd.commonspace csp
application/vnd.contact.cmsg cdbcmsg
application/vnd.cosmocaller cmc
application/vnd.crick.clicker clkx
application/vnd.crick.clicker.keyboard clkk
application/vnd.crick.clicker.palette clkp
application/vnd.crick.clicker.template clkt
application/vnd.crick.clicker.wordbank clkw
application/vnd.criticaltools.wbs+xml wbs
application/vnd.ctc-posml pml
application/vnd.cups-ppd ppd
application/vnd.curl.car car
application/vnd.curl.pcurl pcurl
application/vnd.dart dart
application/vnd.data-vision.rdz rdz
application/vnd.dece.data uvf uvvf uvd uvvd
application/vnd.dece.ttml+xml uvt uvvt
application/vnd.dece.unspecified uvx uvvx
application/vnd.dece.zip uvz uvvz
application/vnd.denovo.fcselayout-link fe_launch
application/vnd.dna dna
application/vnd.dolby.mlp mlp
application/vnd.dpgraph dpg
application/vnd.dreamfactory dfac
application/vnd.ds-keypoint kpxx
application/vnd.dvb.ait ait
application/vnd.dvb.service svc
application/vnd.dynageo geo
application/vnd.ecowin.chart mag
application/vnd.enliven nml
application/vnd.epson.esf esf
application/vnd.epson.msf msf
application/vnd.epson.quickanime qam
application/vnd.epson.salt slt
application/vnd.epson.ssf ssf
application/vnd.eszigno3+xml es3 et3
application/vnd.ezpix-album ez2
application/vnd.ezpix-package ez3
application/vnd.fdf fdf
application/vnd.fdsn.mseed mseed
application/vnd.fdsn.seed seed dataless
application/vnd.flographit gph
application/vnd.fluxtime.clip ftc
application/vnd.framemaker fm frame maker book
application/vnd.frogans.fnc fnc
application/vnd.frogans.ltf ltf
application/vnd.fsc.weblaunch fsc
application/vnd.fujitsu.oasys oas
application/vnd.fujitsu.oasys2 oa2
application/vnd.fujitsu.oasys3 oa3
application/vnd.fujitsu.oasysgp fg5
application/vnd.fujitsu.oasysprs bh2
application/vnd.fujixerox.ddd ddd
application/vnd.fujixerox.docuworks xdw
application/vnd.fujixerox.docuworks.binder xbd
application/vnd.fuzzysheet fzs
application/vnd.genomatix.tuxedo txd
application/vnd.geogebra.file ggb
application/vnd.geogebra.tool ggt
application/vnd.geometry-explorer gex gre
application/vnd.geonext gxt
application/vnd.geoplan g2w
application/vnd.geospace g3w
application/vnd.gmx gmx
application/vnd.google-earth.kml+xml kml
application/vnd.google-earth.kmz kmz
application/vnd.grafeq gqf gqs
application/vnd.groove-account gac
application/vnd.groove-help ghf
application/vnd.groove-identity-message gim
application/vnd.groove-injector grv
application/vnd.groove-tool-message gtm
application/vnd.groove-tool-template tpl
application/vnd.groove-vcard vcg
application/vnd.hal+xml hal
application/vnd.handheld-entertainment+xml zmm
application/vnd.hbci hbci
application/vnd.hhe.lesson-player les
application/vnd.hp-hpgl hpgl
application/vnd.hp-hpid hpid
application/vnd.hp-hps hps
application/vnd.hp-jlyt jlt
application/vnd.hp-pcl pcl
application/vnd.hp-pclxl pclxl
application/vnd.hydrostatix.sof-data sfd-hdstx
application/vnd.ibm.minipay mpy
application/vnd.ibm.modcap afp listafp list3820
application/vnd.ibm.rights-management irm
application/vnd.ibm.secure-container sc
application/vnd.iccprofile icc icm
application/vnd.igloader igl
application/vnd.immervision-ivp ivp
application/vnd.immervision-ivu ivu
application/vnd.insors.igm igm
application/vnd.intercon.formnet xpw xpx
application/vnd.intergeo i2g
application/vnd.intu.qbo qbo
application/vnd.intu.qfx qfx
application/vnd.ipunplugged.rcprofile rcprofile
application/vnd.irepository.package+xml irp
application/vnd.is-xpr xpr
application/vnd.isac.fcs fcs
application/vnd.jam jam
application/vnd.jcp.javame.midlet-rms rms
application/vnd.jisp jisp
application/vnd.joost.joda-archive joda
application/vnd.kahootz ktz ktr
application/vnd.kde.karbon karbon
application/vnd.kde.kchart chrt
application/vnd.kde.kformula kfo
application/vnd.kde.kivio flw
application/vnd.kde.kontour kon
application/vnd.kde.kpresenter kpr kpt
application/vnd.kde.kspread ksp
application/vnd.kde.kword kwd kwt
application/vnd.kenameaapp htke
application/vnd.kidspiration kia
application/vnd.kinar kne knp
application/vnd.koan skp skd skt skm
application/vnd.kodak-descriptor sse
application/vnd.las.las+xml lasxml
application/vnd.llamagraphics.life-balance.desktop lbd
application/vnd.llamagraphics.life-balance.exchange+xml lbe
application/vnd.lotus-1-2-3 123
application/vnd.lotus-approach apr
application/vnd.lotus-freelance pre
application/vnd.lotus-notes nsf
application/vnd.lotus-organizer org
application/vnd.lotus-screencam scm
application/vnd.lotus-wordpro lwp
application/vnd.macports.portpkg portpkg
application/vnd.mcd mcd
application/vnd.medcalcdata mc1
application/vnd.mediastation.cdkey cdkey
application/vnd.mfer mwf
application/vnd.mfmp mfm
application/vnd.micrografx.flo flo
application/vnd.micrografx.igx igx
application/vnd.mif mif
application/vnd.mobius.daf daf
application/vnd.mobius.dis dis
application/vnd.mobius.mbk mbk
application/vnd.mobius.mqy mqy
application/vnd.mobius.msl msl
application/vnd.mobius.plc plc
application/vnd.mobius.txf txf
application/vnd.mophun.application mpn
application/vnd.mophun.certificate mpc
application/vnd.mozilla.xul+xml xul
application/vnd.ms-artgalry cil
application/vnd.ms-cab-compressed cab
application/vnd.ms-excel xls xlm xla xlc xlt xlw
application/vnd.ms-excel.addin.macroenabled.12 xlam
application/vnd.ms-excel.sheet.binary.macroenabled.12 xlsb
application/vnd.ms-excel.sheet.macroenabled.12 xlsm
application/vnd.ms-excel.template.macroenabled.12 xltm
application/vnd.ms-fontobject eot
application/vnd.ms-htmlhelp chm
application/vnd.ms-ims ims
application/vnd.ms-lrm lrm
application/vnd.ms-officetheme thmx
application/vnd.ms-pki.seccat cat
application/vnd.ms-pki.stl stl
application/vnd.ms-powerpoint ppt pps pot
application/vnd.ms-powerpoint.addin.macroenabled.12 ppam
application/vnd.ms-powerpoint.presentation.macroenabled.12 pptm
application/vnd.ms-powerpoint.slide.macroenabled.12 sldm
application/vnd.ms-powerpoint.slideshow.macroenabled.12 ppsm
application/vnd.ms-powerpoint.template.macroenabled.12 potm
application/vnd.ms-project mpp mpt
application/vnd.ms-word.document.macroenabled.12 docm
application/vnd.ms-word.template.macroenabled.12 dotm
application/vnd.ms-works wps wks wcm wdb
application/vnd.ms-wpl wpl
application/vnd.ms-xpsdocument xps
application/vnd.mseq mseq
application/vnd.musician mus
application/vnd.muvee.style msty
application/vnd.mynfc taglet
application/vnd.neurolanguage.nlu nlu
application/vnd.nitf ntf nitf
application/vnd.noblenet-directory nnd
application/vnd.noblenet-sealer nns
application/vnd.noblenet-web nnw
application/vnd.nokia.n-gage.data ngdat
application/vnd.nokia.n-gage.symbian.install n-gage
application/vnd.nokia.radio-preset rpst
application/vnd.nokia.radio-presets rpss
application/vnd.novadigm.edm edm
application/vnd.novadigm.edx edx
application/vnd.novadigm.ext ext
application/vnd.oasis.opendocument.chart odc
application/vnd.oasis.opendocument.chart-template otc
application/vnd.oasis.opendocument.database odb
application/vnd.oasis.opendocument.formula odf
application/vnd.oasis.opendocument.formula-template odft
application/vnd.oasis.opendocument.graphics odg
application/vnd.oasis.opendocument.graphics-template otg
application/vnd.oasis.opendocument.image odi
application/vnd.oasis.opendocument.image-template oti
application/vnd.oasis.opendocument.presentation odp
application/vnd.oasis.opendocument.presentation-template otp
application/vnd.oasis.opendocument.spreadsheet ods
application/vnd.oasis.opendocument.spreadsheet-template ots
application/vnd.oasis.opendocument.text odt
application/vnd.oasis.opendocument.text-master odm
application/vnd.oasis.opendocument.text-template ott
application/vnd.oasis.opendocument.text-web oth
application/vnd.olpc-sugar xo
application/vnd.oma.dd2+xml dd2
application/vnd.openofficeorg.extension oxt
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx
application/vnd.openxmlformats-officedocument.presentationml.slide sldx
application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx
application/vnd.openxmlformats-officedocument.presentationml.template potx
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx
application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx
application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx
application/vnd.osgeo.mapguide.package mgp
application/vnd.osgi.dp dp
application/vnd.osgi.subsystem esa
application/vnd.palm pdb pqa oprc
application/vnd.pawaafile paw
application/vnd.pg.format str
application/vnd.pg.osasli ei6
application/vnd.picsel efif
application/vnd.pmi.widget wg
application/vnd.pocketlearn plf
application/vnd.powerbuilder6 pbd
application/vnd.previewsystems.box box
application/vnd.proteus.magazine mgz
application/vnd.publishare-delta-tree qps
application/vnd.pvi.ptid1 ptid
application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb
application/vnd.realvnc.bed bed
application/vnd.recordare.musicxml mxl
application/vnd.recordare.musicxml+xml musicxml
application/vnd.rig.cryptonote cryptonote
application/vnd.rim.cod cod
application/vnd.rn-realmedia rm
application/vnd.rn-realmedia-vbr rmvb
application/vnd.route66.link66+xml link66
application/vnd.sailingtracker.track st
application/vnd.seemail see
application/vnd.sema sema
application/vnd.semd semd
application/vnd.semf semf
application/vnd.shana.informed.formdata ifm
application/vnd.shana.informed.formtemplate itp
application/vnd.shana.informed.interchange iif
application/vnd.shana.informed.package ipk
application/vnd.simtech-mindmapper twd twds
application/vnd.smaf mmf
application/vnd.smart.teacher teacher
application/vnd.solent.sdkm+xml sdkm sdkd
application/vnd.spotfire.dxp dxp
application/vnd.spotfire.sfs sfs
application/vnd.stardivision.calc sdc
application/vnd.stardivision.draw sda
application/vnd.stardivision.impress sdd
application/vnd.stardivision.math smf
application/vnd.stardivision.writer sdw vor
application/vnd.stardivision.writer-global sgl
application/vnd.stepmania.package smzip
application/vnd.stepmania.stepchart sm
application/vnd.sun.xml.calc sxc
application/vnd.sun.xml.calc.template stc
application/vnd.sun.xml.draw sxd
application/vnd.sun.xml.draw.template std
application/vnd.sun.xml.impress sxi
application/vnd.sun.xml.impress.template sti
application/vnd.sun.xml.math sxm
application/vnd.sun.xml.writer sxw
application/vnd.sun.xml.writer.global sxg
application/vnd.sun.xml.writer.template stw
application/vnd.sus-calendar sus susp
application/vnd.svd svd
application/vnd.symbian.install sis sisx
application/vnd.syncml+xml xsm
application/vnd.syncml.dm+wbxml bdm
application/vnd.syncml.dm+xml xdm
application/vnd.tao.intent-module-archive tao
application/vnd.tcpdump.pcap pcap cap dmp
application/vnd.tmobile-livetv tmo
application/vnd.trid.tpt tpt
application/vnd.triscape.mxs mxs
application/vnd.trueapp tra
application/vnd.ufdl ufd ufdl
application/vnd.uiq.theme utz
application/vnd.umajin umj
application/vnd.unity unityweb
application/vnd.uoml+xml uoml
application/vnd.vcx vcx
application/vnd.visio vsd vst vss vsw
application/vnd.visionary vis
application/vnd.vsf vsf
application/vnd.wap.wbxml wbxml
application/vnd.wap.wmlc wmlc
application/vnd.wap.wmlscriptc wmlsc
application/vnd.webturbo wtb
application/vnd.wolfram.player nbp
application/vnd.wordperfect wpd
application/vnd.wqd wqd
application/vnd.wt.stf stf
application/vnd.xara xar
application/vnd.xfdl xfdl
application/vnd.yamaha.hv-dic hvd
application/vnd.yamaha.hv-script hvs
application/vnd.yamaha.hv-voice hvp
application/vnd.yamaha.openscoreformat osf
application/vnd.yamaha.openscoreformat.osfpvg+xml osfpvg
application/vnd.yamaha.smaf-audio saf
application/vnd.yamaha.smaf-phrase spf
application/vnd.yellowriver-custom-menu cmp
application/vnd.zul zir zirz
application/vnd.zzazz.deck+xml zaz
application/voicexml+xml vxml
application/widget wgt
application/winhlp hlp
application/wsdl+xml wsdl
application/wspolicy+xml wspolicy
application/x-7z-compressed 7z
application/x-abiword abw
application/x-ace-compressed ace
application/x-apple-diskimage dmg
application/x-authorware-bin aab x32 u32 vox
application/x-authorware-map aam
application/x-authorware-seg aas
application/x-bcpio bcpio
application/x-bittorrent torrent
application/x-blorb blb blorb
application/x-bzip bz
application/x-bzip2 bz2 boz
application/x-cbr cbr cba cbt cbz cb7
application/x-cdlink vcd
application/x-cfs-compressed cfs
application/x-chat chat
application/x-chess-pgn pgn
application/x-conference nsc
application/x-cpio cpio
application/x-csh csh
application/x-debian-package deb udeb
application/x-dgc-compressed dgc
application/x-director dir dcr dxr cst cct cxt w3d fgd swa
application/x-doom wad
application/x-dtbncx+xml ncx
application/x-dtbook+xml dtb
application/x-dtbresource+xml res
application/x-dvi dvi
application/x-envoy evy
application/x-eva eva
application/x-font-bdf bdf
application/x-font-ghostscript gsf
application/x-font-linux-psf psf
application/x-font-pcf pcf
application/x-font-snf snf
application/x-font-type1 pfa pfb pfm afm
application/x-freearc arc
application/x-futuresplash spl
application/x-gca-compressed gca
application/x-glulx ulx
application/x-gnumeric gnumeric
application/x-gramps-xml gramps
application/x-gtar gtar
application/x-hdf hdf
application/x-install-instructions install
application/x-iso9660-image iso
application/x-java-jnlp-file jnlp
application/x-latex latex
application/x-lzh-compressed lzh lha
application/x-mie mie
application/x-mobipocket-ebook prc mobi
application/x-ms-application application
application/x-ms-shortcut lnk
application/x-ms-wmd wmd
application/x-ms-wmz wmz
application/x-ms-xbap xbap
application/x-msaccess mdb
application/x-msbinder obd
application/x-mscardfile crd
application/x-msclip clp
application/x-msdownload exe dll com bat msi
application/x-msmediaview mvb m13 m14
application/x-msmetafile wmf wmz emf emz
application/x-msmoney mny
application/x-mspublisher pub
application/x-msschedule scd
application/x-msterminal trm
application/x-mswrite wri
application/x-netcdf nc cdf
application/x-nzb nzb
application/x-pkcs12 p12 pfx
application/x-pkcs7-certificates p7b spc
application/x-pkcs7-certreqresp p7r
application/x-rar-compressed rar
application/x-research-info-systems ris
application/x-sh sh
application/x-shar shar
application/x-shockwave-flash swf
application/x-silverlight-app xap
application/x-sql sql
application/x-stuffit sit
application/x-stuffitx sitx
application/x-subrip srt
application/x-sv4cpio sv4cpio
application/x-sv4crc sv4crc
application/x-t3vm-image t3
application/x-tads gam
application/x-tar tar
application/x-tcl tcl
application/x-tex tex
application/x-tex-tfm tfm
application/x-texinfo texinfo texi
application/x-tgif obj
application/x-ustar ustar
application/x-wais-source src
application/x-x509-ca-cert der crt
application/x-xfig fig
application/x-xliff+xml xlf
application/x-xpinstall xpi
application/x-xz xz
application/x-zmachine z1 z2 z3 z4 z5 z6 z7 z8
application/xaml+xml xaml
application/xcap-diff+xml xdf
application/xenc+xml xenc
application/xhtml+xml xhtml xht
application/xml xml xsl
application/xml-dtd dtd
application/xop+xml xop
application/xproc+xml xpl
application/xslt+xml xslt
application/xspf+xml xspf
application/xv+xml mxml xhvml xvml xvm
application/yang yang
application/yin+xml yin
application/zip zip
audio/adpcm adp
audio/basic au snd
audio/midi mid midi kar rmi
audio/mp4 m4a mp4a
audio/mpeg mpga mp2 mp2a mp3 m2a m3a
audio/ogg oga ogg spx
audio/s3m s3m
audio/silk sil
audio/vnd.dece.audio uva uvva
audio/vnd.digital-winds eol
audio/vnd.dra dra
audio/vnd.dts dts
audio/vnd.dts.hd dtshd
audio/vnd.lucent.voice lvp
audio/vnd.ms-playready.media.pya pya
audio/vnd.nuera.ecelp4800 ecelp4800
audio/vnd.nuera.ecelp7470 ecelp7470
audio/vnd.nuera.ecelp9600 ecelp9600
audio/vnd.rip rip
audio/webm weba
audio/x-aac aac
audio/x-aiff aif aiff aifc
audio/x-caf caf
audio/x-flac flac
audio/x-matroska mka
audio/x-mpegurl m3u
audio/x-ms-wax wax
audio/x-ms-wma wma
audio/x-pn-realaudio ram ra
audio/x-pn-realaudio-plugin rmp
audio/x-wav wav
audio/xm xm
chemical/x-cdx cdx
chemical/x-cif cif
chemical/x-cmdf cmdf
chemical/x-cml cml
chemical/x-csml csml
chemical/x-xyz xyz
font/collection ttc
font/otf otf
font/ttf ttf
font/woff woff
font/woff2 woff2
image/bmp bmp
image/cgm cgm
image/g3fax g3
image/gif gif
image/ief ief
image/jpeg jpeg jpg jpe
image/ktx ktx
image/png png
image/prs.btif btif
image/sgi sgi
image/svg+xml svg svgz
image/tiff tiff tif
image/vnd.adobe.photoshop psd
image/vnd.dece.graphic uvi uvvi uvg uvvg
image/vnd.djvu djvu djv
image/vnd.dvb.subtitle sub
image/vnd.dwg dwg
image/vnd.dxf dxf
image/vnd.fastbidsheet fbs
image/vnd.fpx fpx
image/vnd.fst fst
image/vnd.fujixerox.edmics-mmr mmr
image/vnd.fujixerox.edmics-rlc rlc
image/vnd.ms-modi mdi
image/vnd.ms-photo wdp
image/vnd.net-fpx npx
image/vnd.wap.wbmp wbmp
image/vnd.xiff xif
image/webp webp
image/x-3ds 3ds
image/x-cmu-raster ras
image/x-cmx cmx
image/x-freehand fh fhc fh4 fh5 fh7
image/x-icon ico
image/x-mrsid-image sid
image/x-pcx pcx
image/x-pict pic pct
image/x-portable-anymap pnm
image/x-portable-bitmap pbm
image/x-portable-graymap pgm
image/x-portable-pixmap ppm
image/x-rgb rgb
image/x-tga tga
image/x-xbitmap xbm
image/x-xpixmap xpm
image/x-xwindowdump xwd
message/rfc822 eml mime
model/iges igs iges
model/mesh msh mesh silo
model/vnd.collada+xml dae
model/vnd.dwf dwf
model/vnd.gdl gdl
model/vnd.gtw gtw
model/vnd.mts mts
model/vnd.vtu vtu
model/vrml wrl vrml
model/x3d+binary x3db x3dbz
model/x3d+vrml x3dv x3dvz
model/x3d+xml x3d x3dz
text/cache-manifest appcache
text/calendar ics ifb
text/css css
text/csv csv
text/html html htm
text/n3 n3
text/plain txt text conf def list log in
text/prs.lines.tag dsc
text/richtext rtx
text/sgml sgml sgm
text/tab-separated-values tsv
text/troff t tr roff man me ms
text/turtle ttl
text/uri-list uri uris urls
text/vcard vcard
text/vnd.curl curl
text/vnd.curl.dcurl dcurl
text/vnd.curl.mcurl mcurl
text/vnd.curl.scurl scurl
text/vnd.dvb.subtitle sub
text/vnd.fly fly
text/vnd.fmi.flexstor flx
text/vnd.graphviz gv
text/vnd.in3d.3dml 3dml
text/vnd.in3d.spot spot
text/vnd.sun.j2me.app-descriptor jad
text/vnd.wap.wml wml
text/vnd.wap.wmlscript wmls
text/x-asm s asm
text/x-c c cc cxx cpp h hh dic
text/x-fortran f for f77 f90
text/x-java-source java
text/x-nfo nfo
text/x-opml opml
text/x-pascal p pas
text/x-setext etx
text/x-sfv sfv
text/x-uuencode uu
text/x-vcalendar vcs
text/x-vcard vcf
video/3gpp 3gp
video/3gpp2 3g2
video/h261 h261
video/h263 h263
video/h264 h264
video/jpeg jpgv
video/jpm jpm jpgm
video/mj2 mj2 mjp2
video/mp4 mp4 mp4v mpg4
video/mpeg mpeg mpg mpe m1v m2v
video/ogg ogv
video/quicktime qt mov
video/vnd.dece.hd uvh uvvh
video/vnd.dece.mobile uvm uvvm
video/vnd.dece.pd uvp uvvp
video/vnd.dece.sd uvs uvvs
video/vnd.dece.video uvv uvvv
video/vnd.dvb.file dvb
video/vnd.fvt fvt
video/vnd.mpegurl mxu m4u
video/vnd.ms-playready.media.pyv pyv
video/vnd.uvvu.mp4 uvu uvvu
video/vnd.vivo viv
video/webm webm
video/x-f4v f4v
video/x-fli fli
video/x-flv flv
video/x-m4v m4v
video/x-matroska mkv mk3d mks
video/x-mng mng
video/x-ms-asf asf asx
video/x-ms-vob vob
video/x-ms-wm wm
video/x-ms-wmv wmv
video/x-ms-wmx wmx
video/x-ms-wvx wvx
video/x-msvideo avi
video/x-sgi-movie movie
video/x-smv smv
x-conference/x-cooltalk ice

View File

@@ -1,20 +0,0 @@
import mime_raw from './mime-types.md';
const map: Map<string, string> = new Map();
mime_raw.split('\n').forEach((row: string) => {
const match = /(.+?)\t+(.+)/.exec(row);
if (!match) return;
const type = match[1];
const extensions = match[2].split(' ');
extensions.forEach(ext => {
map.set(ext, type);
});
});
export function lookup(file: string) {
const match = /\.([^\.]+)$/.exec(file);
return match && map.get(match[1]);
}

View File

@@ -1,3 +1,4 @@
import { Stats } from 'fs';
import { ClientRequest, ServerResponse } from 'http';
export type ServerRoute = {
@@ -37,6 +38,18 @@ export type Props = {
[key: string]: any;
};
export interface MiddlewareOptions {
static?: {
headers?: (pathname: string, stats: Stats) => Record<string, string>
},
session?: (req: Req, res: Res) => any,
ignore?: any
}
export interface StartOptions extends MiddlewareOptions {
port?: number;
}
export interface Req extends ClientRequest {
url: string;
baseUrl: string;
@@ -44,7 +57,7 @@ export interface Req extends ClientRequest {
method: string;
path: string;
params: Record<string, string>;
query: Record<string, string>;
query: Record<string, string | string[]>;
headers: Record<string, string>;
}

737
site/package-lock.json generated
View File

@@ -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",
@@ -2377,15 +1774,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 +1837,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 +2151,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 +2191,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 +2211,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 +2313,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 +2407,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 +2496,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 +2536,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"
}
},
@@ -3394,9 +2725,9 @@
"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 +2993,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": {
@@ -3881,12 +3212,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",

View File

@@ -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"
"svelte": "^3.2.2"
}
}

View File

@@ -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%

View File

@@ -1,6 +1,6 @@
{
"background_color": "#ffffff",
"theme_color": "#ff3e00",
"theme_color": "#159794",
"name": "Sapper",
"short_name": "Sapper",
"display": "minimal-ui",

View File

@@ -122,7 +122,7 @@ export async function build({
const client_files = client_result.chunks
.filter(chunk => !chunk.file.endsWith('.map')) // SW does not need to cache sourcemap files
.map(chunk => `client/${chunk.file}`);
.map(chunk => `sapper/${chunk.file}`);
create_serviceworker_manifest({
manifest_data,

View File

@@ -333,7 +333,7 @@ class Watcher extends EventEmitter {
JSON.stringify(result.to_json(manifest_data, this.dirs), null, ' ')
);
const client_files = result.chunks.map(chunk => `client/${chunk.file}`);
const client_files = result.chunks.map(chunk => `sapper/${chunk.file}`);
create_serviceworker_manifest({
manifest_data,

View File

@@ -60,9 +60,8 @@ async function _export({
rimraf(export_dir);
copy(static_files, export_dir);
copy(path.join(build_dir, 'client'), path.join(export_dir, 'client'));
copy(path.join(build_dir, 'service-worker.js'), path.join(export_dir, 'service-worker.js'));
copy(path.join(build_dir, 'service-worker.js.map'), path.join(export_dir, 'service-worker.js.map'));
copy(path.join(build_dir, 'client'), export_dir);
copy(path.join(build_dir, 'service-worker'), export_dir);
const defaultPort = process.env.PORT ? parseInt(process.env.PORT) : 3000;
const port = await ports.find(defaultPort);

View File

@@ -1,9 +1,13 @@
import * as fs from 'fs';
import { mkdirp } from './fs_utils';
export function copy_shimport(dest: string) {
mkdirp(`${dest}/client/sapper`);
const shimport_version = require('shimport/package.json').version;
fs.writeFileSync(
`${dest}/client/shimport@${shimport_version}.js`,
`${dest}/client/sapper/shimport@${shimport_version}.js`,
fs.readFileSync(require.resolve('shimport/index.js'))
);
}

View File

@@ -9,7 +9,7 @@ export default {
},
output: () => {
let dir = `${dest}/client`;
let dir = `${dest}/client/sapper`;
if (process.env.SAPPER_LEGACY_BUILD) dir += `/legacy`;
return {
@@ -45,7 +45,7 @@ export default {
output: () => {
return {
file: `${dest}/service-worker.js`,
file: `${dest}/service-worker/service-worker.js`,
format: 'iife'
}
}

View File

@@ -12,10 +12,10 @@ export default {
output: () => {
return {
path: `${dest}/client`,
path: `${dest}/client/sapper`,
filename: '[hash]/[name].js',
chunkFilename: '[hash]/[name].[id].js',
publicPath: `client/`
publicPath: `sapper/`
};
}
},
@@ -46,7 +46,7 @@ export default {
output: () => {
return {
path: dest,
path: `${dest}/service-worker`,
filename: '[name].js',
chunkFilename: '[name].[id].[hash].js'
}

View File

@@ -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}

View File

@@ -116,7 +116,7 @@ export default function extract_css(client_result: CompileResult, components: Pa
if (!client_result.css_files) return; // Rollup-only for now
let asset_dir = `${dirs.dest}/client`;
let asset_dir = `${dirs.dest}/client/sapper`;
if (process.env.SAPPER_LEGACY_BUILD) asset_dir += '/legacy';
const unclaimed = new Set(client_result.css_files.map(x => x.id));
@@ -238,7 +238,7 @@ export default function extract_css(client_result: CompileResult, components: Pa
map.file = output_file_name;
map.sources = map.sources.map(source => path.relative(asset_dir, source));
fs.writeFileSync(`${asset_dir}/${output_file_name}`, `${code}\n/* sourceMappingURL=client/${output_file_name}.map */`);
fs.writeFileSync(`${asset_dir}/${output_file_name}`, `${code}\n/* sourceMappingURL=sapper/${output_file_name}.map */`);
fs.writeFileSync(`${asset_dir}/${output_file_name}.map`, JSON.stringify(map, null, ' '));
result.main = output_file_name;

View File

@@ -25,6 +25,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 +74,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 +105,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 +143,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 +158,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 +166,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 +187,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 +234,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 +244,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 +319,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 +331,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}`);
}

View File

@@ -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;
}

View File

@@ -1,8 +1,6 @@
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT } = process.env;
const server = sapper.server();
polka()
.use(sapper.middleware())
.listen(PORT);
start(server);

View File

@@ -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';
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(),
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'],
@@ -150,169 +140,170 @@ describe('basics', function() {
);
// TODO preload more than just the entry point
const regex = /<\/client\/client\.\w+\.js>;rel="modulepreload"/;
const regex = /<\/sapper\/client\.\w+\.js>;rel="modulepreload"/;
const link = <string>headers['link'];
assert.ok(regex.test(link), link);
});
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');
assert.equal(await r.text('h1'), 'foo');
await page.click('[href="dirs/bar"]');
await wait(50);
assert.equal(await title(), 'bar');
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();
await r.load('/abc/xyz');
await r.sapper.start();
assert.equal(await title(), 'abc,xyz');
assert.equal(await r.text('h1'), 'abc,xyz');
await page.click('[href="xyz/abc/deep"]');
await wait(50);
assert.equal(await title(), 'xyz,abc');
await r.page.click('[href="xyz/abc/deep"]');
await r.wait();
assert.equal(await r.text('h1'), 'xyz,abc');
await page.click(`[href="xyz/abc/qwe/deep.json"]`);
await wait(50);
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();
await r.load('/dirs/bar/xyz');
await r.sapper.start();
assert.equal(await title(), 'A page');
assert.equal(await r.text('h1'), 'A page');
await page.click('[href="dirs/foo/xyz"]');
await wait(50);
assert.equal(await title(), 'B page');
await r.page.click('[href="dirs/foo/xyz"]');
await r.wait();
assert.equal(await r.text('h1'), 'B page');
});
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,22 +311,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 page.goto(`${base}/skipped/x/1`);
await start();
await prefetchRoutes();
await r.load('/skipped/x/1');
await r.sapper.start();
await r.sapper.prefetchRoutes();
await page.click('a[href="skipped/y/1"]');
await wait(50);
await r.page.click('a[href="skipped/y/1"]');
await r.wait();
assert.equal(
await title(),
await r.text('h1'),
'y:1'
);
});
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
});

43
test/apps/common.js Normal file
View File

@@ -0,0 +1,43 @@
const { NODE_ENV, PORT } = process.env;
export const dev = NODE_ENV === 'development';
export function start(server) {
const port = parseInt(PORT) || 0;
server.listen(port, () => {
server = server.server || server;
const address = 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;
});

View File

@@ -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);

View File

@@ -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, []);
});
});

View File

@@ -1,8 +1,6 @@
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT } = process.env;
const server = sapper.server();
polka()
.use(sapper.middleware())
.listen(PORT);
start(server);

View File

@@ -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, []);
});
});

View File

@@ -1,8 +1,6 @@
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT } = process.env;
const server = sapper.server();
polka()
.use(sapper.middleware())
.listen(PORT);
start(server);

View File

@@ -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, []);
});
});

View File

@@ -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>

View File

@@ -1,8 +1,6 @@
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT } = process.env;
const server = sapper.server();
polka()
.use(sapper.middleware())
.listen(PORT);
start(server);

View File

@@ -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, []);
});
});

View File

@@ -1,15 +1,6 @@
import sirv from 'sirv';
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
const server = sapper.server();
polka()
.use(
sirv('static', { dev }),
sapper.middleware()
)
.listen(PORT, err => {
if (err) console.log('error', err);
});
start(server);

View File

@@ -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));
});
});

View File

@@ -1,15 +1,6 @@
import sirv from 'sirv';
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
const server = sapper.server();
polka()
.use(
sirv('static', { dev }),
sapper.middleware()
)
.listen(PORT, err => {
if (err) console.log('error', err);
});
start(server);

View File

@@ -6,16 +6,15 @@ 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`);
const client_assets = files.filter(file => file.startsWith('client/'));
const non_client_assets = files.filter(file => !file.startsWith('client/')).sort();
const client_assets = files.filter(file => file.startsWith('sapper/'));
const non_client_assets = files.filter(file => !file.startsWith('sapper/')).sort();
assert.ok(client_assets.length > 0);
@@ -45,4 +44,4 @@ describe('export', function() {
});
// TODO test timeout, basepath
});
});

View File

@@ -1,3 +1,8 @@
<h1>{status}</h1>
<script>
export let status;
export let error;
</script>
<h1>{status}</h1>
<p>{error.message}</p>

View File

@@ -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);

View File

@@ -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, []);
});
});

View File

@@ -1,8 +1,6 @@
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT } = process.env;
const server = sapper.server();
polka()
.use(sapper.middleware())
.listen(PORT);
start(server);

View File

@@ -1,33 +1,25 @@
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',
@@ -35,8 +27,8 @@ describe('layout', function() {
'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',
@@ -44,10 +36,10 @@ describe('layout', function() {
'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',
@@ -55,4 +47,8 @@ describe('layout', function() {
'child segment: qux'
]);
});
});
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
});

View File

@@ -1,8 +1,6 @@
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT } = process.env;
const server = sapper.server();
polka()
.use(sapper.middleware())
.listen(PORT);
start(server);

View File

@@ -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,106 @@ 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, []);
});
});

View File

@@ -1,8 +1,6 @@
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT } = process.env;
const server = sapper.server();
polka()
.use(sapper.middleware())
.listen(PORT);
start(server);

View File

@@ -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, []);
});
});

View File

@@ -1,8 +1,6 @@
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT } = process.env;
const server = sapper.server();
polka()
.use(sapper.middleware())
.listen(PORT);
start(server);

View File

@@ -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, []);
});
});

View File

@@ -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);

View File

@@ -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, []);
});
});

View File

@@ -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);

View File

@@ -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'
@@ -55,8 +40,8 @@ describe('with-basepath', function() {
it('crawls an exported site with basepath', () => {
const files = walk(`${__dirname}/__sapper__/export`);
const client_assets = files.filter(file => file.startsWith('custom-basepath/client/'));
const non_client_assets = files.filter(file => !file.startsWith('custom-basepath/client/')).sort();
const client_assets = files.filter(file => file.startsWith('custom-basepath/sapper/'));
const non_client_assets = files.filter(file => !file.startsWith('custom-basepath/sapper/')).sort();
assert.ok(client_assets.length > 0);
@@ -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, []);
});
});

View File

@@ -1,8 +1,6 @@
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT } = process.env;
const server = sapper.server();
polka()
.use(sapper.middleware())
.listen(PORT);
start(server);

View File

@@ -7,10 +7,9 @@ describe('with-sourcemaps-webpack', function() {
this.timeout(10000);
// hooks
before(async () => {
await build({ cwd: __dirname, bundler: 'webpack' });
});
before('build app', () => build({ cwd: __dirname, bundler: 'webpack' }));
// tests
it('does not put sourcemap files in service worker shell', async () => {
const service_worker_source = fs.readFileSync(`${__dirname}/src/node_modules/@sapper/service-worker.js`, 'utf-8');
const shell_source = /shell = (\[[\s\S]+?\])/.exec(service_worker_source)[1];
@@ -19,8 +18,8 @@ describe('with-sourcemaps-webpack', function() {
assert.equal(shell.filter(_ => _.endsWith('.map')).length, 0,
'sourcemap files are not cached in SW');
const clientShellDir = path.resolve(`${__dirname}/__sapper__/build`, path.dirname(shell[0]));
const sourcemapFiles = fs.readdirSync(clientShellDir).filter(_ => _.endsWith('.map'));
assert.ok(sourcemapFiles.length > 0, 'sourcemap files exist');
const client_shell_dir = path.resolve(`${__dirname}/__sapper__/build/client`, path.dirname(shell[0]));
const sourcemap_files = fs.readdirSync(client_shell_dir).filter(_ => _.endsWith('.map'));
assert.ok(sourcemap_files.length > 0, 'sourcemap files exist');
});
});
});

View File

@@ -1,8 +1,6 @@
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const { PORT } = process.env;
const server = sapper.server();
polka()
.use(sapper.middleware())
.listen(PORT);
start(server);

View File

@@ -7,10 +7,9 @@ describe('with-sourcemaps', function() {
this.timeout(10000);
// hooks
before(async () => {
await build({ cwd: __dirname });
});
before('build app', () => build({ cwd: __dirname }));
// tests
it('does not put sourcemap files in service worker shell', async () => {
const service_worker_source = fs.readFileSync(`${__dirname}/src/node_modules/@sapper/service-worker.js`, 'utf-8');
const shell_source = /shell = (\[[\s\S]+?\])/.exec(service_worker_source)[1];
@@ -19,8 +18,8 @@ describe('with-sourcemaps', function() {
assert.equal(shell.filter(_ => _.endsWith('.map')).length, 0,
'sourcemap files are not cached in SW');
const clientShellDir = path.resolve(`${__dirname}/__sapper__/build`, path.dirname(shell[0]));
const sourcemapFiles = fs.readdirSync(clientShellDir).filter(_ => _.endsWith('.map'));
assert.ok(sourcemapFiles.length > 0, 'sourcemap files exist');
const client_shell_dir = path.resolve(`${__dirname}/__sapper__/build/client`, path.dirname(shell[0]));
const sourcemap_files = fs.readdirSync(client_shell_dir).filter(_ => _.endsWith('.map'));
assert.ok(sourcemap_files.length > 0, 'sourcemap files exist');
});
});
});

View File

@@ -20,7 +20,7 @@ describe('manifest_data', () => {
assert.deepEqual(pages, [
{
pattern: /^\/?$/,
pattern: /^\/$/,
parts: [
{ component: index, params: [] }
]
@@ -50,6 +50,13 @@ describe('manifest_data', () => {
]);
assert.deepEqual(server_routes, [
{
name: 'route_index',
pattern: /^\/$/,
file: 'index.js',
params: []
},
{
name: 'route_blog_json',
pattern: /^\/blog.json$/,
@@ -167,7 +174,7 @@ describe('manifest_data', () => {
file: 'foo.js',
name: 'route_foo',
params: [],
pattern: /^\/foo$/
pattern: /^\/foo\/?$/
}]);
});
});