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

fix: add link rel=preload for exported sites #568

Merged
merged 3 commits into from Feb 17, 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
48 changes: 36 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -19,6 +19,7 @@
},
"dependencies": {
"html-minifier": "^3.5.21",
"http-link-header": "^1.0.2",
"shimport": "0.0.14",
"source-map-support": "^0.5.10",
"sourcemap-codec": "^1.4.4",
Expand Down
54 changes: 36 additions & 18 deletions src/api/export.ts
Expand Up @@ -9,6 +9,7 @@ import clean_html from './utils/clean_html';
import minify_html from './utils/minify_html';
import Deferred from './utils/Deferred';
import { noop } from './utils/noop';
import { parse as parseLinkHeader } from 'http-link-header';

type Opts = {
build_dir?: string,
Expand All @@ -21,6 +22,12 @@ type Opts = {
onfile?: ({ file, size, status }: { file: string, size: number, status: number }) => void;
};

type Ref = {
uri: string,
rel: string,
as: string
};

function resolve(from: string, to: string) {
return url.parse(url.resolve(from, to));
}
Expand Down Expand Up @@ -139,38 +146,49 @@ async function _export({
clearTimeout(the_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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hiding whitespace will make the diff below a lot easier to read.

if (type === 'text/html' && pathname !== '/service-worker-index.html') {
const cleaned = clean_html(body);
if (type === 'text/html') {
// parse link rel=preload headers and embed them in the HTML
let link = parseLinkHeader(r.headers.get('Link') || '');
link.refs.forEach((ref: Ref) => {
if (ref.rel === 'preload') {
body = body.replace('</head>',
`<link rel="preload" as=${JSON.stringify(ref.as)} href=${JSON.stringify(ref.uri)}></head>`)
}
});
if (pathname !== '/service-worker-index.html') {
const cleaned = clean_html(body);

const q = yootils.queue(8);
let promise;
const q = yootils.queue(8);
let promise;

const base_match = /<base ([\s\S]+?)>/m.exec(cleaned);
const base_href = base_match && get_href(base_match[1]);
const base = resolve(url.href, base_href);
const base_match = /<base ([\s\S]+?)>/m.exec(cleaned);
const base_href = base_match && get_href(base_match[1]);
const base = resolve(url.href, base_href);

let match;
let pattern = /<a ([\s\S]+?)>/gm;
let match;
let pattern = /<a ([\s\S]+?)>/gm;

while (match = pattern.exec(cleaned)) {
const attrs = match[1];
const href = get_href(attrs);
while (match = pattern.exec(cleaned)) {
const attrs = match[1];
const href = get_href(attrs);

if (href) {
const url = resolve(base.href, href);
if (href) {
const url = resolve(base.href, href);

if (url.protocol === protocol && url.host === host) {
promise = q.add(() => handle(url));
if (url.protocol === protocol && url.host === host) {
promise = q.add(() => handle(url));
}
}
}
}

await promise;
await promise;
}
}
}

Expand Down
9 changes: 9 additions & 0 deletions test/apps/export-webpack/src/client.js
@@ -0,0 +1,9 @@
import * as sapper from '@sapper/app';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I copy-pasta'd the test/apps/export project into test/apps/export-webpack, changing only rollup.config.js by replacing it with webpack.config.js.


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-webpack/src/routes/_error.svelte
@@ -0,0 +1,3 @@
<h1>{status}</h1>

<p>{error.message}</p>
13 changes: 13 additions & 0 deletions test/apps/export-webpack/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-webpack/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-webpack/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-webpack/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-webpack/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));
}
4 changes: 4 additions & 0 deletions test/apps/export-webpack/src/routes/index.svelte
@@ -0,0 +1,4 @@
<h1>Great success!</h1>

<a href="blog">blog</a>
<a href="">empty anchor</a>
15 changes: 15 additions & 0 deletions test/apps/export-webpack/src/server.js
@@ -0,0 +1,15 @@
import sirv from 'sirv';
import polka from 'polka';
import * as sapper from '@sapper/server';

const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';

polka()
.use(
sirv('static', { dev }),
sapper.middleware()
)
.listen(PORT, err => {
if (err) console.log('error', err);
});