Compare commits

..

15 Commits

Author SHA1 Message Date
Rich Harris
002718b609 -> v0.18.4 2018-08-30 20:00:58 -04:00
Rich Harris
45d216c64d Merge pull request #396 from sveltejs/gh-381
implement --dev-port flag
2018-08-30 19:59:09 -04:00
Rich Harris
3d69d483d7 merge master -> gh-381 2018-08-30 19:49:28 -04:00
Rich Harris
54da524467 implement --dev-port flag - fixes #381 2018-08-30 19:46:14 -04:00
Rich Harris
ee95240ca6 Merge pull request #394 from sveltejs/gh-390
omit trailing slash from server route matchers
2018-08-30 19:37:23 -04:00
Rich Harris
74d5d1f9c0 Merge pull request #395 from sveltejs/gh-391
Remove webpack annotations when building with Rollup
2018-08-30 19:37:01 -04:00
Rich Harris
8c2688b1be remove url-parse 2018-08-30 19:34:59 -04:00
Rich Harris
e170e4af9b use builtin url module 2018-08-30 19:29:36 -04:00
Rich Harris
bc31c73c33 omit trailing slash from server route matchers - fixes #390 2018-08-30 18:48:10 -04:00
Rich Harris
7798f8f684 minor tidy up 2018-08-30 18:41:37 -04:00
Rich Harris
70fd7038b0 skip webpack annotations when using Rollup 2018-08-30 18:38:46 -04:00
Rich Harris
c6af2ddfa3 Merge pull request #393 from sveltejs/gh-392
handle non-Sapper responses when exporting
2018-08-30 18:29:17 -04:00
Rich Harris
65d0172abe handle non-Sapper responses when exporting - fixes #392 2018-08-30 18:22:40 -04:00
Rich Harris
1e22031765 -> v0.18.3 2018-08-29 22:52:01 -04:00
Rich Harris
46bf8f2b78 -> v0.18.2 2018-08-29 22:43:21 -04:00
17 changed files with 119 additions and 113 deletions

View File

