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

Allow export to take multiple entrypoints to use when crawling. #825

Merged
merged 4 commits into from Jul 28, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 24 additions & 5 deletions src/api/export.ts
Expand Up @@ -22,6 +22,7 @@ type Opts = {
concurrent?: number,
oninfo?: ({ message }: { message: string }) => void;
onfile?: ({ file, size, status }: { file: string, size: number, status: number }) => void;
entry?: string;
};

type Ref = {
Expand All @@ -34,6 +35,10 @@ function resolve(from: string, to: string) {
return url.parse(url.resolve(from, to));
}

function cleanPath(path: string) {
return path.replace(/^\/|\/$|\/*index(.html)*$|.html$/g, '')
}

type URL = url.UrlWithStringQuery;

export { _export as export };
Expand All @@ -47,7 +52,8 @@ async function _export({
timeout = 5000,
concurrent = 8,
oninfo = noop,
onfile = noop
onfile = noop,
entry = '/'
}: Opts = {}) {
basepath = basepath.replace(/^\//, '')

Expand All @@ -74,10 +80,15 @@ async function _export({
const root = resolve(origin, basepath);
if (!root.href.endsWith('/')) root.href += '/';

oninfo({
message: `Crawling ${root.href}`
const entryPoints = entry.split(' ').map(entryPoint => {
const entry = resolve(origin, `${basepath}/${cleanPath(entryPoint)}`);
if (!entry.href.endsWith('/')) entry.href += '/';

return entry;
});

let currentRoot = 0;

const proc = child_process.fork(path.resolve(`${build_dir}/server/server.js`), [], {
cwd,
env: Object.assign({
Expand Down Expand Up @@ -201,7 +212,7 @@ async function _export({
type = 'text/html';
body = `<script>window.location.href = "${location.replace(origin, '')}"</script>`;

tasks.push(handle(resolve(root.href, location)));
tasks.push(handle(resolve(entryPoints[currentRoot].href, location)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for resolving this relative to the current entry point? I think this should still always be relative to root. My understanding is that the multiple entry points is just so that the crawler can find pages it wouldn't ordinarily find. All of them should still have the same base href value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test you wrote still passes when using root.href, fwiw

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think you're right. I don't think I looked too deeply into this but I guess this clause is handling redirects/ 300s? I think it's using the Location header and building a new url to handle from that. My tests definitely don't test this behaviour, I don't know if any of the existing ones do either but resolving with anything but the root url would fail, I think.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, looking back this clears up some of the confusion I had at the time with the whole entryPoints[currentRoot] thing, I wondered why that was necessary. This correction removes the need for that entirely. I probably should have taken more time to understand the code before implementing this.

}

save(pathname, r.status, type, body);
Expand All @@ -211,7 +222,15 @@ async function _export({

try {
await ports.wait(port);
await handle(root);

for (let i = 0; i < entryPoints.length; i++) {
oninfo({
message: `Crawling ${entryPoints[currentRoot].href}`
});
await handle(entryPoints[currentRoot]);
currentRoot++;
}

await handle(resolve(root.href, 'service-worker-index.html'));
await q.close();

Expand Down
3 changes: 3 additions & 0 deletions src/cli.ts
Expand Up @@ -205,6 +205,7 @@ prog.command('export [dest]')
.option('--output', 'Sapper output directory', 'src/node_modules/@sapper')
.option('--build-dir', 'Intermediate build directory', '__sapper__/build')
.option('--ext', 'Custom Route Extension', '.svelte .html')
.option('--entry', 'Custom entry points', '/')
.action(async (dest = '__sapper__/export', opts: {
build: boolean,
legacy: boolean,
Expand All @@ -219,6 +220,7 @@ prog.command('export [dest]')
output: string,
'build-dir': string,
ext: string
entry: string
}) => {
try {
if (opts.build) {
Expand All @@ -238,6 +240,7 @@ prog.command('export [dest]')
basepath: opts.basepath,
timeout: opts.timeout,
concurrent: opts.concurrent,
entry: opts.entry,

oninfo: event => {
console.log(colors.bold().cyan(`> ${event.message}`));
Expand Down
58 changes: 58 additions & 0 deletions test/apps/export-multiple-entry/rollup.config.js
@@ -0,0 +1,58 @@
import resolve from 'rollup-plugin-node-resolve';
import replace from 'rollup-plugin-replace';
import svelte from 'rollup-plugin-svelte';

const mode = process.env.NODE_ENV;
const dev = mode === 'development';

const config = require('../../../config/rollup.js');

export default {
client: {
input: config.client.input(),
output: config.client.output(),
plugins: [
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode)
}),
svelte({
dev,
hydratable: true,
emitCss: true
}),
resolve()
]
},

server: {
input: config.server.input(),
output: config.server.output(),
plugins: [
replace({
'process.browser': false,
'process.env.NODE_ENV': JSON.stringify(mode)
}),
svelte({
generate: 'ssr',
dev
}),
resolve({
preferBuiltins: true
})
],
external: ['sirv', 'polka']
},

serviceworker: {
input: config.serviceworker.input(),
output: config.serviceworker.output(),
plugins: [
resolve(),
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode)
})
]
}
};
9 changes: 9 additions & 0 deletions test/apps/export-multiple-entry/src/client.js
@@ -0,0 +1,9 @@
import * as sapper from '@sapper/app';

window.start = () => sapper.start({
target: document.querySelector('#sapper')
});

window.prefetchRoutes = () => sapper.prefetchRoutes();
window.prefetch = href => sapper.prefetch(href);
window.goto = href => sapper.goto(href);
3 changes: 3 additions & 0 deletions test/apps/export-multiple-entry/src/routes/_error.svelte
@@ -0,0 +1,3 @@
<h1>{status}</h1>

<p>{error.message}</p>
1 change: 1 addition & 0 deletions test/apps/export-multiple-entry/src/routes/about.svelte
@@ -0,0 +1 @@
<h1>I am an about page</h1>
13 changes: 13 additions & 0 deletions test/apps/export-multiple-entry/src/routes/blog/[slug].html
@@ -0,0 +1,13 @@
<script context="module">
export function preload({ params }) {
return this.fetch(`blog/${params.slug}.json`).then(r => r.json()).then(post => {
return { post };
});
}
</script>

<script>
export let post;
</script>

<h1>{post.title}</h1>
19 changes: 19 additions & 0 deletions test/apps/export-multiple-entry/src/routes/blog/[slug].json.js
@@ -0,0 +1,19 @@
import posts from './_posts.js';

export function get(req, res) {
const post = posts.find(post => post.slug === req.params.slug);

if (post) {
res.writeHead(200, {
'Content-Type': 'application/json'
});

res.end(JSON.stringify(post));
} else {
res.writeHead(404, {
'Content-Type': 'application/json'
});

res.end(JSON.stringify({ message: 'not found' }));
}
}
5 changes: 5 additions & 0 deletions test/apps/export-multiple-entry/src/routes/blog/_posts.js
@@ -0,0 +1,5 @@
export default [
{ slug: 'foo', title: 'once upon a foo' },
{ slug: 'bar', title: 'a bar is born' },
{ slug: 'baz', title: 'bazzily ever after' }
];
17 changes: 17 additions & 0 deletions test/apps/export-multiple-entry/src/routes/blog/index.html
@@ -0,0 +1,17 @@
<script context="module">
export function preload() {
return this.fetch('blog.json').then(r => r.json()).then(posts => {
return { posts };
});
}
</script>

<script>
export let posts;
</script>

<h1>blog</h1>

{#each posts as post}
<p><a href="blog/{post.slug}">{post.title}</a></p>
{/each}
9 changes: 9 additions & 0 deletions test/apps/export-multiple-entry/src/routes/blog/index.json.js
@@ -0,0 +1,9 @@
import posts from './_posts.js';

export function get(req, res) {
res.writeHead(200, {
'Content-Type': 'application/json'
});

res.end(JSON.stringify(posts));
}
12 changes: 12 additions & 0 deletions test/apps/export-multiple-entry/src/routes/boom/[a]/[b].svelte
@@ -0,0 +1,12 @@
<script context="module">
export function preload({ params }) {
return params;
}
</script>

<script>
export let a;
export let b;
</script>

<p>{a}/{b}</p>
15 changes: 15 additions & 0 deletions test/apps/export-multiple-entry/src/routes/boom/[a]/index.svelte
@@ -0,0 +1,15 @@
<script context="module">
export function preload({ params }) {
return params;
}
</script>

<script>
export let a;

const list = Array(20).fill().map((_, i) => i + 1);
</script>

{#each list as b}
<a href="boom/{a}/{b}">{a}/{b}</a>
{/each}
7 changes: 7 additions & 0 deletions test/apps/export-multiple-entry/src/routes/boom/index.svelte
@@ -0,0 +1,7 @@
<script>
const list = Array(20).fill().map((_, i) => i + 1);
</script>

{#each list as a}
<a href="boom/{a}">{a}</a>
{/each}
1 change: 1 addition & 0 deletions test/apps/export-multiple-entry/src/routes/contact.svelte
@@ -0,0 +1 @@
<h1>I am a contact page</h1>
8 changes: 8 additions & 0 deletions test/apps/export-multiple-entry/src/routes/index.svelte
@@ -0,0 +1,8 @@
<h1>Great success!</h1>

<a href="">empty anchor</a>
<a href="">empty anchor #2</a>
<a href="">empty anchor #3</a>
<a href="">empty anchor #4</a>
<a href>empty anchor #5</a>
<a>empty anchor #6</a>
13 changes: 13 additions & 0 deletions test/apps/export-multiple-entry/src/server.js
@@ -0,0 +1,13 @@
import sirv from 'sirv';
import polka from 'polka';
import * as sapper from '@sapper/server';

import { start, dev } from '../../common.js';

const app = polka()
.use(
sirv('static', { dev }),
sapper.middleware()
);

start(app);
82 changes: 82 additions & 0 deletions test/apps/export-multiple-entry/src/service-worker.js
@@ -0,0 +1,82 @@
import * as sapper from '@sapper/service-worker';

const ASSETS = `cache${sapper.timestamp}`;

// `shell` is an array of all the files generated by webpack,
// `files` is an array of everything in the `static` directory
const to_cache = sapper.shell.concat(sapper.files);
const cached = new Set(to_cache);

self.addEventListener('install', event => {
event.waitUntil(
caches
.open(ASSETS)
.then(cache => cache.addAll(to_cache))
.then(() => {
self.skipWaiting();
})
);
});

self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(async keys => {
// delete old caches
for (const key of keys) {
if (key !== ASSETS) await caches.delete(key);
}

self.clients.claim();
})
);
});

self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;

const url = new URL(event.request.url);

// don't try to handle e.g. data: URIs
if (!url.protocol.startsWith('http')) return;

// ignore dev server requests
if (url.hostname === self.location.hostname && url.port !== self.location.port) return;

// always serve assets and webpack-generated files from cache
if (url.host === self.location.host && cached.has(url.pathname)) {
event.respondWith(caches.match(event.request));
return;
}

// for pages, you might want to serve a shell `index.html` file,
// which Sapper has generated for you. It's not right for every
// app, but if it's right for yours then uncomment this section
/*
if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
event.respondWith(caches.match('/index.html'));
return;
}
*/

if (event.request.cache === 'only-if-cached') return;

// for everything else, try the network first, falling back to
// cache if the user is offline. (If the pages never change, you
// might prefer a cache-first approach to a network-first one.)
event.respondWith(
caches
.open(`offline${sapper.timestamp}`)
.then(async cache => {
try {
const response = await fetch(event.request);
cache.put(event.request, response.clone());
return response;
} catch(err) {
const response = await cache.match(event.request);
if (response) return response;

throw err;
}
})
);
});