Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.

Commit 6000fde

Browse files
committed
feat: sapper export --no-subfolders
Prevent trailing slash redirect for hosts that prefer `export/foo.htm` instead of `export/foo/index.htm` (github pages)
1 parent b1c4585 commit 6000fde

21 files changed

+349
-1
lines changed

site/content/docs/10-exporting.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,5 @@ Because `sapper export` writes to the filesystem, it isn't possible to have two
6161
The solution is to rename one of the routes to avoid conflict — for example, `src/routes/foo-bar.js`. (Note that you would also need to update any code that fetches data from `/foo/bar` to reference `/foo-bar` instead.)
6262

6363
For *pages*, we skirt around this problem by writing `export/foo/index.html` instead of `export/foo`.
64+
65+
For hosts that prefer `export/foo.htm` instead of `export/foo/index.htm` (github pages) pass the `--no-subfolders` option.

src/api/export.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Opts = {
1919
static?: string,
2020
basepath?: string,
2121
host_header?: string,
22+
subfolders?: boolean,
2223
timeout?: number | false,
2324
concurrent?: number,
2425
oninfo?: ({ message }: { message: string }) => void;
@@ -51,6 +52,7 @@ async function _export({
5152
export_dir = '__sapper__/export',
5253
basepath = '',
5354
host_header,
55+
subfolders = true,
5456
timeout = 5000,
5557
concurrent = 8,
5658
oninfo = noop,
@@ -113,7 +115,11 @@ async function _export({
113115

114116
if (is_html) {
115117
if (pathname !== '/service-worker-index.html') {
116-
file = file === '' ? 'index.html' : `${file}/index.html`;
118+
if (file === '') {
119+
file = 'index.html'
120+
} else {
121+
file = subfolders ? `${file}/index.html` : `${file}.html`;
122+
}
117123
}
118124
body = minify_html(body);
119125
}

src/cli.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ prog.command('export [dest]')
207207
.option('--build-dir', 'Intermediate build directory', '__sapper__/build')
208208
.option('--ext', 'Custom page route extensions (space separated)', '.svelte .html')
209209
.option('--entry', 'Custom entry points (space separated)', '/')
210+
.option('--subfolders', 'Generate /path/index.html instead of /path.html', true)
210211
.action(async (dest = '__sapper__/export', opts: {
211212
build: boolean,
212213
legacy: boolean,
@@ -223,6 +224,7 @@ prog.command('export [dest]')
223224
'build-dir': string,
224225
ext: string
225226
entry: string
227+
subfolders: boolean
226228
}) => {
227229
try {
228230
if (opts.build) {
@@ -244,6 +246,7 @@ prog.command('export [dest]')
244246
timeout: opts.timeout,
245247
concurrent: opts.concurrent,
246248
entry: opts.entry,
249+
subfolders: opts.subfolders,
247250

248251
oninfo: event => {
249252
console.log(colors.bold().cyan(`> ${event.message}`));
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import resolve from 'rollup-plugin-node-resolve';
2+
import replace from 'rollup-plugin-replace';
3+
import svelte from 'rollup-plugin-svelte';
4+
5+
const mode = process.env.NODE_ENV;
6+
const dev = mode === 'development';
7+
8+
const config = require('../../../config/rollup.js');
9+
10+
export default {
11+
client: {
12+
input: config.client.input(),
13+
output: config.client.output(),
14+
plugins: [
15+
replace({
16+
'process.browser': true,
17+
'process.env.NODE_ENV': JSON.stringify(mode)
18+
}),
19+
svelte({
20+
dev,
21+
hydratable: true,
22+
emitCss: true
23+
}),
24+
resolve()
25+
]
26+
},
27+
28+
server: {
29+
input: config.server.input(),
30+
output: config.server.output(),
31+
plugins: [
32+
replace({
33+
'process.browser': false,
34+
'process.env.NODE_ENV': JSON.stringify(mode)
35+
}),
36+
svelte({
37+
generate: 'ssr',
38+
dev
39+
}),
40+
resolve({
41+
preferBuiltins: true
42+
})
43+
],
44+
external: ['sirv', 'polka']
45+
},
46+
47+
serviceworker: {
48+
input: config.serviceworker.input(),
49+
output: config.serviceworker.output(),
50+
plugins: [
51+
resolve(),
52+
replace({
53+
'process.browser': true,
54+
'process.env.NODE_ENV': JSON.stringify(mode)
55+
})
56+
]
57+
}
58+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as sapper from '@sapper/app';
2+
3+
window.start = () => sapper.start({
4+
target: document.querySelector('#sapper')
5+
});
6+
7+
window.prefetchRoutes = () => sapper.prefetchRoutes();
8+
window.prefetch = href => sapper.prefetch(href);
9+
window.goto = href => sapper.goto(href);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<h1>{status}</h1>
2+
3+
<p>{error.message}</p>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script context="module">
2+
export function preload({ params }) {
3+
return this.fetch(`blog/${params.slug}.json`).then(r => r.json()).then(post => {
4+
return { post };
5+
});
6+
}
7+
</script>
8+
9+
<script>
10+
export let post;
11+
</script>
12+
13+
<h1>{post.title}</h1>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import posts from './_posts.js';
2+
3+
export function get(req, res) {
4+
const post = posts.find(post => post.slug === req.params.slug);
5+
6+
if (post) {
7+
res.writeHead(200, {
8+
'Content-Type': 'application/json'
9+
});
10+
11+
res.end(JSON.stringify(post));
12+
} else {
13+
res.writeHead(404, {
14+
'Content-Type': 'application/json'
15+
});
16+
17+
res.end(JSON.stringify({ message: 'not found' }));
18+
}
19+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default [
2+
{ slug: 'foo', title: 'once upon a foo' },
3+
{ slug: 'bar', title: 'a bar is born' },
4+
{ slug: 'baz', title: 'bazzily ever after' }
5+
];
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script context="module">
2+
export function preload() {
3+
return this.fetch('blog.json').then(r => r.json()).then(posts => {
4+
return { posts };
5+
});
6+
}
7+
</script>
8+
9+
<script>
10+
export let posts;
11+
</script>
12+
13+
<h1>blog</h1>
14+
15+
{#each posts as post}
16+
<p><a href="blog/{post.slug}">{post.title}</a></p>
17+
{/each}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import posts from './_posts.js';
2+
3+
export function get(req, res) {
4+
res.writeHead(200, {
5+
'Content-Type': 'application/json'
6+
});
7+
8+
res.end(JSON.stringify(posts));
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script context="module">
2+
export function preload({ params }) {
3+
return params;
4+
}
5+
</script>
6+
7+
<script>
8+
export let a;
9+
export let b;
10+
</script>
11+
12+
<p>{a}/{b}</p>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script context="module">
2+
export function preload({ params }) {
3+
return params;
4+
}
5+
</script>
6+
7+
<script>
8+
export let a;
9+
10+
const list = Array(20).fill().map((_, i) => i + 1);
11+
</script>
12+
13+
{#each list as b}
14+
<a href="boom/{a}/{b}">{a}/{b}</a>
15+
{/each}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
const list = Array(20).fill().map((_, i) => i + 1);
3+
</script>
4+
5+
{#each list as a}
6+
<a href="boom/{a}">{a}</a>
7+
{/each}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<h1>Great success!</h1>
2+
3+
<a href="blog">blog</a>
4+
<a href="">empty anchor</a>
5+
<a href=''>empty anchor #2</a>
6+
<a href=>empty anchor #3</a>
7+
<a href= >empty anchor #4</a>
8+
<a href>empty anchor #5</a>
9+
<a>empty anchor #6</a>
10+
<a href="boom">boom</a>
11+
<a href="test.pdf">pdf file</a>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import sirv from 'sirv';
2+
import polka from 'polka';
3+
import * as sapper from '@sapper/server';
4+
5+
import { start, dev } from '../../common.js';
6+
7+
const app = polka()
8+
.use(
9+
sirv('static', { dev }),
10+
sapper.middleware()
11+
);
12+
13+
start(app);
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import * as sapper from '@sapper/service-worker';
2+
3+
const ASSETS = `cache${sapper.timestamp}`;
4+
5+
// `shell` is an array of all the files generated by webpack,
6+
// `files` is an array of everything in the `static` directory
7+
const to_cache = sapper.shell.concat(sapper.files);
8+
const cached = new Set(to_cache);
9+
10+
self.addEventListener('install', event => {
11+
event.waitUntil(
12+
caches
13+
.open(ASSETS)
14+
.then(cache => cache.addAll(to_cache))
15+
.then(() => {
16+
self.skipWaiting();
17+
})
18+
);
19+
});
20+
21+
self.addEventListener('activate', event => {
22+
event.waitUntil(
23+
caches.keys().then(async keys => {
24+
// delete old caches
25+
for (const key of keys) {
26+
if (key !== ASSETS) await caches.delete(key);
27+
}
28+
29+
self.clients.claim();
30+
})
31+
);
32+
});
33+
34+
self.addEventListener('fetch', event => {
35+
if (event.request.method !== 'GET') return;
36+
37+
const url = new URL(event.request.url);
38+
39+
// don't try to handle e.g. data: URIs
40+
if (!url.protocol.startsWith('http')) return;
41+
42+
// ignore dev server requests
43+
if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
44+
45+
// always serve assets and webpack-generated files from cache
46+
if (url.host === self.location.host && cached.has(url.pathname)) {
47+
event.respondWith(caches.match(event.request));
48+
return;
49+
}
50+
51+
// for pages, you might want to serve a shell `index.html` file,
52+
// which Sapper has generated for you. It's not right for every
53+
// app, but if it's right for yours then uncomment this section
54+
/*
55+
if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
56+
event.respondWith(caches.match('/index.html'));
57+
return;
58+
}
59+
*/
60+
61+
if (event.request.cache === 'only-if-cached') return;
62+
63+
// for everything else, try the network first, falling back to
64+
// cache if the user is offline. (If the pages never change, you
65+
// might prefer a cache-first approach to a network-first one.)
66+
event.respondWith(
67+
caches
68+
.open(`offline${sapper.timestamp}`)
69+
.then(async cache => {
70+
try {
71+
const response = await fetch(event.request);
72+
cache.put(event.request, response.clone());
73+
return response;
74+
} catch(err) {
75+
const response = await cache.match(event.request);
76+
if (response) return response;
77+
78+
throw err;
79+
}
80+
})
81+
);
82+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset='utf-8'>
5+
6+
%sapper.base%
7+
%sapper.styles%
8+
%sapper.head%
9+
</head>
10+
<body>
11+
<div id='sapper'>%sapper.html%</div>
12+
%sapper.scripts%
13+
</body>
14+
</html>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
body {
2+
font-family: 'Helvetica';
3+
}
5.55 KB
Binary file not shown.

0 commit comments

Comments
 (0)