Compare commits

...

25 Commits

Author SHA1 Message Date
Rich Harris
8c07d9d2ac -> v0.20.3 2018-09-08 10:24:21 -04:00
Rich Harris
7bd684a80e Merge pull request #428 from nolanlawson/cache-control-service-worker
Set service-worker max-age to 0
2018-09-08 10:20:04 -04:00
Rich Harris
cbb5e8755b Use MDN recommendation for preventing SW caching 2018-09-08 10:12:55 -04:00
Rich Harris
7ef72dbb77 Merge pull request #429 from nolanlawson/consistent-cache-control
Use consistent cache-control:max-age=600 for HTML pages
2018-09-08 09:40:56 -04:00
Nolan Lawson
87ff9c2aeb fix test 2018-09-07 17:25:17 -07:00
Nolan Lawson
2d1f535314 use consistent cache-control:max-age=600 for HTML pages 2018-09-07 16:46:40 -07:00
Rich Harris
cd1b53b80d Merge pull request #424 from nolanlawson/csp-nonce
Allow scripts to contain a CSP nonce
2018-09-07 19:46:07 -04:00
Rich Harris
0a7be736c0 snake_case 2018-09-07 19:45:40 -04:00
Nolan Lawson
5ee53a98c6 set service-worker max-age to 0 2018-09-07 16:28:38 -07:00
Rich Harris
0e8ed6612c -> v0.20.2 2018-09-07 18:19:39 -04:00
Rich Harris
5ec748b95d Merge pull request #427 from sveltejs/gh-426
handle value-less query string parameters
2018-09-07 18:18:28 -04:00
Rich Harris
64b16715cd handle value-less query string parameters - fixes #426 2018-09-07 18:02:58 -04:00
Rich Harris
9ea5e5e251 Merge pull request #425 from nolanlawson/cache-control-immutable
Add cache-control:immutable for immutable assets
2018-09-07 17:12:23 -04:00
Rich Harris
68b78f56d6 remove unused file 2018-09-07 16:42:56 -04:00
Nolan Lawson
68e93a8fa0 add cache-control:immutable for immutable assets 2018-09-07 11:07:50 -07:00
Nolan Lawson
e377515867 allow scripts to contain a CSP nonce 2018-09-07 09:46:53 -07:00
Rich Harris
99ae39b8a8 -> v0.20.1 2018-09-06 18:57:11 -04:00
Rich Harris
1b489f4687 -> v0.20.0 2018-09-04 07:35:24 -04:00
Rich Harris
91f2c6e49c Merge pull request #418 from nsivertsen/sourcemaps
Enable source maps by default in dev mode when using rollup
2018-09-04 07:32:15 -04:00
Rich Harris
f5e07e9f78 Merge pull request #419 from sveltejs/gh-417
decode req.params
2018-09-04 07:31:53 -04:00
Rich Harris
17297a9794 Merge pull request #414 from sveltejs/gh-347-b
decode req.path during export
2018-09-04 07:31:36 -04:00
Rich Harris
9ef4f33e38 decode query params 2018-09-03 20:09:25 -04:00
Rich Harris
30966ee7f2 decode req.params - fixes #417 2018-09-03 18:36:07 -04:00
Nikolai Sivertsen
ae90f774e1 Enable source maps by default in dev mode when using rollup 2018-09-04 00:26:18 +02:00
Rich Harris
0706b5f50a decode req.path during export 2018-09-03 09:02:13 -04:00
15 changed files with 143 additions and 24 deletions

View File

