diff --git a/package-lock.json b/package-lock.json
index 2b2b8cf7d..6c1660426 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1986,7 +1986,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -2007,12 +2008,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -2027,17 +2030,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -2154,7 +2160,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -2166,6 +2173,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -2180,6 +2188,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -2187,12 +2196,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -2211,6 +2222,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -2291,7 +2303,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -2303,6 +2316,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -2388,7 +2402,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -2424,6 +2439,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -2443,6 +2459,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -2486,12 +2503,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
@@ -2687,6 +2706,11 @@
"uglify-js": "3.4.x"
}
},
+ "http-link-header": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.0.2.tgz",
+ "integrity": "sha512-z6YOZ8ZEnejkcCWlGZzYXNa6i+ZaTfiTg3WhlV/YvnNya3W/RbX1bMVUMTuCrg/DrtTCQxaFCkXCz4FtLpcebg=="
+ },
"https-browserify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
diff --git a/package.json b/package.json
index 59048042f..41b28fb78 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
},
"dependencies": {
"html-minifier": "^3.5.21",
+ "http-link-header": "^1.0.2",
"shimport": "0.0.14",
"sourcemap-codec": "^1.4.4",
"string-hash": "^1.1.3"
diff --git a/src/api/export.ts b/src/api/export.ts
index cfdfe5ef3..793a10211 100644
--- a/src/api/export.ts
+++ b/src/api/export.ts
@@ -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';
import { rimraf, copy, mkdirp } from './utils/fs_utils';
type Opts = {
@@ -22,6 +23,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));
}
@@ -137,38 +144,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) {
- 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('',
+ ``)
+ }
+ });
+ 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 = / {error.message}{status}
+
+{post.title}
\ No newline at end of file
diff --git a/test/apps/export-webpack/src/routes/blog/[slug].json.js b/test/apps/export-webpack/src/routes/blog/[slug].json.js
new file mode 100644
index 000000000..66781ad28
--- /dev/null
+++ b/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' }));
+ }
+}
\ No newline at end of file
diff --git a/test/apps/export-webpack/src/routes/blog/_posts.js b/test/apps/export-webpack/src/routes/blog/_posts.js
new file mode 100644
index 000000000..d283aa6cf
--- /dev/null
+++ b/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' }
+];
\ No newline at end of file
diff --git a/test/apps/export-webpack/src/routes/blog/index.html b/test/apps/export-webpack/src/routes/blog/index.html
new file mode 100644
index 000000000..d12e4321d
--- /dev/null
+++ b/test/apps/export-webpack/src/routes/blog/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+blog
+
+{#each posts as post}
+
+{/each}
\ No newline at end of file
diff --git a/test/apps/export-webpack/src/routes/blog/index.json.js b/test/apps/export-webpack/src/routes/blog/index.json.js
new file mode 100644
index 000000000..0097f7793
--- /dev/null
+++ b/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));
+}
\ No newline at end of file
diff --git a/test/apps/export-webpack/src/routes/index.svelte b/test/apps/export-webpack/src/routes/index.svelte
new file mode 100644
index 000000000..cdeb5a8e1
--- /dev/null
+++ b/test/apps/export-webpack/src/routes/index.svelte
@@ -0,0 +1,4 @@
+Great success!
+
+blog
+empty anchor
\ No newline at end of file
diff --git a/test/apps/export-webpack/src/server.js b/test/apps/export-webpack/src/server.js
new file mode 100644
index 000000000..2c6932da6
--- /dev/null
+++ b/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);
+ });
diff --git a/test/apps/export-webpack/src/service-worker.js b/test/apps/export-webpack/src/service-worker.js
new file mode 100644
index 000000000..8adb97a43
--- /dev/null
+++ b/test/apps/export-webpack/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;
+ }
+ })
+ );
+});
diff --git a/test/apps/export-webpack/src/template.html b/test/apps/export-webpack/src/template.html
new file mode 100644
index 000000000..0eb1f3ba4
--- /dev/null
+++ b/test/apps/export-webpack/src/template.html
@@ -0,0 +1,14 @@
+
+
+