@@ -1,5 +1,18 @@
# sapper changelog
## 0.18.4
* Handle non-Sapper responses when exporting ([#382](https://github.com/sveltejs/sapper/issues/392))
* Add `--dev-port` flag to `sapper dev` ([#381](https://github.com/sveltejs/sapper/issues/381))
## 0.18.3
* Fix service worker Rollup build config
## 0.18.2
* Update `pkg.files`
## 0.18.1
* Add live reloading ([#385](https://github.com/sveltejs/sapper/issues/385))

24
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "sapper",
"version": "0.17.1",
"version": "0.18.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -5574,12 +5574,6 @@
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
"dev": true
},
"querystringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz",
"integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==",
"dev": true
},
"randomatic": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz",
@@ -5872,12 +5866,6 @@
"resolve-from": "^1.0.0"
}
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"resolve": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
@@ -7285,16 +7273,6 @@
}
}
},
"url-parse": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz",
"integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==",
"dev": true,
"requires": {
"querystringify": "^2.0.0",
"requires-port": "^1.0.0"
}
},
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "sapper",
"version": "0.18.1",
"version": "0.18.4",
"description": "Military-grade apps, engineered by Svelte",
"main": "dist/middleware.js",
"bin": {
@@ -10,6 +10,7 @@
"*.js",
"runtime",
"webpack",
"config",
"sapper",
"components",
"dist"
@@ -62,7 +63,6 @@
"tiny-glob": "^0.2.2",
"ts-node": "^7.0.1",
"typescript": "^2.8.3",
"url-parse": "^1.2.0",
"walk-sync": "^0.3.2",
"webpack": "^4.8.3",
"webpack-format-messages": "^2.0.1"

View File

@@ -55,7 +55,7 @@ async function execute(emitter: EventEmitter, {
const route_objects = create_routes();
// create app/manifest/client.js and app/manifest/server.js
create_main_manifests({ routes: route_objects });
create_main_manifests({ bundler, routes: route_objects });
const { client, server, serviceworker } = create_compilers(validate_bundler(bundler), { webpack, rollup });
@@ -69,7 +69,7 @@ async function execute(emitter: EventEmitter, {
fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify({
bundler,
shimport: bundler === 'rollup' && require('shimport/package.json').version,
assets: client_result.assetsByChunkName
assets: client_result.assets
}));
const server_stats = await server.compile();
@@ -84,7 +84,7 @@ async function execute(emitter: EventEmitter, {
if (serviceworker) {
create_serviceworker_manifest({
routes: route_objects,
client_files: client_result.assets.map((file: string) => `client/${file}`)
client_files: client_result.chunks.map((file: string) => `client/${file}`)
});
serviceworker_stats = await serviceworker.compile();

View File

@@ -29,6 +29,8 @@ class Watcher extends EventEmitter {
}
port: number;
closed: boolean;
dev_port: number;
live: boolean;
hot: boolean;
@@ -50,6 +52,7 @@ class Watcher extends EventEmitter {
app = locations.app(),
dest = locations.dest(),
routes = locations.routes(),
'dev-port': dev_port,
live,
hot,
bundler,
@@ -60,6 +63,7 @@ class Watcher extends EventEmitter {
app: string,
dest: string,
routes: string,
'dev-port': number,
live: boolean,
hot: boolean,
bundler?: string,
@@ -74,6 +78,7 @@ class Watcher extends EventEmitter {
this.port = port;
this.closed = false;
this.dev_port = dev_port;
this.live = live;
this.hot = hot;
@@ -120,11 +125,11 @@ class Watcher extends EventEmitter {
mkdirp.sync(`${dest}/client`);
if (this.bundler === 'rollup') copy_shimport(dest);
const dev_port = await ports.find(10000);
if (!this.dev_port) this.dev_port = await ports.find(10000);
try {
const routes = create_routes();
create_main_manifests({ routes, dev_port });
create_main_manifests({ bundler: this.bundler, routes, dev_port: this.dev_port });
} catch (err) {
this.emit('fatal', <events.FatalEvent>{
message: err.message
@@ -132,7 +137,7 @@ class Watcher extends EventEmitter {
return;
}
this.dev_server = new DevServer(dev_port);
this.dev_server = new DevServer(this.dev_port);
this.filewatchers.push(
watch_dir(
@@ -145,11 +150,11 @@ class Watcher extends EventEmitter {
},
() => {
const routes = create_routes();
create_main_manifests({ routes, dev_port });
create_main_manifests({ bundler: this.bundler, routes, dev_port: this.dev_port });
try {
const routes = create_routes();
create_main_manifests({ routes, dev_port });
create_main_manifests({ bundler: this.bundler, routes, dev_port: this.dev_port });
} catch (err) {
this.emit('error', <events.ErrorEvent>{
message: err.message
@@ -280,7 +285,7 @@ class Watcher extends EventEmitter {
fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify({
bundler: this.bundler,
shimport: this.bundler === 'rollup' && require('shimport/package.json').version,
assets: result.assetsByChunkName
assets: result.assets
}, null, ' '));
const client_files = result.assets.map((file: string) => `client/${file}`);

View File

@@ -1,7 +1,7 @@
import * as child_process from 'child_process';
import * as path from 'path';
import * as sander from 'sander';
import URL from 'url-parse';
import * as url from 'url';
import fetch from 'node-fetch';
import * as ports from 'port-authority';
import { EventEmitter } from 'events';
@@ -34,6 +34,12 @@ export function exporter(opts: Opts) {
return emitter;
}
function resolve(from: string, to: string) {
return url.parse(url.resolve(from, to));
}
type URL = url.UrlWithStringQuery;
async function execute(emitter: EventEmitter, opts: Opts) {
const export_dir = path.join(opts.dest, opts.basepath);
@@ -53,8 +59,12 @@ async function execute(emitter: EventEmitter, opts: Opts) {
const port = await ports.find(3000);
const origin = `http://localhost:${port}`;
const root = new URL(opts.basepath || '', origin);
const protocol = 'http:';
const host = `localhost:${port}`;
const origin = `${protocol}//${host}`;
const root = resolve(origin, opts.basepath || '');
if (!root.href.endsWith('/')) root.href += '/';
emitter.emit('info', {
message: `Crawling ${root.href}`
@@ -72,29 +82,15 @@ async function execute(emitter: EventEmitter, opts: Opts) {
const seen = new Set();
const saved = new Set();
const deferreds = new Map();
function get_deferred(pathname: string) {
pathname = pathname.replace(root.pathname, '');
if (!deferreds.has(pathname)) {
deferreds.set(pathname, new Deferred());
}
return deferreds.get(pathname);
}
proc.on('message', message => {
if (!message.__sapper__ || message.event !== 'file') return;
const pathname = new URL(message.url, origin).pathname;
function save(path: string, status: number, type: string, body: string) {
const { pathname } = resolve(origin, path);
let file = pathname.slice(1);
let { body } = message;
if (saved.has(file)) return;
saved.add(file);
const is_html = message.type === 'text/html';
const is_html = type === 'text/html';
if (is_html) {
file = file === '' ? 'index.html' : `${file}/index.html`;
@@ -104,12 +100,15 @@ async function execute(emitter: EventEmitter, opts: Opts) {
emitter.emit('file', <events.FileEvent>{
file,
size: body.length,
status: message.status
status
});
sander.writeFileSync(export_dir, file, body);
}
get_deferred(pathname).fulfil();
proc.on('message', message => {
if (!message.__sapper__ || message.event !== 'file') return;
save(message.url, message.status, message.type, message.body);
});
async function handle(url: URL) {
@@ -118,32 +117,34 @@ async function execute(emitter: EventEmitter, opts: Opts) {
if (seen.has(pathname)) return;
seen.add(pathname);
const deferred = get_deferred(pathname);
const timeout_deferred = new Deferred();
const timeout = setTimeout(() => {
timeout_deferred.reject(new Error(`Timed out waiting for ${url.href}`));
}, opts.timeout);
const r = await Promise.race([
fetch(url.href),
fetch(url.href, {
redirect: 'manual'
}),
timeout_deferred.promise
]);
clearTimeout(timeout); // prevent it hanging at the end
let type = r.headers.get('Content-Type');
let body = await r.text();
const range = ~~(r.status / 100);
if (range === 2) {
if (r.headers.get('Content-Type') === 'text/html') {
const body = await r.text();
if (type === 'text/html') {
const urls: URL[] = [];
const cleaned = clean_html(body);
const base_match = /<base ([\s\S]+?)>/m.exec(cleaned);
const base_href = base_match && get_href(base_match[1]);
const base = new URL(base_href || '/', url.href);
const base = resolve(url.href, base_href);
let match;
let pattern = /<a ([\s\S]+?)>/gm;
@@ -153,8 +154,11 @@ async function execute(emitter: EventEmitter, opts: Opts) {
const href = get_href(attrs);
if (href) {
const url = new URL(href, base.href);
if (url.origin === origin) urls.push(url);
const url = resolve(base.href, href);
if (url.protocol === protocol && url.host === host) {
urls.push(url);
}
}
}
@@ -162,7 +166,16 @@ async function execute(emitter: EventEmitter, opts: Opts) {
}
}
await deferred.promise;
if (range === 3) {
const location = r.headers.get('Location');
type = 'text/html';
body = `<script>window.location.href = "${location}"</script>`;
await handle(resolve(root.href, location));
}
save(pathname, r.status, type, body);
}
return ports.wait(port)

View File

@@ -11,12 +11,14 @@ prog.command('dev')
.describe('Start a development server')
.option('-p, --port', 'Specify a port')
.option('-o, --open', 'Open a browser window')
.option('--dev-port', 'Specify a port for development server')
.option('--hot', 'Use hot module replacement (requires webpack)', true)
.option('-l --live', 'Reload on changes if not using --hot', true)
.option('--live', 'Reload on changes if not using --hot', true)
.option('--bundler', 'Specify a bundler (rollup or webpack)')
.action(async (opts: {
port: number,
open: boolean,
'dev-port': number,
live: boolean,
hot: boolean,
bundler?: string

View File

@@ -17,8 +17,8 @@ export class CompileResult {
duration: number;
errors: CompileError[];
warnings: CompileError[];
assets: string[];
assetsByChunkName: Record<string, string>;
chunks: string[];
assets: Record<string, string>;
}
class RollupResult extends CompileResult {
@@ -32,15 +32,15 @@ class RollupResult extends CompileResult {
this.errors = compiler.errors.map(munge_rollup_warning_or_error);
this.warnings = compiler.warnings.map(munge_rollup_warning_or_error); // TODO emit this as they happen
this.assets = compiler.chunks.map(chunk => chunk.fileName);
this.chunks = compiler.chunks.map(chunk => chunk.fileName);
// TODO populate this properly. We don't have namedcompiler. chunks, as in
// webpack, but we can have a route -> [chunk] map or something
this.assetsByChunkName = {};
this.assets = {};
compiler.chunks.forEach(chunk => {
if (compiler.input in chunk.modules) {
this.assetsByChunkName.main = chunk.fileName;
this.assets.main = chunk.fileName;
}
});
@@ -110,8 +110,8 @@ class WebpackResult extends CompileResult {
this.duration = info.time;
this.assets = info.assets.map((chunk: { name: string }) => chunk.name);
this.assetsByChunkName = info.assetsByChunkName;
this.chunks = info.assets.map((chunk: { name: string }) => chunk.name);
this.assets = info.assetsByChunkName;
}
print() {

View File

@@ -5,7 +5,8 @@ import { posixify, write_if_changed } from './utils';
import { dev, locations } from '../config';
import { Page, PageComponent, ServerRoute } from '../interfaces';
export function create_main_manifests({ routes, dev_port }: {
export function create_main_manifests({ bundler, routes, dev_port }: {
bundler: string,
routes: { components: PageComponent[], pages: Page[], server_routes: ServerRoute[] };
dev_port?: number;
}) {
@@ -14,7 +15,7 @@ export function create_main_manifests({ routes, dev_port }: {
const path_to_routes = path.relative(manifest_dir, locations.routes());
const client_manifest = generate_client(routes, path_to_routes, dev_port);
const client_manifest = generate_client(routes, path_to_routes, bundler, dev_port);
const server_manifest = generate_server(routes, path_to_routes);
write_if_changed(
@@ -48,6 +49,7 @@ export function create_serviceworker_manifest({ routes, client_files }: {
function generate_client(
routes: { root: PageComponent, components: PageComponent[], pages: Page[], server_routes: ServerRoute[] },
path_to_routes: string,
bundler: string,
dev_port?: number
) {
const page_ids = new Set(routes.pages.map(page =>
@@ -61,10 +63,15 @@ function generate_client(
import root from '${get_file(path_to_routes, routes.root)}';
import error from '${posixify(`${path_to_routes}/_error.html`)}';
${routes.components.map(component =>
`const ${component.name} = () =>
import(/* webpackChunkName: "${component.name}" */ '${get_file(path_to_routes, component)}');`)
.join('\n')}
${routes.components.map(component => {
const annotation = bundler === 'webpack'
? `/* webpackChunkName: "${component.name}" */ `
: '';
const source = get_file(path_to_routes, component);
return `const ${component.name} = () => import(${annotation}'${source}');`;
}).join('\n')}
export const manifest = {
ignore: [${server_routes_to_ignore.map(route => route.pattern).join(', ')}],

View File

@@ -128,12 +128,12 @@ export default function create_routes(cwd = locations.routes()) {
components.push(component);
if (item.basename === 'index.html') {
pages.push({
pattern: get_pattern(parent_segments),
pattern: get_pattern(parent_segments, true),
parts
});
} else {
pages.push({
pattern: get_pattern(segments),
pattern: get_pattern(segments, true),
parts
});
}
@@ -142,7 +142,7 @@ export default function create_routes(cwd = locations.routes()) {
else {
server_routes.push({
name: `route_${get_slug(item.file)}`,
pattern: get_pattern(segments),
pattern: get_pattern(segments, false),
file: item.file,
params: params
});
@@ -276,7 +276,7 @@ function get_slug(file: string) {
});
}
function get_pattern(segments: Part[][]) {
function get_pattern(segments: Part[][], add_trailing_slash: boolean) {
return new RegExp(
`^` +
segments.map(segment => {
@@ -290,6 +290,6 @@ function get_pattern(segments: Part[][]) {
.replace(/%5D/g, ']');
}).join('');
}).join('') +
'\\\/?$'
(add_trailing_slash ? '\\\/?$' : '$')
);
}

View File

@@ -414,18 +414,6 @@ function get_page_handler(
res.setHeader('Location', location);
res.end();
if (process.send) {
process.send({
__sapper__: true,
event: 'file',
url: req.url,
method: req.method,
status: redirect.statusCode,
type: 'text/html',
body: `<script>window.location.href = "${location}"</script>`
});
}
return;
}
@@ -512,18 +500,6 @@ function get_page_handler(
res.statusCode = status;
res.end(body);
if (process.send) {
process.send({
__sapper__: true,
event: 'file',
url: req.url,
method: req.method,
status,
type: 'text/html',
body
});
}
}).catch(err => {
if (error) {
// we encountered an error while rendering the error page — oops

View File

@@ -38,7 +38,7 @@ export default {
output: () => {
return {
dir: locations.dest(),
file: `${locations.dest()}/service-worker.js`,
format: 'iife'
}
}

View File

@@ -108,6 +108,13 @@ const middlewares = [
}),
];
app.get(`${BASEPATH}/non-sapper-redirect-from`, (req, res) => {
res.writeHead(301, {
Location: `${BASEPATH}/non-sapper-redirect-to`
});
res.end();
});
if (BASEPATH) {
app.use(BASEPATH, ...middlewares);
} else {

View File

@@ -7,6 +7,7 @@
<a href='.'>home</a>
<a href='about'>about</a>
<a href='slow-preload'>slow preload</a>
<a href='non-sapper-redirect-from'>redirect</a>
<a href='redirect-from'>redirect</a>
<a href='redirect-root'>redirect (root)</a>
<a href='blog/nope'>broken link</a>

View File

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

View File

@@ -2,9 +2,7 @@ const fs = require('fs');
const path = require('path');
const assert = require('assert');
const Nightmare = require('nightmare');
const serve = require('serve-static');
const walkSync = require('walk-sync');
const fetch = require('node-fetch');
const rimraf = require('rimraf');
const ports = require('port-authority');
@@ -83,6 +81,11 @@ function testExport({ basepath = '' }) {
'about/index.html',
'slow-preload/index.html',
'redirect-from/index.html',
'redirect-to/index.html',
'non-sapper-redirect-from/index.html',
'non-sapper-redirect-to/index.html',
'blog/index.html',
'blog/a-very-long-post/index.html',
'blog/how-can-i-get-involved/index.html',

View File

@@ -53,14 +53,14 @@ describe('create_routes', () => {
assert.deepEqual(server_routes, [
{
name: 'route_blog_json',
pattern: /^\/blog.json\/?$/,
pattern: /^\/blog.json$/,
file: 'blog/index.json.js',
params: []
},
{
name: 'route_blog_$slug_json',
pattern: /^\/blog\/([^\/]+?).json\/?$/,
pattern: /^\/blog\/([^\/]+?).json$/,
file: 'blog/[slug].json.js',
params: ['slug']
}