@@ -1,5 +1,26 @@
# sapper changelog # sapper changelog
## 0.20.3
* Inject `nonce` attribute if `res.locals.nonce` is present ([#424](https://github.com/sveltejs/sapper/pull/424))
* Prevent service worker caching ([#428](https://github.com/sveltejs/sapper/pull/428))
* Consistent caching for HTML responses ([#429](https://github.com/sveltejs/sapper/pull/429))
## 0.20.2
* Add `immutable` cache control header for hashed assets ([#425](https://github.com/sveltejs/sapper/pull/425))
* Handle value-less query string params ([#426](https://github.com/sveltejs/sapper/issues/426))
## 0.20.1
* Update shimport
## 0.20.0
* Decode `req.params` and `req.query` ([#417](https://github.com/sveltejs/sapper/issues/417))
* Decode URLs before writing files in `sapper export` ([#414](https://github.com/sveltejs/sapper/pull/414))
* Generate server sourcemaps for Rollup apps in dev mode ([#418](https://github.com/sveltejs/sapper/pull/418))
## 0.19.3 ## 0.19.3
* Better unicode route handling ([#347](https://github.com/sveltejs/sapper/issues/347)) * Better unicode route handling ([#347](https://github.com/sveltejs/sapper/issues/347))

View File

@@ -1 +0,0 @@
<svelte:component this={child.component} {...child.props}/>

8
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "sapper", "name": "sapper",
"version": "0.19.0", "version": "0.20.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -6287,9 +6287,9 @@
} }
}, },
"shimport": { "shimport": {
"version": "0.0.10", "version": "0.0.11",
"resolved": "https://registry.npmjs.org/shimport/-/shimport-0.0.10.tgz", "resolved": "https://registry.npmjs.org/shimport/-/shimport-0.0.11.tgz",
"integrity": "sha512-3xPFDLmcLj87sx0OwA60qbloMQUsu6VGF97IG4RqxTf91sGeiaaXOPxM1PoQHbaTm4TOhH8zosokqLAZtuNGnA==" "integrity": "sha512-wRlG/wMuV/czrzJEWBUPjydU/Ve0kTrTH8wHLRjuY6S2BDB+qDDXkTY/WrNc/7t5jnd0LPVO1sRIE7Ga6uXTpw=="
}, },
"signal-exit": { "signal-exit": {
"version": "3.0.2", "version": "3.0.2",

View File

@@ -1,6 +1,6 @@
{ {
"name": "sapper", "name": "sapper",
"version": "0.19.3", "version": "0.20.3",
"description": "Military-grade apps, engineered by Svelte", "description": "Military-grade apps, engineered by Svelte",
"main": "dist/middleware.js", "main": "dist/middleware.js",
"bin": { "bin": {
@@ -20,7 +20,7 @@
}, },
"dependencies": { "dependencies": {
"html-minifier": "^3.5.16", "html-minifier": "^3.5.16",
"shimport": "^0.0.10", "shimport": "0.0.11",
"source-map-support": "^0.5.6", "source-map-support": "^0.5.6",
"sourcemap-codec": "^1.4.1", "sourcemap-codec": "^1.4.1",
"string-hash": "^1.1.3", "string-hash": "^1.1.3",

View File

@@ -85,7 +85,7 @@ async function execute(emitter: EventEmitter, opts: Opts) {
function save(path: string, status: number, type: string, body: string) { function save(path: string, status: number, type: string, body: string) {
const { pathname } = resolve(origin, path); const { pathname } = resolve(origin, path);
let file = pathname.slice(1); let file = decodeURIComponent(pathname.slice(1));
if (saved.has(file)) return; if (saved.has(file)) return;
saved.add(file); saved.add(file);

View File

@@ -63,6 +63,8 @@ function generate_client(
import root from '${get_file(path_to_routes, manifest_data.root)}'; import root from '${get_file(path_to_routes, manifest_data.root)}';
import error from '${posixify(`${path_to_routes}/_error.html`)}'; import error from '${posixify(`${path_to_routes}/_error.html`)}';
const d = decodeURIComponent;
${manifest_data.components.map(component => { ${manifest_data.components.map(component => {
const annotation = bundler === 'webpack' const annotation = bundler === 'webpack'
? `/* webpackChunkName: "${component.name}" */ ` ? `/* webpackChunkName: "${component.name}" */ `
@@ -88,7 +90,7 @@ function generate_client(
if (part === null) return 'null'; if (part === null) return 'null';
if (part.params.length > 0) { if (part.params.length > 0) {
const props = part.params.map((param, i) => `${param}: match[${i + 1}]`); const props = part.params.map((param, i) => `${param}: d(match[${i + 1}])`);
return `{ component: ${part.component.name}, params: match => ({ ${props.join(', ')} }) }`; return `{ component: ${part.component.name}, params: match => ({ ${props.join(', ')} }) }`;
} }
@@ -138,6 +140,8 @@ function generate_server(
// This file is generated by Sapper — do not edit it! // This file is generated by Sapper — do not edit it!
${imports.join('\n')} ${imports.join('\n')}
const d = decodeURIComponent;
export const manifest = { export const manifest = {
server_routes: [ server_routes: [
${manifest_data.server_routes.map(route => `{ ${manifest_data.server_routes.map(route => `{
@@ -145,7 +149,7 @@ function generate_server(
pattern: ${route.pattern}, pattern: ${route.pattern},
handlers: ${route.name}, handlers: ${route.name},
params: ${route.params.length > 0 params: ${route.params.length > 0
? `match => ({ ${route.params.map((param, i) => `${param}: match[${i + 1}]`).join(', ')} })` ? `match => ({ ${route.params.map((param, i) => `${param}: d(match[${i + 1}])`).join(', ')} })`
: `() => ({})`} : `() => ({})`}
}`).join(',\n\n\t\t\t\t')} }`).join(',\n\n\t\t\t\t')}
], ],
@@ -165,7 +169,7 @@ function generate_server(
]; ];
if (part.params.length > 0) { if (part.params.length > 0) {
const params = part.params.map((param, i) => `${param}: match[${i + 1}]`); const params = part.params.map((param, i) => `${param}: d(match[${i + 1}])`);
props.push(`params: match => ({ ${params.join(', ')} })`); props.push(`params: match => ({ ${params.join(', ')} })`);
} }

View File

@@ -136,22 +136,22 @@ export default function middleware(opts: {
fs.existsSync(path.join(output, 'index.html')) && serve({ fs.existsSync(path.join(output, 'index.html')) && serve({
pathname: '/index.html', pathname: '/index.html',
cache_control: 'max-age=600' cache_control: dev() ? 'no-cache' : 'max-age=600'
}), }),
fs.existsSync(path.join(output, 'service-worker.js')) && serve({ fs.existsSync(path.join(output, 'service-worker.js')) && serve({
pathname: '/service-worker.js', pathname: '/service-worker.js',
cache_control: 'max-age=600' cache_control: 'no-cache, no-store, must-revalidate'
}), }),
fs.existsSync(path.join(output, 'service-worker.js.map')) && serve({ fs.existsSync(path.join(output, 'service-worker.js.map')) && serve({
pathname: '/service-worker.js.map', pathname: '/service-worker.js.map',
cache_control: 'max-age=600' cache_control: 'no-cache, no-store, must-revalidate'
}), }),
serve({ serve({
prefix: '/client/', prefix: '/client/',
cache_control: dev() ? 'no-cache' : 'max-age=31536000' cache_control: dev() ? 'no-cache' : 'max-age=31536000, immutable'
}), }),
get_server_route_handler(manifest.server_routes), get_server_route_handler(manifest.server_routes),
@@ -312,6 +312,7 @@ function get_page_handler(
} = get_build_info(); } = get_build_info();
res.setHeader('Content-Type', 'text/html'); res.setHeader('Content-Type', 'text/html');
res.setHeader('Cache-Control', dev() ? 'no-cache' : 'max-age=600');
// preload main.js and current route // preload main.js and current route
// TODO detect other stuff we can preload? images, CSS, fonts? // TODO detect other stuff we can preload? images, CSS, fonts?
@@ -524,9 +525,12 @@ function get_page_handler(
styles = (css && css.code ? `<style>${css.code}</style>` : ''); styles = (css && css.code ? `<style>${css.code}</style>` : '');
} }
// users can set a CSP nonce using res.locals.nonce
const nonce_attr = (res.locals && res.locals.nonce) ? ` nonce="${res.locals.nonce}"` : '';
const body = template() const body = template()
.replace('%sapper.base%', () => `<base href="${req.baseUrl}/">`) .replace('%sapper.base%', () => `<base href="${req.baseUrl}/">`)
.replace('%sapper.scripts%', () => `<script>${script}</script>`) .replace('%sapper.scripts%', () => `<script${nonce_attr}>${script}</script>`)
.replace('%sapper.html%', () => html) .replace('%sapper.html%', () => html)
.replace('%sapper.head%', () => `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`) .replace('%sapper.head%', () => `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
.replace('%sapper.styles%', () => styles); .replace('%sapper.styles%', () => styles);

View File

@@ -30,7 +30,8 @@ export default {
output: () => { output: () => {
return { return {
dir: locations.dest(), dir: locations.dest(),
format: 'cjs' format: 'cjs',
sourcemap: dev()
}; };
} }
}, },

View File

@@ -66,8 +66,8 @@ function select_route(url: URL): Target {
const query: Record<string, string | true> = {}; const query: Record<string, string | true> = {};
if (url.search.length > 0) { if (url.search.length > 0) {
url.search.slice(1).split('&').forEach(searchParam => { url.search.slice(1).split('&').forEach(searchParam => {
const [, key, value] = /([^=]+)=(.*)/.exec(searchParam); const [, key, value] = /([^=]+)(?:=(.*))?/.exec(searchParam);
query[key] = value || true; query[key] = decodeURIComponent((value || '').replace(/\+/g, ' '));
}); });
} }
return { url, path, page, match, query }; return { url, path, page, match, query };

View File

@@ -1,4 +1,4 @@
import { init, prefetchRoutes } from '../../../runtime.js'; import { init, goto, prefetchRoutes } from '../../../runtime.js';
import { Store } from 'svelte/store.js'; import { Store } from 'svelte/store.js';
import { manifest } from './manifest/client.js'; import { manifest } from './manifest/client.js';
@@ -11,3 +11,4 @@ window.init = () => {
}; };
window.prefetchRoutes = prefetchRoutes; window.prefetchRoutes = prefetchRoutes;
window.goto = goto;

View File

@@ -106,6 +106,14 @@ const posts = [
<p>If you didn't have adult onset diabetes, I wouldn't mind giving you a little sugar. Everybody dance NOW. And the soup of the day is bread. Great, now I'm gonna smell to high heaven like a tuna melt!</p> <p>If you didn't have adult onset diabetes, I wouldn't mind giving you a little sugar. Everybody dance NOW. And the soup of the day is bread. Great, now I'm gonna smell to high heaven like a tuna melt!</p>
<p>That's how Tony Wonder lost a nut. She calls it a Mayonegg. Go ahead, touch the Cornballer. There's a new daddy in town. A discipline daddy.</p> <p>That's how Tony Wonder lost a nut. She calls it a Mayonegg. Go ahead, touch the Cornballer. There's a new daddy in town. A discipline daddy.</p>
` `
},
{
title: 'Encödïng test',
slug: 'encödïng-test',
html: `
<p>It works</p>
`
} }
]; ];

View File

@@ -0,0 +1,12 @@
<h1>{slug} ({message})</h1>
<script>
export default {
preload({ params, query }) {
return {
slug: params.slug,
message: query.message
};
}
};
</script>

View File

@@ -0,0 +1,15 @@
export function get(req, res) {
res.writeHead(200, {
'Content-Type': 'text/html'
});
res.end(`
<!doctype html>
<html>
<head><meta charset="utf-8"></head>
<body>
<h1>${req.params.slug}</h1>
</body>
</html>
`);
}

View File

@@ -15,6 +15,8 @@
<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>
<a href="const">const</a> <a href="const">const</a>
<a href="echo/page/encöded?message=hëllö+wörld">echo/page/encöded?message=hëllö+wörld</a>
<a href="echo/page/empty?message">echo/page/empty?message</a>
<div class='hydrate-test'></div> <div class='hydrate-test'></div>

View File

@@ -5,6 +5,7 @@ const Nightmare = require('nightmare');
const walkSync = require('walk-sync'); const walkSync = require('walk-sync');
const rimraf = require('rimraf'); const rimraf = require('rimraf');
const ports = require('port-authority'); const ports = require('port-authority');
const fetch = require('node-fetch');
Nightmare.action('page', { Nightmare.action('page', {
title(done) { title(done) {
@@ -93,6 +94,7 @@ function testExport({ basepath = '' }) {
'blog/how-to-use-sapper/index.html', 'blog/how-to-use-sapper/index.html',
'blog/what-is-sapper/index.html', 'blog/what-is-sapper/index.html',
'blog/why-the-name/index.html', 'blog/why-the-name/index.html',
'blog/encödïng-test/index.html',
'blog.json', 'blog.json',
'blog/a-very-long-post.json', 'blog/a-very-long-post.json',
@@ -101,6 +103,7 @@ function testExport({ basepath = '' }) {
'blog/how-to-use-sapper.json', 'blog/how-to-use-sapper.json',
'blog/what-is-sapper.json', 'blog/what-is-sapper.json',
'blog/why-the-name.json', 'blog/why-the-name.json',
'blog/encödïng-test.json',
'favicon.png', 'favicon.png',
'global.css', 'global.css',
@@ -751,18 +754,67 @@ function run({ mode, basepath = '' }) {
assert.equal(title, 'reserved words are okay as routes'); assert.equal(title, 'reserved words are okay as routes');
}); });
}); });
it('encodes req.params and req.query for server-rendered pages', () => {
return nightmare.goto(`${base}/echo/page/encöded?message=hëllö+wörld`)
.page.title()
.then(title => {
assert.equal(title, 'encöded (hëllö wörld)');
});
});
it('encodes req.params and req.query for client-rendered pages', () => {
return nightmare.goto(base).init()
.click('a[href="echo/page/encöded?message=hëllö+wörld"]')
.wait(100)
.page.title()
.then(title => {
assert.equal(title, 'encöded (hëllö wörld)');
});
});
it('accepts value-less query string parameter on server', () => {
return nightmare.goto(`${base}/echo/page/empty?message`)
.page.title()
.then(title => {
assert.equal(title, 'empty ()');
});
});
it('accepts value-less query string parameter on client', () => {
return nightmare.goto(base).init()
.click('a[href="echo/page/empty?message"]')
.wait(100)
.page.title()
.then(title => {
assert.equal(title, 'empty ()');
});
});
it('encodes req.params for server routes', () => {
return nightmare.goto(`${base}/echo/server-route/encöded`)
.page.title()
.then(title => {
assert.equal(title, 'encöded');
});
});
}); });
describe('headers', () => { describe('headers', () => {
it('sets Content-Type and Link...preload headers', () => { it('sets Content-Type, Link...preload, and Cache-Control headers', () => {
return capture(() => nightmare.goto(base)).then(requests => { return capture(() => fetch(base)).then(responses => {
const { headers } = requests[0]; const { headers } = responses[0];
assert.equal( assert.equal(
headers['content-type'], headers['content-type'],
'text/html' 'text/html'
); );
assert.equal(
headers['cache-control'],
'max-age=600'
);
const str = ['main', '.+?\\.\\d+'] const str = ['main', '.+?\\.\\d+']
.map(file => { .map(file => {
return `<${basepath}/client/[^/]+/${file}\\.js>;rel="preload";as="script"`; return `<${basepath}/client/[^/]+/${file}\\.js>;rel="preload";as="script"`;