Compare commits

...

32 Commits

Author SHA1 Message Date
Rich Harris
cbbf4a95db -> v0.15.8 2018-08-16 12:46:13 -04:00
Rich Harris
55b7ffd2ed Merge pull request #361 from sveltejs/handle-unknown-preload-errors
handle unknown preload errors
2018-08-16 12:44:27 -04:00
Rich Harris
9f4d4e70de can remove this, preloading is set false on render 2018-08-16 12:39:54 -04:00
Rich Harris
deef1bbfcf handle unknown preload errors 2018-08-16 12:25:23 -04:00
Seth Thompson
17b0fc0d0c nit 2018-08-11 17:52:53 -04:00
Seth Thompson
3c44c511e4 make sure page has expected preloading value 2018-08-11 17:51:44 -04:00
Seth Thompson
7cf1b9613a prefetching should not set root preloading value, closes #352 2018-08-11 12:26:27 -04:00
Rich Harris
99e5a9601c -> v0.15.7 2018-08-09 20:14:37 -04:00
Rich Harris
4c9c1dccf5 Merge pull request #350 from sveltejs/gh-344
pass response object to store getter
2018-08-09 20:13:17 -04:00
Rich Harris
2cddd5afa0 Merge pull request #345 from sveltejs/fix/redirect
Fix Preload's Redirect
2018-08-09 20:08:51 -04:00
Rich Harris
8c6a0c4773 Merge branch 'master' into gh-344 2018-08-09 20:03:37 -04:00
Rich Harris
af5063552d Merge pull request #351 from sveltejs/argh-windows
doh
2018-08-09 20:02:51 -04:00
Rich Harris
419d154794 fffffuuuuu 2018-08-09 19:53:26 -04:00
Rich Harris
abda059be5 doh 2018-08-09 19:46:09 -04:00
Rich Harris
444908cac5 pass response object to store getter - fixes #344 2018-08-08 10:57:10 -04:00
Luke Edwards
c6da26e1a0 add redirect test to root (“/“) 2018-08-06 20:29:28 -07:00
Luke Edwards
aad87857ce fix: replace leading slash in preload’s redirect 2018-08-06 20:28:28 -07:00
Rich Harris
666c113297 -> v0.15.6 2018-08-06 22:36:17 -04:00
Rich Harris
84a58f34a0 add test for exporting with custom basepath 2018-08-06 22:35:02 -04:00
Rich Harris
75f5b5c721 Merge pull request #342 from aubergene/gh-338
Remove basepath from deferred urls and add trailing slash to root
2018-08-06 22:06:09 -04:00
Julian Burgess
a176a3b79b Remove basepath from deferred urls and add trailing slash to root request 2018-08-06 16:43:02 +01:00
Rich Harris
1627a5767a -> v0.15.5 2018-08-03 01:18:12 -04:00
Rich Harris
6ff3a9e9ab Merge branch 'master' of github.com:sveltejs/sapper 2018-08-03 01:16:43 -04:00
Rich Harris
3ce2bd30f9 Use npm ci instead of npm install (#336)
* dont write server_info.json either - second half of #318

* use npm ci

* update lockfile

* try this
2018-08-03 01:16:26 -04:00
Rich Harris
de4f99807f Merge branch 'master' of github.com:sveltejs/sapper 2018-08-03 00:11:09 -04:00
Rich Harris
eae8351f77 Better/faster exporting
* add --build and --build-dir options to sapper export (#325)

* tweak export logging, update port-authority to prevent timeout bug

* better logging of export progress

* handle case where linked resource is already fetched

* default to .sapper/dev instead of .sapper

* handle query params and redirects

* dont write server_info.json either - second half of #318

* update changelog

* update lockfile

* try to track down ci test failures

* err wut

* curiouser and curiouser

* ok, seems to work now
2018-08-03 00:10:58 -04:00
Rich Harris
d386308301 update changelog 2018-08-02 13:23:12 -04:00
Rich Harris
13afbc84d7 dont write server_info.json either - second half of #318 2018-08-02 10:59:23 -04:00
Rich Harris
31327b3780 Merge pull request #333 from sveltejs/gh-332
only blur activeElement if there is one
2018-08-02 10:43:27 -04:00
Rich Harris
81f483d7b8 Merge pull request #334 from sveltejs/gh-318
dont emit client_info.json
2018-08-02 08:40:30 -04:00
Rich Harris
1bcf20511b dont emit client_info.json - fixes #318 2018-08-01 21:46:26 -04:00
Rich Harris
003fa8ab2c only blur activeElement if there is one - fixes #332 2018-08-01 21:34:30 -04:00
18 changed files with 2836 additions and 499 deletions

View File

@@ -18,4 +18,4 @@ addons:
install: install:
- export DISPLAY=':99.0' - export DISPLAY=':99.0'
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
- npm install - npm ci || npm i

View File

@@ -1,5 +1,25 @@
# sapper changelog # sapper changelog
## 0.15.8
* Only set `preloading: true` on navigation, not prefetch ([#352](https://github.com/sveltejs/sapper/issues/352))
* Provide fallback for missing preload errors ([#361](https://github.com/sveltejs/sapper/pull/361))
## 0.15.7
* Strip leading slash from redirects ([#291](https://github.com/sveltejs/sapper/issues/291))
* Pass `(req, res)` to store getter ([#344](https://github.com/sveltejs/sapper/issues/344))
## 0.15.6
* Fix exporting with custom basepath ([#342](https://github.com/sveltejs/sapper/pull/342))
## 0.15.5
* Faster `export` with more explanatory output ([#335](https://github.com/sveltejs/sapper/pull/335))
* Only blur `activeElement` if it exists ([#332](https://github.com/sveltejs/sapper/issues/332))
* Don't emit `client_info.json` or `server_info.json` ([#318](https://github.com/sveltejs/sapper/issues/318))
## 0.15.4 ## 0.15.4
* Add `ignore` option ([#326](https://github.com/sveltejs/sapper/pull/326)) * Add `ignore` option ([#326](https://github.com/sveltejs/sapper/pull/326))

View File

@@ -14,7 +14,7 @@ environment:
install: install:
- ps: Install-Product node $env:nodejs_version - ps: Install-Product node $env:nodejs_version
- npm install - npm ci
test_script: test_script:
- node --version && npm --version - node --version && npm --version

3062
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "sapper", "name": "sapper",
"version": "0.15.4", "version": "0.15.8",
"description": "Military-grade apps, engineered by Svelte", "description": "Military-grade apps, engineered by Svelte",
"main": "dist/middleware.ts.js", "main": "dist/middleware.ts.js",
"bin": { "bin": {
@@ -28,7 +28,7 @@
"html-minifier": "^3.5.16", "html-minifier": "^3.5.16",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"node-fetch": "^2.1.1", "node-fetch": "^2.1.1",
"port-authority": "^1.0.2", "port-authority": "^1.0.5",
"pretty-bytes": "^5.0.0", "pretty-bytes": "^5.0.0",
"pretty-ms": "^3.1.0", "pretty-ms": "^3.1.0",
"require-relative": "^0.8.7", "require-relative": "^0.8.7",

View File

@@ -4,8 +4,7 @@ import mkdirp from 'mkdirp';
import rimraf from 'rimraf'; import rimraf from 'rimraf';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { minify_html } from './utils/minify_html'; import { minify_html } from './utils/minify_html';
import { create_compilers, create_main_manifests, create_routes, create_serviceworker_manifest } from '../core' import { create_compilers, create_main_manifests, create_routes, create_serviceworker_manifest } from '../core';
import { locations } from '../config';
import * as events from './interfaces'; import * as events from './interfaces';
export function build(opts: {}) { export function build(opts: {}) {
@@ -62,7 +61,6 @@ async function execute(emitter: EventEmitter, {
}); });
const client_info = client_stats.toJson(); const client_info = client_stats.toJson();
fs.writeFileSync(path.join(dest, 'client_info.json'), JSON.stringify(client_info));
fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(client_info.assetsByChunkName)); fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(client_info.assetsByChunkName));
const server_stats = await compile(server); const server_stats = await compile(server);

View File

@@ -9,6 +9,7 @@ import format_messages from 'webpack-format-messages';
import { locations } from '../config'; import { locations } from '../config';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { create_routes, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core'; import { create_routes, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core';
import Deferred from './utils/Deferred';
import * as events from './interfaces'; import * as events from './interfaces';
export function dev(opts) { export function dev(opts) {
@@ -168,8 +169,6 @@ class Watcher extends EventEmitter {
}, },
result: info => { result: info => {
fs.writeFileSync(path.join(dest, 'server_info.json'), JSON.stringify(info, null, ' '));
this.deferreds.client.promise.then(() => { this.deferreds.client.promise.then(() => {
const restart = () => { const restart = () => {
log = ''; log = '';
@@ -252,7 +251,6 @@ class Watcher extends EventEmitter {
}, },
result: info => { result: info => {
fs.writeFileSync(path.join(dest, 'client_info.json'), JSON.stringify(info));
fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(info.assetsByChunkName, null, ' ')); fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(info.assetsByChunkName, null, ' '));
this.deferreds.client.fulfil(); this.deferreds.client.fulfil();
@@ -401,19 +399,6 @@ function mungeWebpackError(message: string, duplicate: boolean) {
}; };
} }
class Deferred {
promise: Promise<any>;
fulfil: (value?: any) => void;
reject: (error: Error) => void;
constructor() {
this.promise = new Promise((fulfil, reject) => {
this.fulfil = fulfil;
this.reject = reject;
});
}
}
const INTERVAL = 10000; const INTERVAL = 10000;
class DevServer { class DevServer {

View File

@@ -7,7 +7,7 @@ import fetch from 'node-fetch';
import * as ports from 'port-authority'; import * as ports from 'port-authority';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { minify_html } from './utils/minify_html'; import { minify_html } from './utils/minify_html';
import { locations } from '../config'; import Deferred from './utils/Deferred';
import * as events from './interfaces'; import * as events from './interfaces';
export function exporter(opts: {}) { export function exporter(opts: {}) {
@@ -51,6 +51,11 @@ async function execute(emitter: EventEmitter, {
const port = await ports.find(3000); const port = await ports.find(3000);
const origin = `http://localhost:${port}`; const origin = `http://localhost:${port}`;
const root = new URL(basepath || '', origin);
emitter.emit('info', {
message: `Crawling ${root.href}`
});
const proc = child_process.fork(path.resolve(`${build}/server.js`), [], { const proc = child_process.fork(path.resolve(`${build}/server.js`), [], {
cwd: process.cwd(), cwd: process.cwd(),
@@ -64,11 +69,23 @@ async function execute(emitter: EventEmitter, {
const seen = new Set(); const seen = new Set();
const saved = new Set(); const saved = new Set();
const deferreds = new Map();
function get_deferred(pathname: string) {
pathname = pathname.replace(root.pathname, '');
if (!deferreds.has(pathname)) {
deferreds.set(pathname, new Deferred());
}
return deferreds.get(pathname);
}
proc.on('message', message => { proc.on('message', message => {
if (!message.__sapper__ || message.event !== 'file') return; if (!message.__sapper__ || message.event !== 'file') return;
let file = new URL(message.url, origin).pathname.slice(1); const pathname = new URL(message.url, origin).pathname;
let file = pathname.slice(1);
let { body } = message; let { body } = message;
if (saved.has(file)) return; if (saved.has(file)) return;
@@ -83,24 +100,26 @@ async function execute(emitter: EventEmitter, {
emitter.emit('file', <events.FileEvent>{ emitter.emit('file', <events.FileEvent>{
file, file,
size: body.length size: body.length,
status: message.status
}); });
sander.writeFileSync(export_dir, file, body); sander.writeFileSync(export_dir, file, body);
get_deferred(pathname).fulfil();
}); });
async function handle(url: URL) { async function handle(url: URL) {
const pathname = (url.pathname.replace(root.pathname, '') || '/');
if (seen.has(pathname)) return;
seen.add(pathname);
const deferred = get_deferred(pathname);
const r = await fetch(url.href); const r = await fetch(url.href);
const range = ~~(r.status / 100); const range = ~~(r.status / 100);
if (range >= 4) {
emitter.emit('failure', <events.FailureEvent>{
status: r.status,
pathname: url.pathname
});
return;
}
if (range === 2) { if (range === 2) {
if (r.headers.get('Content-Type') === 'text/html') { if (r.headers.get('Content-Type') === 'text/html') {
const body = await r.text(); const body = await r.text();
@@ -111,19 +130,20 @@ async function execute(emitter: EventEmitter, {
$('a[href]').each((i: number, $a) => { $('a[href]').each((i: number, $a) => {
const url = new URL($a.attribs.href, base.href); const url = new URL($a.attribs.href, base.href);
if (url.origin === origin) urls.push(url);
if (url.origin === origin && !seen.has(url.pathname)) {
seen.add(url.pathname);
urls.push(url);
}
}); });
await Promise.all(urls.map(handle)); await Promise.all(urls.map(handle));
} }
} }
await deferred.promise;
} }
return ports.wait(port) return ports.wait(port)
.then(() => handle(new URL(`/${basepath}`, origin))) // TODO all static routes .then(() => {
// TODO all static routes
return handle(root);
})
.then(() => proc.kill()); .then(() => proc.kill());
} }

12
src/api/utils/Deferred.ts Normal file
View File

@@ -0,0 +1,12 @@
export default class Deferred {
promise: Promise<any>;
fulfil: (value?: any) => void;
reject: (error: Error) => void;
constructor() {
this.promise = new Promise((fulfil, reject) => {
this.fulfil = fulfil;
this.reject = reject;
});
}
}

View File

@@ -1,11 +1,8 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as child_process from 'child_process';
import sade from 'sade'; import sade from 'sade';
import * as colors from 'ansi-colors'; import * as colors from 'ansi-colors';
import prettyMs from 'pretty-ms'; import prettyMs from 'pretty-ms';
// import upgrade from './cli/upgrade';
import * as ports from 'port-authority';
import * as pkg from '../package.json'; import * as pkg from '../package.json';
const prog = sade('sapper').version(pkg.version); const prog = sade('sapper').version(pkg.version);
@@ -65,19 +62,22 @@ prog.command('start [dir]')
prog.command('export [dest]') prog.command('export [dest]')
.describe('Export your app as static files (if possible)') .describe('Export your app as static files (if possible)')
.option('--build', '(Re)build app before exporting', true)
.option('--build-dir', 'Specify a custom temporary build directory', '.sapper/prod')
.option('--basepath', 'Specify a base path') .option('--basepath', 'Specify a base path')
.action(async (dest = 'export', opts: { basepath?: string }) => { .action(async (dest = 'export', opts: { build: boolean, 'build-dir': string, basepath?: string }) => {
console.log(`> Building...`);
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = 'production';
process.env.SAPPER_DEST = '.sapper/.export'; process.env.SAPPER_DEST = opts['build-dir'];
const start = Date.now(); const start = Date.now();
try { try {
const { build } = await import('./cli/build'); if (opts.build) {
await build(); console.log(`> Building...`);
console.error(`\n> Built in ${elapsed(start)}. Crawling site...`); const { build } = await import('./cli/build');
await build();
console.error(`\n> Built in ${elapsed(start)}`);
}
const { exporter } = await import('./cli/export'); const { exporter } = await import('./cli/export');
await exporter(dest, opts); await exporter(dest, opts);

View File

@@ -3,6 +3,11 @@ import * as colors from 'ansi-colors';
import prettyBytes from 'pretty-bytes'; import prettyBytes from 'pretty-bytes';
import { locations } from '../config'; import { locations } from '../config';
function left_pad(str: string, len: number) {
while (str.length < len) str = ` ${str}`;
return str;
}
export function exporter(export_dir: string, { basepath = '' }) { export function exporter(export_dir: string, { basepath = '' }) {
return new Promise((fulfil, reject) => { return new Promise((fulfil, reject) => {
try { try {
@@ -13,11 +18,19 @@ export function exporter(export_dir: string, { basepath = '' }) {
}); });
emitter.on('file', event => { emitter.on('file', event => {
console.log(`${colors.bold.cyan(event.file)} ${colors.gray(`(${prettyBytes(event.size)})`)}`); const pb = prettyBytes(event.size);
const size_color = event.size > 150000 ? colors.bold.red : event.size > 50000 ? colors.bold.yellow : colors.bold.gray;
const size_label = size_color(left_pad(prettyBytes(event.size), 10));
const file_label = event.status === 200
? event.file
: colors.bold[event.status >= 400 ? 'red' : 'yellow'](`(${event.status}) ${event.file}`);
console.log(`${size_label} ${file_label}`);
}); });
emitter.on('failure', event => { emitter.on('info', event => {
console.log(`${colors.red(`> Received ${event.status} response when fetching ${event.pathname}`)}`); console.log(colors.bold.cyan(`> ${event.message}`));
}); });
emitter.on('error', event => { emitter.on('error', event => {

View File

@@ -6,5 +6,5 @@ export const locations = {
base: () => path.resolve(process.env.SAPPER_BASE || ''), base: () => path.resolve(process.env.SAPPER_BASE || ''),
app: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_APP || 'app'), app: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_APP || 'app'),
routes: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_ROUTES || 'routes'), routes: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_ROUTES || 'routes'),
dest: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_DEST || '.sapper') dest: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_DEST || `.sapper/${dev() ? 'dev' : 'prod'}`)
}; };

View File

@@ -83,7 +83,7 @@ function toIgnore(uri: string, val: any) {
export default function middleware(opts: { export default function middleware(opts: {
manifest: Manifest, manifest: Manifest,
store: (req: Req) => Store, store: (req: Req, res: ServerResponse) => Store,
ignore?: any, ignore?: any,
routes?: any // legacy routes?: any // legacy
}) { }) {
@@ -276,7 +276,10 @@ function get_server_route_handler(routes: ServerRoute[]) {
}; };
} }
function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store) { function get_page_handler(
manifest: Manifest,
store_getter: (req: Req, res: ServerResponse) => Store
) {
const output = locations.dest(); const output = locations.dest();
const get_chunks = dev() const get_chunks = dev()
@@ -296,13 +299,10 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
parts: [ parts: [
{ name: null, component: error_route } { name: null, component: error_route }
] ]
}, req, res, statusCode, error); }, req, res, statusCode, error || new Error('Unknown error in preload function'));
} }
function handle_page(page: Page, req: Req, res: ServerResponse, status = 200, error: Error | string = null) { function handle_page(page: Page, req: Req, res: ServerResponse, status = 200, error: Error | string = null) {
const get_params = page.parts[page.parts.length - 1].params || (() => ({}));
const match = error ? null : page.pattern.exec(req.path);
const chunks: Record<string, string | string[]> = get_chunks(); const chunks: Record<string, string | string[]> = get_chunks();
res.setHeader('Content-Type', 'text/html'); res.setHeader('Content-Type', 'text/html');
@@ -326,7 +326,7 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
res.setHeader('Link', link); res.setHeader('Link', link);
const store = store_getter ? store_getter(req) : null; const store = store_getter ? store_getter(req, res) : null;
let redirect: { statusCode: number, location: string }; let redirect: { statusCode: number, location: string };
let preload_error: { statusCode: number, message: Error | string }; let preload_error: { statusCode: number, message: Error | string };
@@ -336,6 +336,7 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
if (redirect && (redirect.statusCode !== statusCode || redirect.location !== location)) { if (redirect && (redirect.statusCode !== statusCode || redirect.location !== location)) {
throw new Error(`Conflicting redirects`); throw new Error(`Conflicting redirects`);
} }
location = location.replace(/^\//g, ''); // leading slash (only)
redirect = { statusCode, location }; redirect = { statusCode, location };
}, },
error: (statusCode: number, message: Error | string) => { error: (statusCode: number, message: Error | string) => {
@@ -387,6 +388,8 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
}) })
: {}; : {};
const match = error ? null : page.pattern.exec(req.path);
Promise.all([root_preloaded].concat(page.parts.map(part => { Promise.all([root_preloaded].concat(page.parts.map(part => {
if (!part) return null; if (!part) return null;
@@ -402,10 +405,24 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
return []; // appease TypeScript return []; // appease TypeScript
}).then(preloaded => { }).then(preloaded => {
if (redirect) { if (redirect) {
const location = `${req.baseUrl}/${redirect.location}`;
res.statusCode = redirect.statusCode; res.statusCode = redirect.statusCode;
res.setHeader('Location', `${req.baseUrl}/${redirect.location}`); res.setHeader('Location', location);
res.end(); res.end();
if (process.send) {
process.send({
__sapper__: true,
event: 'file',
url: req.url,
method: req.method,
status: redirect.statusCode,
type: 'text/html',
body: `<script>window.location.href = "${location}"</script>`
});
}
return; return;
} }
@@ -498,7 +515,7 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store)
event: 'file', event: 'file',
url: req.url, url: req.url,
method: req.method, method: req.method,
status: 200, status,
type: 'text/html', type: 'text/html',
body body
}); });
@@ -568,4 +585,4 @@ function escape_html(html: string) {
}; };
return html.replace(/["'&<>]/g, c => `&${chars[c]};`); return html.replace(/["'&<>]/g, c => `&${chars[c]};`);
} }

View File

@@ -136,10 +136,6 @@ function prepare_page(target: Target): Promise<{
data?: any; data?: any;
nullable_depth?: number; nullable_depth?: number;
}> { }> {
if (root) {
root.set({ preloading: true });
}
const { page, path, query } = target; const { page, path, query } = target;
const new_segments = path.split('/').filter(Boolean); const new_segments = path.split('/').filter(Boolean);
let changed_from = 0; let changed_from = 0;
@@ -285,6 +281,9 @@ async function navigate(target: Target, id: number): Promise<any> {
cid = id; cid = id;
if (root) {
root.set({ preloading: true });
}
const loaded = prefetching && prefetching.href === target.url.href ? const loaded = prefetching && prefetching.href === target.url.href ?
prefetching.promise : prefetching.promise :
prepare_page(target); prepare_page(target);
@@ -298,7 +297,7 @@ async function navigate(target: Target, id: number): Promise<any> {
await goto(redirect.location, { replaceState: true }); await goto(redirect.location, { replaceState: true });
} else { } else {
render(data, nullable_depth, scroll_history[id], token); render(data, nullable_depth, scroll_history[id], token);
document.activeElement.blur(); if (document.activeElement) document.activeElement.blur();
} }
} }

View File

@@ -85,11 +85,18 @@ const middlewares = [
next(); next();
}, },
// set up some values for the store
(req, res, next) => {
req.hello = 'hello';
res.locals = { name: 'world' };
next();
},
sapper({ sapper({
manifest, manifest,
store: () => { store: (req, res) => {
return new Store({ return new Store({
title: 'Stored title' title: `${req.hello} ${res.locals.name}`
}); });
}, },
ignore: [ ignore: [

View File

@@ -8,6 +8,7 @@
<a href='about'>about</a> <a href='about'>about</a>
<a href='slow-preload'>slow preload</a> <a href='slow-preload'>slow preload</a>
<a href='redirect-from'>redirect</a> <a href='redirect-from'>redirect</a>
<a href='redirect-root'>redirect (root)</a>
<a href='blog/nope'>broken link</a> <a href='blog/nope'>broken link</a>
<a href='blog/throw-an-error'>error link</a> <a href='blog/throw-an-error'>error link</a>
<a href='credentials?creds=include'>credentials</a> <a href='credentials?creds=include'>credentials</a>

View File

@@ -0,0 +1,7 @@
<script>
export default {
preload() {
this.redirect(301, '/');
}
};
</script>

View File

@@ -59,9 +59,19 @@ describe('sapper', function() {
basepath: '/custom-basepath' basepath: '/custom-basepath'
}); });
describe('export', () => { testExport({});
testExport({ basepath: '/custom-basepath' });
});
function testExport({ basepath = '' }) {
describe(basepath ? `export --basepath ${basepath}` : 'export', () => {
before(() => { before(() => {
return exec(`node ${cli} export`); if (basepath) {
process.env.BASEPATH = basepath;
}
return exec(`node ${cli} export ${basepath ? `--basepath ${basepath}` : ''}`);
}); });
it('export all pages', () => { it('export all pages', () => {
@@ -96,7 +106,10 @@ describe('sapper', function() {
'service-worker.js', 'service-worker.js',
'svelte-logo-192.png', 'svelte-logo-192.png',
'svelte-logo-512.png', 'svelte-logo-512.png',
]; ].map(file => {
return basepath ? `${basepath.replace(/^[\/\\]/, '')}/${file}` : file;
});
// Client scripts that should show up in the extraction directory. // Client scripts that should show up in the extraction directory.
const expectedClientRegexes = [ const expectedClientRegexes = [
/client\/[^/]+\/main(\.\d+)?\.js/, /client\/[^/]+\/main(\.\d+)?\.js/,
@@ -126,7 +139,7 @@ describe('sapper', function() {
}); });
}); });
}); });
}); }
function run({ mode, basepath = '' }) { function run({ mode, basepath = '' }) {
describe(`mode=${mode}`, function () { describe(`mode=${mode}`, function () {
@@ -423,6 +436,33 @@ function run({ mode, basepath = '' }) {
}); });
}); });
it('redirects on server (root)', () => {
return nightmare.goto(`${base}/redirect-root`)
.path()
.then(path => {
assert.equal(path, `${basepath}/`);
})
.then(() => nightmare.page.title())
.then(title => {
assert.equal(title, 'Great success!');
});
});
it('redirects in client (root)', () => {
return nightmare.goto(base)
.wait('[href="redirect-root"]')
.click('[href="redirect-root"]')
.wait(200)
.path()
.then(path => {
assert.equal(path, `${basepath}/`);
})
.then(() => nightmare.page.title())
.then(title => {
assert.equal(title, 'Great success!');
});
});
it('handles 4xx error on server', () => { it('handles 4xx error on server', () => {
return nightmare.goto(`${base}/blog/nope`) return nightmare.goto(`${base}/blog/nope`)
.path() .path()
@@ -568,11 +608,11 @@ function run({ mode, basepath = '' }) {
return nightmare.goto(`${base}/store`) return nightmare.goto(`${base}/store`)
.page.title() .page.title()
.then(title => { .then(title => {
assert.equal(title, 'Stored title'); assert.equal(title, 'hello world');
return nightmare.init().page.title(); return nightmare.init().page.title();
}) })
.then(title => { .then(title => {
assert.equal(title, 'Stored title'); assert.equal(title, 'hello world');
}); });
}); });