Merge branch 'master' into collision

This commit is contained in:
Rich Harris
2018-05-04 17:05:18 -04:00
committed by GitHub
21 changed files with 105 additions and 49 deletions

View File

@@ -1,5 +1,11 @@
# sapper changelog # sapper changelog
## 0.11.0
* Create launcher file ([#240](https://github.com/sveltejs/sapper/issues/240))
* Only keep necessary parts of webpack stats ([#251](https://github.com/sveltejs/sapper/pull/251))
* Allow `NODE_ENV` to be overridden when building ([#241](https://github.com/sveltejs/sapper/issues/241))
## 0.10.7 ## 0.10.7
* Allow routes to have a leading `.` ([#243](https://github.com/sveltejs/sapper/pull/243)) * Allow routes to have a leading `.` ([#243](https://github.com/sveltejs/sapper/pull/243))

View File

@@ -1,6 +1,6 @@
{ {
"name": "sapper", "name": "sapper",
"version": "0.10.7", "version": "0.11.0",
"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": {
@@ -34,35 +34,35 @@
"rimraf": "^2.6.2", "rimraf": "^2.6.2",
"sade": "^1.4.0", "sade": "^1.4.0",
"sander": "^0.6.0", "sander": "^0.6.0",
"source-map-support": "^0.5.4", "source-map-support": "^0.5.5",
"tslib": "^1.9.0", "tslib": "^1.9.0",
"url-parse": "^1.2.0", "url-parse": "^1.2.0",
"webpack-format-messages": "^1.0.2" "webpack-format-messages": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@std/esm": "^0.25.3",
"@types/glob": "^5.0.34", "@types/glob": "^5.0.34",
"@types/mkdirp": "^0.5.2", "@types/mkdirp": "^0.5.2",
"@types/rimraf": "^2.0.2", "@types/rimraf": "^2.0.2",
"compression": "^1.7.1", "compression": "^1.7.1",
"eslint": "^4.13.1", "eslint": "^4.13.1",
"eslint-plugin-import": "^2.8.0", "eslint-plugin-import": "^2.8.0",
"esm": "^3.0.28",
"express": "^4.16.3", "express": "^4.16.3",
"get-port": "^3.2.0", "get-port": "^3.2.0",
"mocha": "^5.0.4", "mocha": "^5.0.4",
"nightmare": "^3.0.0", "nightmare": "^3.0.0",
"npm-run-all": "^4.1.2", "npm-run-all": "^4.1.2",
"polka": "^0.3.4", "polka": "^0.3.4",
"rollup": "^0.57.0", "rollup": "^0.58.2",
"rollup-plugin-commonjs": "^9.1.0", "rollup-plugin-commonjs": "^9.1.3",
"rollup-plugin-json": "^2.3.0", "rollup-plugin-json": "^2.3.0",
"rollup-plugin-string": "^2.0.2", "rollup-plugin-string": "^2.0.2",
"rollup-plugin-typescript": "^0.8.1", "rollup-plugin-typescript": "^0.8.1",
"serve-static": "^1.13.2", "serve-static": "^1.13.2",
"svelte": "^1.57.4", "svelte": "^2.4.4",
"svelte-loader": "^2.5.1", "svelte-loader": "^2.9.0",
"ts-node": "^5.0.1", "ts-node": "^6.0.2",
"typescript": "^2.6.2", "typescript": "^2.8.3",
"walk-sync": "^0.3.2", "walk-sync": "^0.3.2",
"webpack": "^4.1.0" "webpack": "^4.1.0"
}, },

View File

@@ -21,10 +21,12 @@ prog.command('dev')
prog.command('build [dest]') prog.command('build [dest]')
.describe('Create a production-ready version of your app') .describe('Create a production-ready version of your app')
.action(async (dest = 'build') => { .option('-p, --port', 'Default of process.env.PORT', '3000')
.example(`build custom-dir -p 4567`)
.action(async (dest = 'build', opts: { port: string }) => {
console.log(`> Building...`); console.log(`> Building...`);
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = process.env.NODE_ENV || 'production';
process.env.SAPPER_DEST = dest; process.env.SAPPER_DEST = dest;
const start = Date.now(); const start = Date.now();
@@ -32,7 +34,20 @@ prog.command('build [dest]')
try { try {
const { build } = await import('./cli/build'); const { build } = await import('./cli/build');
await build(); await build();
console.error(`\n> Finished in ${elapsed(start)}. Type ${clorox.bold.cyan(dest === 'build' ? 'npx sapper start' : `npx sapper start ${dest}`)} to run the app.`);
const launcher = path.resolve(dest, 'index.js');
fs.writeFileSync(launcher, `
// generated by sapper build at ${new Date().toISOString()}
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
process.env.SAPPER_DEST = __dirname;
process.env.PORT = process.env.PORT || ${opts.port || 3000};
console.log('Starting server on port ' + process.env.PORT);
require('./server.js');
`.replace(/^\t+/gm, '').trim());
console.error(`\n> Finished in ${elapsed(start)}. Type ${clorox.bold.cyan(`node ${dest}`)} to run the app.`);
} catch (err) { } catch (err) {
console.error(err ? err.details || err.stack || err.message || err : 'Unknown error'); console.error(err ? err.details || err.stack || err.message || err : 'Unknown error');
} }

View File

@@ -35,7 +35,9 @@ export async function build() {
const client_stats = await compile(client); const client_stats = await compile(client);
console.log(`${clorox.inverse(`\nbuilt client`)}`); console.log(`${clorox.inverse(`\nbuilt client`)}`);
console.log(client_stats.toString({ colors: true })); console.log(client_stats.toString({ colors: true }));
fs.writeFileSync(path.join(output, 'client_info.json'), JSON.stringify(client_stats.toJson())); fs.writeFileSync(path.join(output, 'client_info.json'), JSON.stringify({
assets: client_stats.toJson().assetsByChunkName
}));
const server_stats = await compile(server); const server_stats = await compile(server);
console.log(`${clorox.inverse(`\nbuilt server`)}`); console.log(`${clorox.inverse(`\nbuilt server`)}`);

View File

@@ -263,7 +263,9 @@ export async function dev(opts: { port: number, open: boolean }) {
}, },
result: info => { result: info => {
fs.writeFileSync(path.join(dir, 'client_info.json'), JSON.stringify(info, null, ' ')); fs.writeFileSync(path.join(dir, 'client_info.json'), JSON.stringify({
assets: info.assetsByChunkName
}, null, ' '));
deferreds.client.fulfil(); deferreds.client.fulfil();
const client_files = info.assets.map((chunk: { name: string }) => `client/${chunk.name}`); const client_files = info.assets.map((chunk: { name: string }) => `client/${chunk.name}`);

View File

@@ -7,6 +7,8 @@ export default function create_routes({ files } = { files: glob.sync('**/*.*', {
const routes: Route[] = files const routes: Route[] = files
.filter((file: string) => !/(^|\/|\\)_/.test(file)) .filter((file: string) => !/(^|\/|\\)_/.test(file))
.map((file: string) => { .map((file: string) => {
if (/(^|\/|\\)(_|\.(?!well-known))/.test(file)) return;
if (/]\[/.test(file)) { if (/]\[/.test(file)) {
throw new Error(`Invalid route ${file} — parameters must be separated`); throw new Error(`Invalid route ${file} — parameters must be separated`);
} }

View File

@@ -90,7 +90,7 @@ export default function middleware({ routes, store }: {
cache_control: 'max-age=31536000' cache_control: 'max-age=31536000'
}), }),
get_route_handler(client_info.assetsByChunkName, routes, store) get_route_handler(client_info.assets, routes, store)
].filter(Boolean)); ].filter(Boolean));
return middleware; return middleware;

View File

@@ -214,7 +214,7 @@ let prefetching: {
export function prefetch(href: string) { export function prefetch(href: string) {
const selected = select_route(new URL(href, document.baseURI)); const selected = select_route(new URL(href, document.baseURI));
if (selected) { if (selected && (!prefetching || href !== prefetching.href)) {
prefetching = { prefetching = {
href, href,
promise: selected.route.load().then(mod => prepare_route(mod.default, selected.data)) promise: selected.route.load().then(mod => prepare_route(mod.default, selected.data))
@@ -222,7 +222,16 @@ export function prefetch(href: string) {
} }
} }
function handle_touchstart_mouseover(event: MouseEvent | TouchEvent) { let mousemove_timeout: NodeJS.Timer;
function handle_mousemove(event: MouseEvent) {
clearTimeout(mousemove_timeout);
mousemove_timeout = setTimeout(() => {
trigger_prefetch(event);
}, 20);
}
function trigger_prefetch(event: MouseEvent | TouchEvent) {
const a: HTMLAnchorElement = <HTMLAnchorElement>findAnchor(<Node>event.target); const a: HTMLAnchorElement = <HTMLAnchorElement>findAnchor(<Node>event.target);
if (!a || a.rel !== 'prefetch') return; if (!a || a.rel !== 'prefetch') return;
@@ -248,8 +257,8 @@ export function init(_target: Node, _routes: Route[], opts?: { store?: (data: an
window.addEventListener('popstate', handle_popstate); window.addEventListener('popstate', handle_popstate);
// prefetch // prefetch
window.addEventListener('touchstart', handle_touchstart_mouseover); window.addEventListener('touchstart', trigger_prefetch);
window.addEventListener('mouseover', handle_touchstart_mouseover); window.addEventListener('mousemove', handle_mousemove);
inited = true; inited = true;
} }

View File

@@ -1,6 +1,6 @@
<:Head> <svelte:head>
<title>{{status}}</title> <title>{status}</title>
</:Head> </svelte:head>
<h1>Not found</h1> <h1>Not found</h1>
<p>{{error.message}}</p> <p>{error.message}</p>

View File

@@ -1,6 +1,6 @@
<:Head> <svelte:head>
<title>Internal server error</title> <title>Internal server error</title>
</:Head> </svelte:head>
<h1>Internal server error</h1> <h1>Internal server error</h1>
<p>{{error.message}}</p> <p>{error.message}</p>

View File

@@ -1,6 +1,6 @@
<:Head> <svelte:head>
<title>About</title> <title>About</title>
</:Head> </svelte:head>
<h1>About this site</h1> <h1>About this site</h1>

View File

@@ -1,11 +1,11 @@
<:Head> <svelte:head>
<title>{{post.title}}</title> <title>{post.title}</title>
</:Head> </svelte:head>
<h1>{{post.title}}</h1> <h1>{post.title}</h1>
<div class='content'> <div class='content'>
{{{post.html}}} {@html post.html}
</div> </div>
<script> <script>

View File

@@ -1,17 +1,17 @@
<:Head> <svelte:head>
<title>Blog</title> <title>Blog</title>
</:Head> </svelte:head>
<h1>Recent posts</h1> <h1>Recent posts</h1>
<ul> <ul>
{{#each posts as post}} {#each posts as post}
<!-- we're using the non-standard `rel=prefetch` attribute to <!-- we're using the non-standard `rel=prefetch` attribute to
tell Sapper to load the data for the page as soon as tell Sapper to load the data for the page as soon as
the user hovers over the link or taps it, instead of the user hovers over the link or taps it, instead of
waiting for the 'click' event --> waiting for the 'click' event -->
<li><a rel='prefetch' href='blog/{{post.slug}}'>{{post.title}}</a></li> <li><a rel='prefetch' href='blog/{post.slug}'>{post.title}</a></li>
{{/each}} {/each}
</ul> </ul>
<script> <script>

View File

@@ -1,4 +1,4 @@
<h1>{{message}}</h1> <h1>{message}</h1>
<script> <script>
export default { export default {

View File

@@ -1,6 +1,6 @@
<:Head> <svelte:head>
<title>Sapper project template</title> <title>Sapper project template</title>
</:Head> </svelte:head>
<h1>Great success!</h1> <h1>Great success!</h1>
@@ -11,7 +11,7 @@
<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>
<a rel=prefetch class='{{page === "blog" ? "selected" : ""}}' href='blog'>blog</a> <a rel=prefetch class='{page === "blog" ? "selected" : ""}' href='blog'>blog</a>
<div class='hydrate-test'></div> <div class='hydrate-test'></div>

View File

@@ -1,4 +1,4 @@
<h1>{{foo.bar()}}</h1> <h1>{foo.bar()}</h1>
<script> <script>
export default { export default {

View File

@@ -1,4 +1,4 @@
<h1>{{set.has('x')}}</h1> <h1>{set.has('x')}</h1>
<script> <script>
export default { export default {

View File

@@ -1,4 +1,4 @@
<p>URL is {{url}}</p> <p>URL is {url}</p>
<script> <script>
export default { export default {

View File

@@ -1 +1 @@
<h1>{{$title}}</h1> <h1>{$title}</h1>

View File

@@ -1,3 +1,4 @@
const fs = require('fs');
const path = require('path'); const path = require('path');
const assert = require('assert'); const assert = require('assert');
const Nightmare = require('nightmare'); const Nightmare = require('nightmare');
@@ -38,6 +39,7 @@ describe('sapper', function() {
rimraf.sync('export'); rimraf.sync('export');
rimraf.sync('build'); rimraf.sync('build');
rimraf.sync('.sapper'); rimraf.sync('.sapper');
rimraf.sync('start.js');
this.timeout(process.env.CI ? 30000 : 10000); this.timeout(process.env.CI ? 30000 : 10000);
@@ -148,7 +150,7 @@ function run({ mode, basepath = '' }) {
before(() => { before(() => {
const promise = mode === 'production' const promise = mode === 'production'
? exec(`node ${cli} build`).then(() => ports.find(3000)) ? exec(`node ${cli} build -l`).then(() => ports.find(3000))
: ports.find(3000).then(port => { : ports.find(3000).then(port => {
exec(`node ${cli} dev`); exec(`node ${cli} dev`);
return ports.wait(port).then(() => port); return ports.wait(port).then(() => port);
@@ -160,6 +162,10 @@ function run({ mode, basepath = '' }) {
const dir = mode === 'production' ? 'build' : '.sapper'; const dir = mode === 'production' ? 'build' : '.sapper';
if (mode === 'production') {
assert.ok(fs.existsSync('build/index.js'));
}
proc = require('child_process').fork(`${dir}/server.js`, { proc = require('child_process').fork(`${dir}/server.js`, {
cwd: process.cwd(), cwd: process.cwd(),
env: { env: {
@@ -301,14 +307,17 @@ function run({ mode, basepath = '' }) {
}); });
}); });
it('reuses prefetch promise', () => { it.skip('reuses prefetch promise', () => {
return nightmare return nightmare
.goto(`${base}/blog`) .goto(`${base}/blog`)
.init() .init()
.then(() => { .then(() => {
return capture(() => { return capture(() => {
return nightmare return nightmare
.mouseover('[href="blog/what-is-sapper"]') .evaluate(() => {
const a = document.querySelector('[href="blog/what-is-sapper"]');
a.dispatchEvent(new MouseEvent('mousemove'));
})
.wait(200); .wait(200);
}); });
}) })

View File

@@ -172,6 +172,17 @@ describe('create_routes', () => {
); );
}); });
it('ignores files and directories with leading dots except .well-known', () => {
const routes = create_routes({
files: ['.well-known', '.unknown']
});
assert.deepEqual(
routes.map(r => r.file),
['.well-known']
);
});
it('matches /foo/:bar before /:baz/qux', () => { it('matches /foo/:bar before /:baz/qux', () => {
const a = create_routes({ const a = create_routes({
files: ['foo/[bar].html', '[baz]/qux.html'] files: ['foo/[bar].html', '[baz]/qux.html']