Compare commits

..

29 Commits

Author SHA1 Message Date
Conduitry
d0caad5303 -> v0.27.2 2019-06-08 18:50:52 -04:00
cudr
b1e84687c0 repair regexp routes 2019-06-08 18:34:09 -04:00
Conduitry
993bd6cc5b site: make readme more relevant 2019-06-06 17:17:14 -04:00
Conduitry
d4ee7ab040 site: quote degit repo argument (#726) 2019-06-06 17:13:11 -04:00
Poluektov Dmitriy
43e7f0d30d fix: cli ignores --output option (#723) 2019-06-06 06:57:17 -04:00
Conduitry
d7e2662298 site: bump sapper 2019-05-25 18:51:53 -04:00
Richard Harris
a86b613e12 -> v0.27.1 2019-05-21 00:17:22 -04:00
Rich Harris
fcd66fb736 Merge pull request #704 from sveltejs/gh-701
always re-run preload functions when query string changes
2019-05-21 00:12:09 -04:00
Richard Harris
b6e2736eb0 always re-run preload functions when query string changes - fixes #701 2019-05-20 23:45:39 -04:00
Richard Harris
aa31e3ca6a refactor slightly 2019-05-20 23:11:50 -04:00
Richard Harris
8b310dd458 Merge branch 'issues/688' of https://github.com/btakita/sapper into btakita-issues/688 2019-05-20 23:08:58 -04:00
Brian Takita
5460896228 Checking the current_branch[i] route match against the current request's route match.
current_branch[i].match.slice(1, i+2) compared to match.slice(1, i+2)

Fixes https://github.com/sveltejs/sapper/issues/688
2019-05-20 23:03:08 -04:00
Rich Harris
bcf6ac3a6f Merge pull request #692 from sveltejs/gh-331
leave anchor scrolling on initial load to browser
2019-05-20 22:37:26 -04:00
Rich Harris
b8d99aaa90 Merge pull request #632 from pngwn/master
Add support for custom route file extensions.
2019-05-20 22:33:56 -04:00
Rich Harris
2bb173f140 Merge pull request #683 from cristianl/pr-make-live-optional
Allow disabling live reload
2019-05-20 22:24:06 -04:00
Rich Harris
594cb0a356 Merge pull request #694 from jarrodldavis/fix/restart-server
Fix restarting server in dev
2019-05-20 22:23:29 -04:00
Rich Harris
9ff87af23b Merge pull request #698 from mrkishi/gh-689
Fix export queue
2019-05-20 22:20:59 -04:00
Conduitry
75afc691f4 site: fix doc hash link destinations (#696) 2019-05-18 15:41:59 -04:00
mrkishi
9dd63ab760 fix export queue 2019-05-17 16:50:08 -03:00
pngwn
e7b1aa373a Add support for custom route file extensions. 2019-05-17 19:19:04 +01:00
Antony Jones
ce50c2ff98 License is MIT, not LIL 2019-05-16 11:42:20 -04:00
Jarrod Davis
6add2518aa Fix restarting server in dev
Wait for the server process to exit before starting a new one, so that
the server debugger port is free when the new process begins.
2019-05-15 02:28:41 -06:00
Conduitry
ab939b8cb5 site: update links to Discord chat 2019-05-15 00:34:48 -04:00
Conduitry
b59d9003f9 leave anchor scrolling on initial load to browser (#331) 2019-05-13 08:56:21 -04:00
Cristian Lorsson
1fc169e7b8 Reload only when live reload is enabled 2019-05-10 15:08:32 -03:00
Rich Harris
7aa3e90f87 Merge pull request #677 from mrkishi/infinite-loop
Abort infinite loop for preload errors
2019-05-10 09:45:11 -04:00
Rich Harris
b27c9ecd59 Merge pull request #681 from Troush/patch-1
Typo fix for proper routing link
2019-05-10 09:13:56 -04:00
Alex
8653a799eb Typo fix for proper routing link 2019-05-10 14:50:42 +03:00
mrkishi
62969d59f6 Abort infinite loop for preload errors 2019-05-09 10:40:23 -03:00
94 changed files with 1860 additions and 380 deletions

View File

@@ -1,5 +1,23 @@
# sapper changelog
## 0.27.2
* Fix routes with regular expressions ([#707](https://github.com/sveltejs/sapper/issues/707))
* Fix `sapper build --output` option ([#723](https://github.com/sveltejs/sapper/pull/723))
## 0.27.1
* Prevent infinite loop if `preload` errors ([#677](https://github.com/sveltejs/sapper/pull/677))
* Allow disabling of live reload ([#683](https://github.com/sveltejs/sapper/pull/683))
* Let browser handle initial scroll ([#331](https://github.com/sveltejs/sapper/issues/331))
* Allow custom route file extensions via `--ext` ([#632](https://github.com/sveltejs/sapper/pull/632))
* Wait for server to restart before attaching debugger ([#694](https://github.com/sveltejs/sapper/pull/694))
* Fix export queue ([#698](https://github.com/sveltejs/sapper/pull/698))
* Rerun `preload` functions when query changes ([#701](https://github.com/sveltejs/sapper/issues/701))
* Navigate when spread route changes ([#688](https://github.com/sveltejs/sapper/issues/688))
## 0.27.0
* Change license from LIL to MIT ([#652](https://github.com/sveltejs/sapper/pull/652))
@@ -611,4 +629,4 @@
## 0.1.0
* First public preview
* First public preview

View File

@@ -74,4 +74,4 @@ npm run test
## License
[LIL](LICENSE)
[MIT](LICENSE)

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "sapper",
"version": "0.27.0",
"version": "0.27.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "sapper",
"version": "0.27.0",
"version": "0.27.2",
"description": "Military-grade apps, engineered by Svelte",
"bin": {
"sapper": "./sapper"

View File

@@ -24,9 +24,8 @@ function template(kind, external) {
external,
plugins: [
resolve({
extensions: ['.mjs', '.js', '.json', '.ts']
extensions: ['.mjs', '.js', '.ts']
}),
json(),
commonjs(),
string({
include: '**/*.md'

View File

@@ -22,6 +22,7 @@ let root_component: Component;
let current_token: {};
let root_preloaded: Promise<any>;
let current_branch = [];
let current_query = '{}';
const stores = {
page: writable({}),
@@ -250,10 +251,28 @@ async function render(redirect: Redirect, branch: any[], props: any, page: Page)
}
current_branch = branch;
current_query = JSON.stringify(page.query);
ready = true;
session_dirty = false;
}
function part_changed(i, segment, match, stringified_query) {
// TODO only check query string changes for preload functions
// that do in fact depend on it (using static analysis or
// runtime instrumentation)
if (stringified_query !== current_query) return true;
const previous = current_branch[i];
if (!previous) return false;
if (segment !== previous.segment) return true;
if (previous.match) {
if (JSON.stringify(previous.match.slice(1, i + 2)) !== JSON.stringify(match.slice(1, i + 2))) {
return true;
}
}
}
export async function hydrate_target(target: Target): Promise<{
redirect?: Redirect;
props?: any;
@@ -292,11 +311,15 @@ export async function hydrate_target(target: Target): Promise<{
let l = 1;
try {
const stringified_query = JSON.stringify(page.query);
const match = route.pattern.exec(page.path);
let segment_dirty = false;
branch = await Promise.all(route.parts.map(async (part, i) => {
const segment = segments[i];
if (current_branch[i] && current_branch[i].segment !== segment) segment_dirty = true;
if (part_changed(i, segment, match, stringified_query)) segment_dirty = true;
props.segments[l] = segments[i + 1]; // TODO make this less confusing
if (!part) return { segment };
@@ -324,7 +347,7 @@ export async function hydrate_target(target: Target): Promise<{
preloaded = initial_data.preloaded[i + 1];
}
return (props[`level${j}`] = { component, props: preloaded, segment, part: part.i });
return (props[`level${j}`] = { component, props: preloaded, segment, match, part: part.i });
}));
} catch (error) {
props.error = error;
@@ -336,7 +359,7 @@ export async function hydrate_target(target: Target): Promise<{
}
function load_css(chunk: string) {
const href = `sapper/${chunk}`;
const href = `client/${chunk}`;
if (document.querySelector(`link[href="${href}"]`)) return;
return new Promise((fulfil, reject) => {

View File

@@ -40,7 +40,7 @@ export default function start(opts: {
if (initial_data.error) return handle_error(url);
const target = select_target(url);
if (target) return navigate(target, uid, false, hash);
if (target) return navigate(target, uid, true, hash);
});
}

View File

@@ -1,17 +1 @@
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;
}
export { default as middleware } from './middleware/index';

View File

@@ -5,7 +5,7 @@ import cookie from 'cookie';
import devalue from 'devalue';
import fetch from 'node-fetch';
import URL from 'url';
import { Manifest, Page, Req, Res } from '../types';
import { Manifest, Page, Req, Res } from './types';
import { build_dir, dev, src_dir } from '@sapper/internal/manifest-server';
import App from '@sapper/internal/App.svelte';
@@ -21,10 +21,20 @@ 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/service-worker.js'));
const has_service_worker = fs.existsSync(path.join(build_dir, 'service-worker.js'));
const { server_routes, pages } = manifest;
const error_route = manifest.error;
function bail(req: Req, res: Res, err: Error) {
console.error(err);
const message = dev ? escape_html(err.message) : 'Internal server error';
res.statusCode = 500;
res.end(`<pre>${message}</pre>`);
}
function handle_error(req: Req, res: Res, statusCode: number, error: Error | string) {
handle_page({
pattern: null,
@@ -62,7 +72,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}/sapper/${file}>;rel="modulepreload"`)
.map(file => `<${req.baseUrl}/client/${file}>;rel="modulepreload"`)
.join(', ');
res.setHeader('Link', link);
@@ -71,7 +81,7 @@ export function get_page_handler(
.filter(file => file && !file.match(/\.map$/))
.map((file) => {
const as = /\.css$/.test(file) ? 'style' : 'script';
return `<${req.baseUrl}/sapper/${file}>;rel="preload";as="${as}"`;
return `<${req.baseUrl}/client/${file}>;rel="preload";as="${as}"`;
})
.join(', ');
@@ -168,6 +178,10 @@ export function get_page_handler(
preloaded = await Promise.all(toPreload);
} catch (err) {
if (error) {
return bail(req, res, err)
}
preload_error = { statusCode: 500, message: err };
preloaded = []; // appease TypeScript
}
@@ -262,14 +276,14 @@ export function get_page_handler(
}
const file = [].concat(build_info.assets.main).filter(file => file && /\.js$/.test(file))[0];
const main = `${req.baseUrl}/sapper/${file}`;
const main = `${req.baseUrl}/client/${file}`;
if (build_info.bundler === 'rollup') {
if (build_info.legacy_assets) {
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);}());`;
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);}());`;
} 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}/sapper/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}/client/shimport@${build_info.shimport}.js";s.setAttribute("data-main","${main}")}document.head.appendChild(s)`;
}
} else {
script += `</script><script src="${main}">`;
@@ -294,7 +308,7 @@ export function get_page_handler(
});
styles = Array.from(css_chunks)
.map(href => `<link rel="stylesheet" href="sapper/${href}">`)
.map(href => `<link rel="stylesheet" href="client/${href}">`)
.join('')
} else {
styles = (css && css.code ? `<style>${css.code}</style>` : '');
@@ -313,11 +327,8 @@ export function get_page_handler(
res.statusCode = status;
res.end(body);
} catch(err) {
console.log(err);
if (error) {
// we encountered an error while rendering the error page — oops
res.statusCode = 500;
res.end(`<pre>${escape_html(err.message)}</pre>`);
bail(req, res, err)
} else {
handle_error(req, res, 500, err);
}
@@ -326,12 +337,12 @@ export function get_page_handler(
return function find_route(req: Req, res: Res, next: () => void) {
if (req.path === '/service-worker-index.html') {
const homePage = manifest.pages.find(page => page.pattern.test('/'));
const homePage = pages.find(page => page.pattern.test('/'));
handle_page(homePage, req, res);
return;
}
for (const page of manifest.pages) {
for (const page of pages) {
if (page.pattern.test(req.path)) {
handle_page(page, req, res);
return;

View File

@@ -1,4 +1,4 @@
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) {

View File

@@ -1,42 +1,23 @@
import fs from 'fs';
import querystring from 'querystring';
import sirv from 'sirv';
import path from 'path';
import { build_dir, dev, manifest } from '@sapper/internal/manifest-server';
import { Handler, Req, Res, MiddlewareOptions } from '../types';
import { Handler, Req, Res } from './types';
import { get_server_route_handler } from './get_server_route_handler';
import { get_page_handler } from './get_page_handler';
import { lookup } from './mime';
export default function middleware(opts: MiddlewareOptions = {}) {
export default function middleware(opts: {
session?: (req: Req, res: Res) => any,
ignore?: any
} = {}) {
const { session, ignore } = opts;
let emitted_basepath = false;
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]);
})
}),
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)) : {}
(req: Req, res: Res, next: () => void) => {
if (req.baseUrl === undefined) {
let originalUrl = req.originalUrl || req.url;
let { originalUrl } = req;
if (req.url === '/' && originalUrl[originalUrl.length - 1] !== '/') {
originalUrl += '/';
}
@@ -63,6 +44,21 @@ export default function middleware(opts: MiddlewareOptions = {}) {
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)
@@ -98,4 +94,40 @@ 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 (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

@@ -0,0 +1,767 @@
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

@@ -0,0 +1,20 @@
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,4 +1,3 @@
import { Stats } from 'fs';
import { ClientRequest, ServerResponse } from 'http';
export type ServerRoute = {
@@ -38,18 +37,6 @@ 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;
@@ -57,7 +44,7 @@ export interface Req extends ClientRequest {
method: string;
path: string;
params: Record<string, string>;
query: Record<string, string | string[]>;
query: Record<string, string>;
headers: Record<string, string>;
}

View File

@@ -1,88 +1,17 @@
# sapper-template
## Running locally
The default [Sapper](https://github.com/sveltejs/sapper) template, with branches for Rollup and webpack. To clone it and get started:
Set up the project:
```bash
# for Rollup
npx degit sveltejs/sapper-template#rollup my-app
# for webpack
npx degit sveltejs/sapper-template#webpack my-app
cd my-app
npm install # or yarn!
npm run dev
git clone https://github.com/sveltejs/sapper.git
cd sapper/site
npm ci
```
Open up [localhost:3000](http://localhost:3000) and start clicking around.
Start the server with `npm run dev`, and navigate to [localhost:3000](http://localhost:3000).
Consult [sapper.svelte.technology](https://sapper.svelte.technology) for help getting started.
## Translating the API docs
Anchors are automatically generated using headings in the documentation and by default (for the english language) they are latinised to make sure the URL is always conforming to RFC3986.
## Structure
Sapper expects to find two directories in the root of your project — `src` and `static`.
### src
The [src](src) directory contains the entry points for your app — `client.js`, `server.js` and (optionally) a `service-worker.js` — along with a `template.html` file and a `routes` directory.
#### src/routes
This is the heart of your Sapper app. There are two kinds of routes — *pages*, and *server routes*.
**Pages** are Svelte components written in `.html` files. When a user first visits the application, they will be served a server-rendered version of the route in question, plus some JavaScript that 'hydrates' the page and initialises a client-side router. From that point forward, navigating to other pages is handled entirely on the client for a fast, app-like feel. (Sapper will preload and cache the code for these subsequent pages, so that navigation is instantaneous.)
**Server routes** are modules written in `.js` files, that export functions corresponding to HTTP methods. Each function receives Express `request` and `response` objects as arguments, plus a `next` function. This is useful for creating a JSON API, for example.
There are three simple rules for naming the files that define your routes:
* A file called `src/routes/about.html` corresponds to the `/about` route. A file called `src/routes/blog/[slug].html` corresponds to the `/blog/:slug` route, in which case `params.slug` is available to the route
* The file `src/routes/index.html` (or `src/routes/index.js`) corresponds to the root of your app. `src/routes/about/index.html` is treated the same as `src/routes/about.html`.
* Files and directories with a leading underscore do *not* create routes. This allows you to colocate helper modules and components with the routes that depend on them — for example you could have a file called `src/routes/_helpers/datetime.js` and it would *not* create a `/_helpers/datetime` route
### static
The [static](static) directory contains any static assets that should be available. These are served using [sirv](https://github.com/lukeed/sirv).
In your [service-worker.js](app/service-worker.js) file, you can import these as `files` from the generated manifest...
```js
import { files } from '../__sapper__/service-worker.js';
```
...so that you can cache them (though you can choose not to, for example if you don't want to cache very large files).
## Bundler config
Sapper uses Rollup or webpack to provide code-splitting and dynamic imports, as well as compiling your Svelte components. With webpack, it also provides hot module reloading. As long as you don't do anything daft, you can edit the configuration files to add whatever plugins you'd like.
## Production mode and deployment
To start a production version of your app, run `npm run build && npm start`. This will disable live reloading, and activate the appropriate bundler plugins.
You can deploy your application to any environment that supports Node 8 or above. As an example, to deploy to [Now](https://zeit.co/now), run these commands:
```bash
npm install -g now
now
```
## Using external components with webpack
When using Svelte components installed from npm, such as [@sveltejs/svelte-virtual-list](https://github.com/sveltejs/svelte-virtual-list), Svelte needs the original component source (rather than any precompiled JavaScript that ships with the component). This allows the component to be rendered server-side, and also keeps your client-side app smaller.
Because of that, it's essential that webpack doesn't treat the package as an *external dependency*. You can either modify the `externals` option in [webpack/server.config.js](webpack/server.config.js), or simply install the package to `devDependencies` rather than `dependencies`, which will cause it to get bundled (and therefore compiled) with your app:
```bash
yarn add -D @sveltejs/svelte-virtual-list
```
## Bugs and feedback
Sapper is in early development, and may have the odd rough edge here and there. Please be vocal over on the [Sapper issue tracker](https://github.com/sveltejs/sapper/issues).
If we need to translate the API documentation to a language using unicode chars, we can setup this app to export the correct anchors by setting up `SLUG_PRESERVE_UNICODE` to `true` in `config.js`.

View File

@@ -4,7 +4,7 @@ title: Introduction
### Before we begin
> Sapper is in early development, and some things may change before we hit version 1.0. This document is a work-in-progress. If you get stuck, reach out for help in the [Discord chatroom](https://discord.gg/yy75DKs).
> Sapper is in early development, and some things may change before we hit version 1.0. This document is a work-in-progress. If you get stuck, reach out for help in the [Discord chatroom](https://svelte.dev/chat).
>
> See the [migration guides](migrating) for help upgrading from older versions.
@@ -32,7 +32,7 @@ For web developers, the stakes are generally lower than for combat engineers. Bu
[Next.js](https://github.com/zeit/next.js) is a React framework from [Zeit](https://zeit.co), and is the inspiration for Sapper. There are a few notable differences, however:
* Sapper is powered by Svelte instead of React, so it's faster and your apps are smaller
* Instead of route masking, we encode route parameters in filenames (see the [routing](docs#routing) section below)
* Instead of route masking, we encode route parameters in filenames (see the [routing](docs#Routing) section below)
* As well as *pages*, you can create *server routes* in your `src/routes` directory. This makes it very easy to, for example, add a JSON API such as the one powering this very page (try visiting [/docs.json](/docs.json))
* Links are just `<a>` elements, rather than framework-specific `<Link>` components. That means, for example, that [this link right here](/), despite being inside a blob of markdown, works with the router as you'd expect
@@ -42,8 +42,8 @@ For web developers, the stakes are generally lower than for combat engineers. Bu
The easiest way to start building a Sapper app is to clone the [sapper-template](https://github.com/sveltejs/sapper-template) repo with [degit](https://github.com/Rich-Harris/degit):
```bash
npx degit sveltejs/sapper-template#rollup my-app
# or: npx degit sveltejs/sapper-template#webpack my-app
npx degit "sveltejs/sapper-template#rollup" my-app
# or: npx degit "sveltejs/sapper-template#webpack" my-app
cd my-app
npm install
npm run dev

View File

@@ -24,9 +24,9 @@ If you take a look inside the [sapper-template](https://github.com/sveltejs/sapp
When you first run Sapper, it will create an additional `__sapper__` directory containing generated files.
You'll notice a few extra files and a `cypress` directory which relates to [testing](docs#testing) — we don't need to worry about those right now.
You'll notice a few extra files and a `cypress` directory which relates to [testing](docs#Testing) — we don't need to worry about those right now.
> You *can* create these files from scratch, but it's much better to use the template. See [getting started](docs#getting-started) for instructions on how to easily clone it
> You *can* create these files from scratch, but it's much better to use the template. See [getting started](docs#Getting_started) for instructions on how to easily clone it
### package.json
@@ -35,9 +35,9 @@ Your package.json contains your app's dependencies and defines a number of scrip
* `npm run dev` — start the app in development mode, and watch source files for changes
* `npm run build` — build the app in production mode
* `npm run export` — bake out a static version, if applicable (see [exporting](docs#exporting))
* `npm run export` — bake out a static version, if applicable (see [exporting](docs#Exporting))
* `npm start` — start the app in production mode after you've built it
* `npm test` — run the tests (see [testing](docs#testing))
* `npm test` — run the tests (see [testing](docs#Testing))
### src
@@ -56,7 +56,7 @@ sapper.start({
});
```
In many cases, that's the entirety of your entry module, though you can do as much or as little here as you wish. See the [client API](docs#client-api) section for more information on functions you can import.
In many cases, that's the entirety of your entry module, though you can do as much or as little here as you wish. See the [client API](docs#Client_API) section for more information on functions you can import.
#### src/server.js
@@ -88,7 +88,7 @@ Because every app needs a slightly different service worker (sometimes it's appr
This file is a template for responses from the server. Sapper will inject content that replaces the following tags:
* `%sapper.base%` — a `<base>` element (see [base URLs](docs#base-urls))
* `%sapper.base%` — a `<base>` element (see [base URLs](docs#Base_URLs))
* `%sapper.styles%` — critical CSS for the page being requested
* `%sapper.head%` — HTML representing page-specific `<head>` contents, like `<title>`
* `%sapper.html%` — HTML representing the body of the page being rendered
@@ -97,7 +97,7 @@ This file is a template for responses from the server. Sapper will inject conten
### src/routes
This is the meat of your app — the pages and server routes. See the section on [routing](docs#routing) for the juicy details.
This is the meat of your app — the pages and server routes. See the section on [routing](docs#Routing) for the juicy details.
### static

View File

@@ -70,7 +70,7 @@ Dynamic parameters are encoded using `[brackets]`. For example, here's how you c
</div>
```
> See the section on [preloading](docs#preloading) for more info about `preload` and `this.fetch`
> See the section on [preloading](docs#Preloading) for more info about `preload` and `this.fetch`
### Server routes

View File

@@ -36,7 +36,7 @@ Programmatically navigates to the given `href`. If the destination is a Sapper r
* `href` — the page to prefetch
Programmatically prefetches the given page, which means a) ensuring that the code for the page is loaded, and b) calling the page's `preload` method with the appropriate options. This is the same behaviour that Sapper triggers when the user taps or mouses over an `<a>` element with [rel=prefetch](docs#prefetching).
Programmatically prefetches the given page, which means a) ensuring that the code for the page is loaded, and b) calling the page's `preload` method with the appropriate options. This is the same behaviour that Sapper triggers when the user taps or mouses over an `<a>` element with [rel=prefetch](docs#Prefetching).

View File

@@ -2,7 +2,7 @@
title: Preloading
---
As seen in the [routing](docs#routing) section, page components can have an optional `preload` function that will load some data that the page depends on. This is similar to `getInitialProps` in Next.js or `asyncData` in Nuxt.js.
As seen in the [routing](docs#Routing) section, page components can have an optional `preload` function that will load some data that the page depends on. This is similar to `getInitialProps` in Next.js or `asyncData` in Nuxt.js.
```html
<script context="module">

View File

@@ -21,7 +21,7 @@ express() // or Polka, or a similar framework
Sapper will detect the base path and configure both the server-side and client-side routers accordingly.
If you're [exporting](docs#exporting) your app, you will need to tell the exporter where to begin crawling:
If you're [exporting](docs#Exporting) your app, you will need to tell the exporter where to begin crawling:
```bash
sapper export --basepath my-base-path

View File

@@ -292,7 +292,7 @@ Once your `App.html` has been created and your server and client apps updated, y
##### app/template.html
* Your `<head>` element must contain `%sapper.base%` (see ([base URLs](docs#base-urls))
* Your `<head>` element must contain `%sapper.base%` (see ([base URLs](docs#Base_URLs))
* Remove references to your service worker; this is now handled by `%sapper.scripts%`
##### Pages

58
site/package-lock.json generated
View File

@@ -1705,26 +1705,18 @@
"dev": true
},
"html-minifier": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz",
"integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
"integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
"dev": true,
"requires": {
"camel-case": "3.0.x",
"clean-css": "4.2.x",
"commander": "2.17.x",
"he": "1.2.x",
"param-case": "2.1.x",
"relateurl": "0.2.x",
"uglify-js": "3.4.x"
},
"dependencies": {
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
"dev": true
}
"camel-case": "^3.0.0",
"clean-css": "^4.2.1",
"commander": "^2.19.0",
"he": "^1.2.0",
"param-case": "^2.1.1",
"relateurl": "^0.2.7",
"uglify-js": "^3.5.1"
}
},
"http-link-header": {
@@ -2638,14 +2630,14 @@
}
},
"sapper": {
"version": "0.26.0-alpha.12",
"resolved": "https://registry.npmjs.org/sapper/-/sapper-0.26.0-alpha.12.tgz",
"integrity": "sha512-NEXr6Eu5jawY76N5IEQhKMKhcZW6+42E2alH4J8DxFMmOI7Gi2nlwCQ2jcZv7q/S+zMP+OSuqE44c94A5u1H8Q==",
"version": "0.27.1",
"resolved": "https://registry.npmjs.org/sapper/-/sapper-0.27.1.tgz",
"integrity": "sha512-RH0K1uQ3zJ1IXvowxr2SuboGXV69q22KaPMhhoM5VNDv9fsUlVHtluZE8WTcGxckiO2L1xFfgM7v/aINkSZpcw==",
"dev": true,
"requires": {
"html-minifier": "^3.5.21",
"html-minifier": "^4.0.0",
"http-link-header": "^1.0.2",
"shimport": "0.0.14",
"shimport": "^1.0.0",
"sourcemap-codec": "^1.4.4",
"string-hash": "^1.1.3"
}
@@ -2719,9 +2711,9 @@
}
},
"shimport": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/shimport/-/shimport-0.0.14.tgz",
"integrity": "sha512-JfzpHhyZ6CYuPYUSitFNw2/IxWEABLN1chs9xDHnn4tPXXCmfIn1METCmIl8Vwvm2FZGW7b8WB4zMCKaJEhY6A==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shimport/-/shimport-1.0.0.tgz",
"integrity": "sha512-XKd+39voZT1rFR1ct+pr8sFSYAW6IvM3LeF87FrgcGHc/uSZ4GfOZVA42LE5LXFOpTWgmDC5sS8DNDtiV4Vd4Q==",
"dev": true
},
"sirv": {
@@ -3086,21 +3078,15 @@
}
},
"uglify-js": {
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
"integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==",
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.15.tgz",
"integrity": "sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg==",
"dev": true,
"requires": {
"commander": "~2.19.0",
"commander": "~2.20.0",
"source-map": "~0.6.1"
},
"dependencies": {
"commander": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",

View File

@@ -37,7 +37,7 @@
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^4.0.4",
"sapper": "alpha",
"sapper": "^0.27.1",
"svelte": "^3.2.2"
}
}

View File

@@ -61,7 +61,7 @@
<p>Please try reloading the page.</p>
{/if}
<p>If the error persists, please drop by <a href="https://discord.gg/yy75DKs">Discord chatroom</a> and let us know, or raise an issue on <a href="https://github.com/sveltejs/svelte">GitHub</a>. Thanks!</p>
<p>If the error persists, please drop by <a href="https://svelte.dev/chat">Discord chatroom</a> and let us know, or raise an issue on <a href="https://github.com/sveltejs/svelte">GitHub</a>. Thanks!</p>
{/if}
{:else}
<h1>It looks like you're offline</h1>

View File

@@ -1,8 +1,10 @@
<script>
import { page } from '@sapper/app';
import { stores } from '@sapper/app';
import { Icons, Icon, Nav, NavItem } from '@sveltejs/site-kit';
export let segment;
const { page } = stores();
</script>
<style>
@@ -26,7 +28,7 @@
<NavItem external="https://svelte.dev">Svelte</NavItem>
<NavItem external="https://discord.gg/yy75DKs" title="Discord Chat">
<NavItem external="https://svelte.dev/chat" title="Discord Chat">
<Icon name="message-square"/>
</NavItem>
@@ -37,4 +39,4 @@
<main>
<slot></slot>
</main>
</main>

View File

@@ -50,9 +50,9 @@
<div style="grid-area: start; display: flex; flex-direction: column; min-width: 0" slot="how">
<pre class="language-bash" style="margin: 0 0 1em 0; min-width: 0; min-height: 0">
# for Rollup
npx degit sveltejs/sapper-template#rollup my-app
npx degit "sveltejs/sapper-template#rollup" my-app
# for webpack
npx degit sveltejs/sapper-template#webpack my-app
npx degit "sveltejs/sapper-template#webpack" my-app
cd my-app
npm install

View File

@@ -19,6 +19,7 @@ type Opts = {
static?: string;
legacy?: boolean;
bundler?: 'rollup' | 'webpack';
ext?: string;
oncompile?: ({ type, result }: { type: string, result: CompileResult }) => void;
};
@@ -32,6 +33,7 @@ export async function build({
bundler,
legacy = false,
ext,
oncompile = noop
}: Opts = {}) {
bundler = validate_bundler(bundler);
@@ -68,7 +70,7 @@ export async function build({
fs.writeFileSync(`${dest}/template.html`, minify_html(template));
const manifest_data = create_manifest_data(routes);
const manifest_data = create_manifest_data(routes, ext);
// create src/node_modules/@sapper/app.mjs and server.mjs
create_app({
@@ -122,7 +124,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 => `sapper/${chunk.file}`);
.map(chunk => `client/${chunk.file}`);
create_serviceworker_manifest({
manifest_data,

View File

@@ -28,7 +28,8 @@ type Opts = {
hot?: boolean,
'devtools-port'?: number,
bundler?: 'rollup' | 'webpack',
port?: number
port?: number,
ext: string
};
export function dev(opts: Opts) {
@@ -47,7 +48,7 @@ class Watcher extends EventEmitter {
}
port: number;
closed: boolean;
dev_port: number;
live: boolean;
hot: boolean;
@@ -67,6 +68,7 @@ class Watcher extends EventEmitter {
unique_warnings: Set<string>;
unique_errors: Set<string>;
}
ext: string;
constructor({
cwd = '.',
@@ -80,7 +82,8 @@ class Watcher extends EventEmitter {
hot,
'devtools-port': devtools_port,
bundler,
port = +process.env.PORT
port = +process.env.PORT,
ext
}: Opts) {
super();
@@ -95,7 +98,7 @@ class Watcher extends EventEmitter {
output: path.resolve(cwd, output),
static: path.resolve(cwd, static_files)
};
this.ext = ext;
this.port = port;
this.closed = false;
@@ -161,7 +164,7 @@ class Watcher extends EventEmitter {
let manifest_data: ManifestData;
try {
manifest_data = create_manifest_data(routes);
manifest_data = create_manifest_data(routes, this.ext);
create_app({
bundler: this.bundler,
manifest_data,
@@ -189,7 +192,7 @@ class Watcher extends EventEmitter {
},
() => {
try {
const new_manifest_data = create_manifest_data(routes);
const new_manifest_data = create_manifest_data(routes, this.ext);
create_app({
bundler: this.bundler,
manifest_data, // TODO is this right? not new_manifest_data?
@@ -206,15 +209,19 @@ class Watcher extends EventEmitter {
});
}
}
),
fs.watch(`${src}/template.html`, () => {
this.dev_server.send({
action: 'reload'
});
})
)
);
if (this.live) {
this.filewatchers.push(
fs.watch(`${src}/template.html`, () => {
this.dev_server.send({
action: 'reload'
});
})
);
}
let deferred = new Deferred();
// TODO watch the configs themselves?
@@ -252,7 +259,7 @@ class Watcher extends EventEmitter {
this.dev_server.send({
status: 'completed'
});
} else {
} else if (this.live) {
this.dev_server.send({
action: 'reload'
});
@@ -267,48 +274,54 @@ class Watcher extends EventEmitter {
});
};
const start_server = () => {
// we need to give the child process its own DevTools port,
// otherwise Node will try to use the parent's (and fail)
const debugArgRegex = /--inspect(?:-brk|-port)?|--debug-port/;
const execArgv = process.execArgv.slice();
if (execArgv.some((arg: string) => !!arg.match(debugArgRegex))) {
execArgv.push(`--inspect-port=${this.devtools_port}`);
}
this.proc = child_process.fork(`${dest}/server/server.js`, [], {
cwd: process.cwd(),
env: Object.assign({
PORT: this.port
}, process.env),
stdio: ['ipc'],
execArgv
});
this.proc.stdout.on('data', chunk => {
this.emit('stdout', chunk);
});
this.proc.stderr.on('data', chunk => {
this.emit('stderr', chunk);
});
this.proc.on('message', message => {
if (message.__sapper__ && message.event === 'basepath') {
this.emit('basepath', {
basepath: message.basepath
});
}
});
this.proc.on('exit', emitFatal);
};
if (this.proc) {
this.proc.removeListener('exit', emitFatal);
this.proc.kill();
this.proc.on('exit', restart);
this.proc.on('exit', () => {
start_server();
restart();
});
} else {
start_server();
restart();
}
// we need to give the child process its own DevTools port,
// otherwise Node will try to use the parent's (and fail)
const debugArgRegex = /--inspect(?:-brk|-port)?|--debug-port/;
const execArgv = process.execArgv.slice();
if (execArgv.some((arg: string) => !!arg.match(debugArgRegex))) {
execArgv.push(`--inspect-port=${this.devtools_port}`);
}
this.proc = child_process.fork(`${dest}/server/server.js`, [], {
cwd: process.cwd(),
env: Object.assign({
PORT: this.port
}, process.env),
stdio: ['ipc'],
execArgv
});
this.proc.stdout.on('data', chunk => {
this.emit('stdout', chunk);
});
this.proc.stderr.on('data', chunk => {
this.emit('stderr', chunk);
});
this.proc.on('message', message => {
if (message.__sapper__ && message.event === 'basepath') {
this.emit('basepath', {
basepath: message.basepath
});
}
});
this.proc.on('exit', emitFatal);
});
}
});
@@ -333,7 +346,7 @@ class Watcher extends EventEmitter {
JSON.stringify(result.to_json(manifest_data, this.dirs), null, ' ')
);
const client_files = result.chunks.map(chunk => `sapper/${chunk.file}`);
const client_files = result.chunks.map(chunk => `client/${chunk.file}`);
create_serviceworker_manifest({
manifest_data,

View File

@@ -60,8 +60,9 @@ async function _export({
rimraf(export_dir);
copy(static_files, export_dir);
copy(path.join(build_dir, 'client'), export_dir);
copy(path.join(build_dir, 'service-worker'), 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'));
const defaultPort = process.env.PORT ? parseInt(process.env.PORT) : 3000;
const port = await ports.find(defaultPort);
@@ -131,19 +132,23 @@ async function _export({
if (seen.has(pathname)) return;
seen.add(pathname);
const timeout_deferred = new Deferred();
const the_timeout = setTimeout(() => {
timeout_deferred.reject(new Error(`Timed out waiting for ${url.href}`));
}, timeout);
const r = await q.add(async () => {
const timeout_deferred = new Deferred();
const the_timeout = setTimeout(() => {
timeout_deferred.reject(new Error(`Timed out waiting for ${url.href}`));
}, timeout);
const r = await Promise.race([
q.add(() => fetch(url.href, {
redirect: 'manual'
})),
timeout_deferred.promise
]);
const r = await Promise.race([
fetch(url.href, {
redirect: 'manual'
}),
timeout_deferred.promise
]);
clearTimeout(the_timeout); // prevent it hanging at the end
clearTimeout(the_timeout); // prevent it hanging at the end
return r;
}) as Response;
let type = r.headers.get('Content-Type');
@@ -151,6 +156,8 @@ async function _export({
const range = ~~(r.status / 100);
let tasks = [];
if (range === 2) {
if (type === 'text/html') {
// parse link rel=preload headers and embed them in the HTML
@@ -172,8 +179,6 @@ async function _export({
let match;
let pattern = /<a ([\s\S]+?)>/gm;
let promise;
while (match = pattern.exec(cleaned)) {
const attrs = match[1];
const href = get_href(attrs);
@@ -182,12 +187,10 @@ async function _export({
const url = resolve(base.href, href);
if (url.protocol === protocol && url.host === host) {
promise = handle(url);
tasks.push(handle(url));
}
}
}
await promise;
}
}
}
@@ -198,10 +201,12 @@ async function _export({
type = 'text/html';
body = `<script>window.location.href = "${location.replace(origin, '')}"</script>`;
await handle(resolve(root.href, location));
tasks.push(handle(resolve(root.href, location)));
}
save(pathname, r.status, type, body);
await Promise.all(tasks);
}
try {

View File

@@ -1,13 +1,9 @@
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/sapper/shimport@${shimport_version}.js`,
`${dest}/client/shimport@${shimport_version}.js`,
fs.readFileSync(require.resolve('shimport/index.js'))
);
}

View File

@@ -31,6 +31,7 @@ prog.command('dev')
.option('--static', 'Static files directory', 'static')
.option('--output', 'Sapper output directory', 'src/node_modules/@sapper')
.option('--build-dir', 'Development build directory', '__sapper__/dev')
.option('--ext', 'Custom Route Extension', '.svelte .html')
.action(async (opts: {
port: number,
open: boolean,
@@ -43,7 +44,8 @@ prog.command('dev')
routes: string,
static: string,
output: string,
'build-dir': string
'build-dir': string,
ext: string
}) => {
const { dev } = await import('./api/dev');
@@ -59,7 +61,8 @@ prog.command('dev')
'dev-port': opts['dev-port'],
live: opts.live,
hot: opts.hot,
bundler: opts.bundler
bundler: opts.bundler,
ext: opts.ext
});
let first = true;
@@ -151,6 +154,7 @@ prog.command('build [dest]')
.option('--src', 'Source directory', 'src')
.option('--routes', 'Routes directory', 'src/routes')
.option('--output', 'Sapper output directory', 'src/node_modules/@sapper')
.option('--ext', 'Custom Route Extension', '.svelte .html')
.example(`build custom-dir -p 4567`)
.action(async (dest = '__sapper__/build', opts: {
port: string,
@@ -159,12 +163,13 @@ prog.command('build [dest]')
cwd: string,
src: string,
routes: string,
output: string
output: string,
ext: string
}) => {
console.log(`> Building...`);
try {
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, dest);
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, dest, opts.ext);
const launcher = path.resolve(dest, 'index.js');
@@ -199,6 +204,7 @@ prog.command('export [dest]')
.option('--static', 'Static files directory', 'static')
.option('--output', 'Sapper output directory', 'src/node_modules/@sapper')
.option('--build-dir', 'Intermediate build directory', '__sapper__/build')
.option('--ext', 'Custom Route Extension', '.svelte .html')
.action(async (dest = '__sapper__/export', opts: {
build: boolean,
legacy: boolean,
@@ -212,11 +218,12 @@ prog.command('export [dest]')
static: string,
output: string,
'build-dir': string,
ext: string
}) => {
try {
if (opts.build) {
console.log(`> Building...`);
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, opts['build-dir']);
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, opts['build-dir'], opts.ext);
console.error(`\n> Built in ${elapsed(start)}`);
}
@@ -265,7 +272,8 @@ async function _build(
src: string,
routes: string,
output: string,
dest: string
dest: string,
ext: string
) {
const { build } = await import('./api/build');
@@ -276,7 +284,8 @@ async function _build(
src,
routes,
dest,
ext,
output,
oncompile: event => {
let banner = `built ${event.type}`;
let c = (txt: string) => colors.cyan(txt);

View File

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

View File

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

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/sapper`;
let asset_dir = `${dirs.dest}/client`;
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=sapper/${output_file_name}.map */`);
fs.writeFileSync(`${asset_dir}/${output_file_name}`, `${code}\n/* sourceMappingURL=client/${output_file_name}.map */`);
fs.writeFileSync(`${asset_dir}/${output_file_name}.map`, JSON.stringify(map, null, ' '));
result.main = output_file_name;

View File

@@ -4,9 +4,10 @@ import svelte from 'svelte/compiler';
import { Page, PageComponent, ServerRoute, ManifestData } from '../interfaces';
import { posixify, reserved_words } from '../utils';
const component_extensions = ['.svelte', '.html']; // TODO make this configurable (to include e.g. .svelte.md?)
export default function create_manifest_data(cwd: string, extensions: string = '.svelte .html'): ManifestData {
const component_extensions = extensions.split(' ');
export default function create_manifest_data(cwd: string): ManifestData {
// TODO remove in a future version
if (!fs.existsSync(cwd)) {
throw new Error(`As of Sapper 0.21, the routes/ directory should become src/routes/`);
@@ -334,7 +335,7 @@ function get_pattern(segments: Part[][], add_trailing_slash: boolean) {
const path = segments.map(segment => {
return segment.map(part => {
return part.dynamic
? part.qualifier || part.spread ? '(.+)' : '([^\\/]+?)'
? part.qualifier || (part.spread ? '(.+)' : '([^\\/]+?)')
: encodeURI(part.content.normalize())
.replace(/\?/g, '%3F')
.replace(/#/g, '%23')

View File

@@ -1,8 +1,18 @@
<script context="module">
export function preload({ query, params }) {
const { rest } = params;
return { rest };
}
</script>
<script>
import { stores } from '@sapper/app';
const { page } = stores();
export let rest;
</script>
<h1>{$page.params.rest.join(',')}</h1>
<h2>{rest.join(',')}</h2>
<a href="xyz/abc/qwe/deep.json">deep</a>
<a href="xyz/abc">back</a>

View File

@@ -1,8 +1,20 @@
<script context="module">
export function preload({ query, params }) {
const { rest } = params;
return { rest };
}
</script>
<script>
import { stores } from '@sapper/app';
const { page } = stores();
export let rest;
</script>
<h1>{$page.params.rest.join(',')}</h1>
<h2>{rest.join(',')}</h2>
<a href="xyz/abc/deep">deep</a>
<a href="xyz/abc">deep</a>
<a href="xyz/abc/def">deep</a>
<a href="xyz/abc/def/ghi">deep</a>

View File

@@ -0,0 +1,8 @@
<script>
import { stores } from '@sapper/app';
const { page } = stores();
</script>
<h1>Regexp page {$page.params.id}</h1>
<a href="regexp/234">nested regexp route</a>

View File

@@ -3,4 +3,6 @@
const { page } = stores();
</script>
<h1>{$page.params.slug.toUpperCase()}</h1>
<h1>{$page.params.slug}</h1>
<a href="234">regexp route</a>

View File

@@ -0,0 +1,6 @@
<script>
import { stores } from '@sapper/app';
const { page } = stores();
</script>
<h1>Nested regexp page {$page.params.id}</h1>

View File

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

View File

@@ -1,7 +1,7 @@
import * as assert from 'assert';
import * as http from 'http';
import { build } from '../../../api';
import { AppRunner } from '../AppRunner';
import {build} from '../../../api';
import {AppRunner} from '../AppRunner';
declare let deleted: { id: number };
declare let el: any;
@@ -82,7 +82,7 @@ describe('basics', function() {
assert.equal(
await r.text('h1'),
'TEST-SLUG'
'test-slug'
);
});
@@ -140,7 +140,7 @@ describe('basics', function() {
);
// TODO preload more than just the entry point
const regex = /<\/sapper\/client\.\w+\.js>;rel="modulepreload"/;
const regex = /<\/client\/client\.\w+\.js>;rel="modulepreload"/;
const link = <string>headers['link'];
assert.ok(regex.test(link), link);
@@ -267,7 +267,6 @@ describe('basics', function() {
await r.sapper.start();
assert.equal(await r.text('h1'), 'foo');
await r.page.click('[href="dirs/bar"]');
await r.wait();
assert.equal(await r.text('h1'), 'bar');
@@ -278,11 +277,26 @@ describe('basics', function() {
await r.sapper.start();
assert.equal(await r.text('h1'), 'abc,xyz');
await r.page.click('[href="xyz/abc/def/ghi"]');
await r.wait();
assert.equal(await r.text('h1'), 'xyz,abc,def,ghi');
assert.equal(await r.text('h2'), 'xyz,abc,def,ghi');
await r.page.click('[href="xyz/abc/def"]');
await r.wait();
assert.equal(await r.text('h1'), 'xyz,abc,def');
assert.equal(await r.text('h2'), 'xyz,abc,def');
await r.page.click('[href="xyz/abc/def"]');
await r.wait();
assert.equal(await r.text('h1'), 'xyz,abc,def');
assert.equal(await r.text('h2'), 'xyz,abc,def');
await r.page.click('[href="xyz/abc"]');
await r.wait();
assert.equal(await r.text('h1'), 'xyz,abc');
assert.equal(await r.text('h2'), 'xyz,abc');
await r.page.click('[href="xyz/abc/deep"]');
await r.wait();
assert.equal(await r.text('h1'), 'xyz,abc');
assert.equal(await r.text('h2'), 'xyz,abc');
await r.page.click('[href="xyz/abc/qwe/deep.json"]');
await r.wait();
assert.equal(
@@ -302,6 +316,22 @@ describe('basics', function() {
assert.equal(await r.text('h1'), 'B page');
});
it('find regexp routes', async () => {
await r.load('/qwe');
await r.sapper.start();
assert.equal(await r.text('h1'), 'qwe');
await r.page.click('[href="234"]');
await r.wait();
assert.equal(await r.text('h1'), 'Regexp page 234');
await r.page.click('[href="regexp/234"]');
await r.wait();
assert.equal(await r.text('h1'), 'Nested regexp page 234');
});
it('runs server route handlers before page handlers, if they match', async () => {
const json = await get(`${r.base}/middleware`, {
headers: {

View File

@@ -2,12 +2,11 @@ const { NODE_ENV, PORT } = process.env;
export const dev = NODE_ENV === 'development';
export function start(server) {
export function start(app) {
const port = parseInt(PORT) || 0;
server.listen(port, () => {
server = server.server || server;
const address = server.address();
app.listen(port, () => {
const address = app.server.address();
process.env.PORT = address.port;

View File

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

View File

@@ -0,0 +1,60 @@
import resolve from 'rollup-plugin-node-resolve';
import replace from 'rollup-plugin-replace';
import svelte from 'rollup-plugin-svelte';
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const config = require('../../../config/rollup.js');
export default {
client: {
input: config.client.input(),
output: config.client.output(),
plugins: [
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode)
}),
svelte({
extensions: ['.whokilledthemuffinman', '.jesuslivesineveryone', '.mdx', '.svelte', '.html'],
dev,
hydratable: true,
emitCss: true
}),
resolve()
]
},
server: {
input: config.server.input(),
output: config.server.output(),
plugins: [
replace({
'process.browser': false,
'process.env.NODE_ENV': JSON.stringify(mode)
}),
svelte({
extensions: ['.whokilledthemuffinman', '.jesuslivesineveryone', '.mdx', '.svelte', '.html'],
generate: 'ssr',
dev
}),
resolve({
preferBuiltins: true
})
],
external: ['sirv', 'polka']
},
serviceworker: {
input: config.serviceworker.input(),
output: config.serviceworker.output(),
plugins: [
resolve(),
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode)
})
]
}
};

View File

@@ -0,0 +1,9 @@
import * as sapper from '@sapper/app';
window.start = () => sapper.start({
target: document.querySelector('#sapper')
});
window.prefetchRoutes = () => sapper.prefetchRoutes();
window.prefetch = href => sapper.prefetch(href);
window.goto = href => sapper.goto(href);

View File

@@ -0,0 +1,6 @@
<script>
import { stores } from '@sapper/app';
const { page } = stores();
</script>
<h1>{$page.params.slug.toUpperCase()}</h1>

View File

@@ -0,0 +1,3 @@
<h1>hi</h1>
<p>hi</p>

View File

@@ -0,0 +1 @@
<h1>a</h1>

View File

@@ -0,0 +1 @@
<h1>Tremendous!</h1>

View File

@@ -0,0 +1,8 @@
<h1>Great success!</h1>
<a href="a">a</a>
<a href="ambiguous/ok.json">ok</a>
<a href="echo-query?message">ok</a>
<a href="echo-query?p=one&p=two">ok</a>
<div class='hydrate-test'></div>

View File

@@ -0,0 +1 @@
<h1>Bazooom!</h1>

View File

@@ -0,0 +1,9 @@
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const app = polka()
.use(sapper.middleware())
start(app);

View File

@@ -0,0 +1,81 @@
import * as sapper from '@sapper/service-worker';
const ASSETS = `cache${sapper.timestamp}`;
// `app.shell` is an array of all the files generated by webpack,
// `app.files` is an array of everything in the `static` directory
const to_cache = sapper.shell.concat(sapper.files);
const cached = new Set(to_cache);
self.addEventListener('install', event => {
event.waitUntil(
caches
.open(ASSETS)
.then(cache => cache.addAll(to_cache))
.then(() => {
self.skipWaiting();
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(async keys => {
// delete old caches
for (const key of keys) {
if (key !== ASSETS) await caches.delete(key);
}
self.clients.claim();
})
);
});
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
const url = new URL(event.request.url);
// don't try to handle e.g. data: URIs
if (!url.protocol.startsWith('http')) return;
// ignore dev server requests
if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
// always serve assets and webpack-generated files from cache
if (url.host === self.location.host && cached.has(url.pathname)) {
event.respondWith(caches.match(event.request));
return;
}
// for pages, you might want to serve a shell `index.html` file,
// which Sapper has generated for you. It's not right for every
// app, but if it's right for yours then uncomment this section
/*
event.respondWith(caches.match('/index.html'));
return;
}
*/
if (event.request.cache === 'only-if-cached') return;
// for everything else, try the network first, falling back to
// cache if the user is offline. (If the pages never change, you
// might prefer a cache-first approach to a network-first one.)
event.respondWith(
caches
.open(`offline${sapper.timestamp}`)
.then(async cache => {
try {
const response = await fetch(event.request);
cache.put(event.request, response.clone());
return response;
} catch(err) {
const response = await cache.match(event.request);
if (response) return response;
throw err;
}
})
);
});

View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset='utf-8'>
%sapper.base%
%sapper.styles%
%sapper.head%
</head>
<body>
<div id='sapper'>%sapper.html%</div>
%sapper.scripts%
</body>
</html>

View File

@@ -0,0 +1,61 @@
import * as assert from 'assert';
import { build } from '../../../api';
import { AppRunner } from '../AppRunner';
describe('custom extensions', function() {
this.timeout(10000);
let r: AppRunner;
// hooks
before('build app', () => build({ cwd: __dirname , ext: '.jesuslivesineveryone .whokilledthemuffinman .mdx .svelte' }));
before('start runner', async () => {
r = await new AppRunner().start(__dirname);
});
after(() => r && r.end());
it('works with arbitrary extensions', async () => {
await r.load(`/`);
assert.equal(
await r.text('h1'),
'Great success!'
);
});
it('works with other arbitrary extensions', async () => {
await r.load(`/const`);
assert.equal(
await r.text('h1'),
'Tremendous!'
);
await r.load(`/a`);
assert.equal(
await r.text('h1'),
'a'
);
await r.load(`/test-slug`);
assert.equal(
await r.text('h1'),
'TEST-SLUG'
);
await r.load(`/unsafe-replacement`);
assert.equal(
await r.text('h1'),
'Bazooom!'
);
});
});

View File

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

View File

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

View File

@@ -0,0 +1,58 @@
import resolve from 'rollup-plugin-node-resolve';
import replace from 'rollup-plugin-replace';
import svelte from 'rollup-plugin-svelte';
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const config = require('../../../config/rollup.js');
export default {
client: {
input: config.client.input(),
output: config.client.output(),
plugins: [
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode)
}),
svelte({
dev,
hydratable: true,
emitCss: true
}),
resolve()
]
},
server: {
input: config.server.input(),
output: config.server.output(),
plugins: [
replace({
'process.browser': false,
'process.env.NODE_ENV': JSON.stringify(mode)
}),
svelte({
generate: 'ssr',
dev
}),
resolve({
preferBuiltins: true
})
],
external: ['sirv', 'polka']
},
serviceworker: {
input: config.serviceworker.input(),
output: config.serviceworker.output(),
plugins: [
resolve(),
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode)
})
]
}
};

View File

@@ -0,0 +1,9 @@
import * as sapper from '@sapper/app';
window.start = () => sapper.start({
target: document.querySelector('#sapper')
});
window.prefetchRoutes = () => sapper.prefetchRoutes();
window.prefetch = href => sapper.prefetch(href);
window.goto = href => sapper.goto(href);

View File

@@ -0,0 +1,3 @@
<h1>{status}</h1>
<p>{error.message}</p>

View File

@@ -0,0 +1,15 @@
<script context="module">
export function preload({ params }) {
if (params.x === 'a') {
return new Promise(resolve => setTimeout(resolve, 100));
}
return params;
}
</script>
<script>
export let x;
</script>
<a href="b-{x}">b-{x}</a>

View File

@@ -0,0 +1,5 @@
<script>
export let x;
</script>
<p>b-{x}</p>

View File

@@ -0,0 +1,3 @@
<a href="a-a">1</a>
<a href="a-b">2</a>

View File

@@ -0,0 +1,13 @@
import sirv from 'sirv';
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start, dev } from '../../common.js';
const app = polka()
.use(
sirv('static', { dev }),
sapper.middleware()
);
start(app);

View File

@@ -0,0 +1,82 @@
import * as sapper from '@sapper/service-worker';
const ASSETS = `cache${sapper.timestamp}`;
// `shell` is an array of all the files generated by webpack,
// `files` is an array of everything in the `static` directory
const to_cache = sapper.shell.concat(sapper.files);
const cached = new Set(to_cache);
self.addEventListener('install', event => {
event.waitUntil(
caches
.open(ASSETS)
.then(cache => cache.addAll(to_cache))
.then(() => {
self.skipWaiting();
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(async keys => {
// delete old caches
for (const key of keys) {
if (key !== ASSETS) await caches.delete(key);
}
self.clients.claim();
})
);
});
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
const url = new URL(event.request.url);
// don't try to handle e.g. data: URIs
if (!url.protocol.startsWith('http')) return;
// ignore dev server requests
if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
// always serve assets and webpack-generated files from cache
if (url.host === self.location.host && cached.has(url.pathname)) {
event.respondWith(caches.match(event.request));
return;
}
// for pages, you might want to serve a shell `index.html` file,
// which Sapper has generated for you. It's not right for every
// app, but if it's right for yours then uncomment this section
/*
if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
event.respondWith(caches.match('/index.html'));
return;
}
*/
if (event.request.cache === 'only-if-cached') return;
// for everything else, try the network first, falling back to
// cache if the user is offline. (If the pages never change, you
// might prefer a cache-first approach to a network-first one.)
event.respondWith(
caches
.open(`offline${sapper.timestamp}`)
.then(async cache => {
try {
const response = await fetch(event.request);
cache.put(event.request, response.clone());
return response;
} catch(err) {
const response = await cache.match(event.request);
if (response) return response;
throw err;
}
})
);
});

View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset='utf-8'>
%sapper.base%
%sapper.styles%
%sapper.head%
</head>
<body>
<div id='sapper'>%sapper.html%</div>
%sapper.scripts%
</body>
</html>

View File

@@ -0,0 +1,3 @@
body {
font-family: 'Comic Sans MS';
}

View File

@@ -0,0 +1,13 @@
import * as api from '../../../api';
describe('export-queue', function() {
this.timeout(10000);
// hooks
before('build app', () => api.build({ cwd: __dirname }));
// tests
it('exports a site with inconsistent load time', async () => {
await api.export({ cwd: __dirname });
});
});

View File

@@ -1,6 +1,13 @@
import sirv from 'sirv';
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const server = sapper.server();
import { start, dev } from '../../common.js';
start(server);
const app = polka()
.use(
sirv('static', { dev }),
sapper.middleware()
);
start(app);

View File

@@ -1,6 +1,13 @@
import sirv from 'sirv';
import polka from 'polka';
import * as sapper from '@sapper/server';
import { start } from '../../common.js';
const server = sapper.server();
import { start, dev } from '../../common.js';
start(server);
const app = polka()
.use(
sirv('static', { dev }),
sapper.middleware()
);
start(app);

View File

@@ -13,8 +13,8 @@ describe('export', function() {
it('crawls a site', () => {
const files = walk(`${__dirname}/__sapper__/export`);
const client_assets = files.filter(file => file.startsWith('sapper/'));
const non_client_assets = files.filter(file => !file.startsWith('sapper/')).sort();
const client_assets = files.filter(file => file.startsWith('client/'));
const non_client_assets = files.filter(file => !file.startsWith('client/')).sort();
assert.ok(client_assets.length > 0);

View File

@@ -16,4 +16,5 @@
</script>
<span>z: {$page.params.z} {count}</span>
<a href="foo/bar/qux">click me</a>
<a href="foo/bar/qux">goto foo/bar/qux</a>
<a href="foo/abc/def">goto foo/abc/def</a>

View File

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

View File

@@ -23,7 +23,8 @@ describe('layout', function() {
assert.deepEqual(text1.split('\n').map(str => str.trim()).filter(Boolean), [
'y: bar 1',
'z: baz 1',
'click me',
'goto foo/bar/qux',
'goto foo/abc/def',
'child segment: baz'
]);
@@ -32,7 +33,8 @@ describe('layout', function() {
assert.deepEqual(text2.split('\n').map(str => str.trim()).filter(Boolean), [
'y: bar 1',
'z: baz 1',
'click me',
'goto foo/bar/qux',
'goto foo/abc/def',
'child segment: baz'
]);
@@ -43,9 +45,22 @@ describe('layout', function() {
assert.deepEqual(text3.split('\n').map(str => str.trim()).filter(Boolean), [
'y: bar 1',
'z: qux 2',
'click me',
'goto foo/bar/qux',
'goto foo/abc/def',
'child segment: qux'
]);
await r.page.click('[href="foo/abc/def"]');
await r.wait();
const text4 = await r.text('#sapper');
assert.deepEqual(text4.split('\n').map(str => str.trim()).filter(Boolean), [
'y: abc 2',
'z: def 3',
'goto foo/bar/qux',
'goto foo/abc/def',
'child segment: def'
]);
});
it('survives the tests with no server errors', () => {

View File

@@ -0,0 +1,17 @@
<script context="module">
export function preload(page) {
return {
query: page.query
};
}
</script>
<script>
export let query;
</script>
<pre>{JSON.stringify(query)}</pre>
<a href="echo?foo=1">foo=1</a>
<a href="echo?foo=2">foo=2</a>
<a href="echo?foo=3">foo=3</a>

View File

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

View File

@@ -109,4 +109,23 @@ describe('preloading', function() {
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
it('re-runs preload when page.query changes', async () => {
await r.load('/echo?foo=1');
await r.sapper.start();
await r.sapper.prefetchRoutes();
assert.equal(
await r.text('pre'),
`{"foo":"1"}`
);
await r.page.click('a[href="echo?foo=2"]');
await r.wait();
assert.equal(
await r.text('pre'),
`{"foo":"2"}`
);
});
});

View File

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

View File

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

View File

@@ -40,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/sapper/'));
const non_client_assets = files.filter(file => !file.startsWith('custom-basepath/sapper/')).sort();
const client_assets = files.filter(file => file.startsWith('custom-basepath/client/'));
const non_client_assets = files.filter(file => !file.startsWith('custom-basepath/client/')).sort();
assert.ok(client_assets.length > 0);

View File

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

View File

@@ -18,8 +18,8 @@ describe('with-sourcemaps-webpack', function() {
assert.equal(shell.filter(_ => _.endsWith('.map')).length, 0,
'sourcemap files are not cached in SW');
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');
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');
});
});

View File

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

View File

@@ -18,8 +18,8 @@ describe('with-sourcemaps', function() {
assert.equal(shell.filter(_ => _.endsWith('.map')).length, 0,
'sourcemap files are not cached in SW');
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');
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');
});
});

View File

@@ -95,16 +95,15 @@ describe('manifest_data', () => {
]);
});
// this test broken
// it('allows regex qualifiers', () => {
// const { pages } = create_manifest_data(path.join(__dirname, 'samples/qualifiers'));
//
// assert.deepEqual(pages.map(p => p.pattern), [
// /^\/([0-9-a-z]{3,})\/?$/,
// /^\/([a-z]{2})\/?$/,
// /^\/([^\/]+?)\/?$/
// ]);
// });
it('allows regex qualifiers', () => {
const { pages } = create_manifest_data(path.join(__dirname, 'samples/qualifiers'));
assert.deepEqual(pages.map(p => p.pattern), [
/^\/([0-9-a-z]{3,})\/?$/,
/^\/([a-z]{2})\/?$/,
/^\/([^\/]+?)\/?$/
]);
});
it('sorts routes correctly', () => {
const { pages } = create_manifest_data(path.join(__dirname, 'samples/sorting'));
@@ -177,4 +176,66 @@ describe('manifest_data', () => {
pattern: /^\/foo\/?$/
}]);
});
it('works with custom extensions' , () => {
const { components, pages, server_routes } = create_manifest_data(path.join(__dirname, 'samples/custom-extension'), '.jazz .beebop .funk .html');
const index = { name: 'index', file: 'index.funk', has_preload: false };
const about = { name: 'about', file: 'about.jazz', has_preload: false };
const blog = { name: 'blog', file: 'blog/index.html', has_preload: false };
const blog_$slug = { name: 'blog_$slug', file: 'blog/[slug].beebop', has_preload: false };
assert.deepEqual(components, [
index,
about,
blog,
blog_$slug
]);
assert.deepEqual(pages, [
{
pattern: /^\/$/,
parts: [
{ component: index, params: [] }
]
},
{
pattern: /^\/about\/?$/,
parts: [
{ component: about, params: [] }
]
},
{
pattern: /^\/blog\/?$/,
parts: [
{ component: blog, params: [] }
]
},
{
pattern: /^\/blog\/([^\/]+?)\/?$/,
parts: [
null,
{ component: blog_$slug, params: ['slug'] }
]
}
]);
assert.deepEqual(server_routes, [
{
name: 'route_blog_json',
pattern: /^\/blog.json$/,
file: 'blog/index.json.js',
params: []
},
{
name: 'route_blog_$slug_json',
pattern: /^\/blog\/([^\/]+?).json$/,
file: 'blog/[slug].json.js',
params: ['slug']
}
]);
});
});