mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-20 22:35:09 +00:00
fix exporting
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@ yarn.lock
|
|||||||
node_modules
|
node_modules
|
||||||
cypress/screenshots
|
cypress/screenshots
|
||||||
test/app/.sapper
|
test/app/.sapper
|
||||||
|
test/app/app/manifest
|
||||||
runtime.js
|
runtime.js
|
||||||
runtime.js.map
|
runtime.js.map
|
||||||
cli.js
|
cli.js
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import * as path from 'path';
|
|||||||
import * as sander from 'sander';
|
import * as sander from 'sander';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cheerio from 'cheerio';
|
import cheerio from 'cheerio';
|
||||||
import fetch from 'node-fetch';
|
|
||||||
import URL from 'url-parse';
|
import URL from 'url-parse';
|
||||||
import { create_assets } from 'sapper/core.js';
|
|
||||||
|
|
||||||
const { OUTPUT_DIR = 'dist' } = process.env;
|
const { OUTPUT_DIR = 'dist' } = process.env;
|
||||||
|
|
||||||
@@ -15,61 +13,50 @@ function read_json(file: string) {
|
|||||||
return JSON.parse(sander.readFileSync(file, { encoding: 'utf-8' }));
|
return JSON.parse(sander.readFileSync(file, { encoding: 'utf-8' }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function exporter({ src, dest }) { // TODO dest is a terrible name in this context
|
export default async function exporter(dir: string) { // dir === '.sapper'
|
||||||
// Prep output directory
|
// Prep output directory
|
||||||
sander.rimrafSync(OUTPUT_DIR);
|
sander.rimrafSync(OUTPUT_DIR);
|
||||||
|
|
||||||
sander.copydirSync('assets').to(OUTPUT_DIR);
|
sander.copydirSync('assets').to(OUTPUT_DIR);
|
||||||
sander.copydirSync(dest, 'client').to(OUTPUT_DIR, 'client');
|
sander.copydirSync(dir, 'client').to(OUTPUT_DIR, 'client');
|
||||||
|
sander.copyFileSync(dir, 'service-worker.js').to(OUTPUT_DIR, 'service-worker.js');
|
||||||
// Intercept server route fetches
|
|
||||||
function save(res) {
|
|
||||||
res = res.clone();
|
|
||||||
|
|
||||||
return res.text().then(body => {
|
|
||||||
const { pathname } = new URL(res.url);
|
|
||||||
let dest = OUTPUT_DIR + pathname;
|
|
||||||
|
|
||||||
const type = res.headers.get('Content-Type');
|
|
||||||
if (type && type.startsWith('text/html')) dest += '/index.html';
|
|
||||||
|
|
||||||
sander.writeFileSync(dest, body);
|
|
||||||
|
|
||||||
return body;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const port = await require('get-port')(3000);
|
const port = await require('get-port')(3000);
|
||||||
|
|
||||||
const origin = `http://localhost:${port}`;
|
const origin = `http://localhost:${port}`;
|
||||||
|
|
||||||
global.fetch = (url, opts) => {
|
const proc = child_process.fork(path.resolve(`${dir}/server.js`), [], {
|
||||||
if (url[0] === '/') {
|
|
||||||
url = `http://localhost:${port}${url}`;
|
|
||||||
|
|
||||||
return fetch(url, opts)
|
|
||||||
.then(r => {
|
|
||||||
save(r);
|
|
||||||
return r;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch(url, opts);
|
|
||||||
};
|
|
||||||
|
|
||||||
const proc = child_process.fork(path.resolve(`${dest}/server.js`), [], {
|
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
env: {
|
env: {
|
||||||
PORT: port,
|
PORT: port,
|
||||||
NODE_ENV: 'production'
|
NODE_ENV: 'production',
|
||||||
|
SAPPER_EXPORT: 'true'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const seen = new Set();
|
||||||
|
const saved = new Set();
|
||||||
|
|
||||||
|
proc.on('message', message => {
|
||||||
|
if (!message.__sapper__) return;
|
||||||
|
|
||||||
|
const url = new URL(message.url, origin);
|
||||||
|
|
||||||
|
if (saved.has(url.pathname)) return;
|
||||||
|
saved.add(url.pathname);
|
||||||
|
|
||||||
|
if (message.type === 'text/html') {
|
||||||
|
const dest = `${OUTPUT_DIR}/${url.pathname}/index.html`;
|
||||||
|
sander.writeFileSync(dest, message.body);
|
||||||
|
} else {
|
||||||
|
const dest = `${OUTPUT_DIR}/${url.pathname}`;
|
||||||
|
sander.writeFileSync(dest, message.body);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await require('wait-port')({ port });
|
await require('wait-port')({ port });
|
||||||
|
|
||||||
const seen = new Set();
|
function handle(url: URL) {
|
||||||
|
|
||||||
function handle(url) {
|
|
||||||
if (url.origin !== origin) return;
|
if (url.origin !== origin) return;
|
||||||
|
|
||||||
if (seen.has(url.pathname)) return;
|
if (seen.has(url.pathname)) return;
|
||||||
@@ -77,14 +64,12 @@ export default async function exporter({ src, dest }) { // TODO dest is a terrib
|
|||||||
|
|
||||||
return fetch(url.href)
|
return fetch(url.href)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
save(r);
|
|
||||||
|
|
||||||
if (r.headers.get('Content-Type') === 'text/html') {
|
if (r.headers.get('Content-Type') === 'text/html') {
|
||||||
return r.text().then(body => {
|
return r.text().then((body: string) => {
|
||||||
const $ = cheerio.load(body);
|
const $ = cheerio.load(body);
|
||||||
const hrefs = [];
|
const hrefs: string[] = [];
|
||||||
|
|
||||||
$('a[href]').each((i, $a) => {
|
$('a[href]').each((i: number, $a) => {
|
||||||
hrefs.push($a.attribs.href);
|
hrefs.push($a.attribs.href);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,7 +79,7 @@ export default async function exporter({ src, dest }) { // TODO dest is a terrib
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err: Error) => {
|
||||||
console.error(`Error rendering ${url.pathname}: ${err.message}`);
|
console.error(`Error rendering ${url.pathname}: ${err.message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ export default function create_templates() {
|
|||||||
return key in data ? data[key] : '';
|
return key in data ? data[key] : '';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
stream: (res: any, data: Record<string, string | Promise<string>>) => {
|
stream: (req: any, res: any, data: Record<string, string | Promise<string>>) => {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
|
let body = '';
|
||||||
|
|
||||||
function stream_inner(): Promise<void> {
|
function stream_inner(): Promise<void> {
|
||||||
if (i >= template.length) {
|
if (i >= template.length) {
|
||||||
return;
|
return;
|
||||||
@@ -46,11 +48,26 @@ export default function create_templates() {
|
|||||||
const start = template.indexOf('%sapper', i);
|
const start = template.indexOf('%sapper', i);
|
||||||
|
|
||||||
if (start === -1) {
|
if (start === -1) {
|
||||||
res.end(template.slice(i));
|
const chunk = template.slice(i);
|
||||||
|
body += chunk;
|
||||||
|
res.end(chunk);
|
||||||
|
|
||||||
|
if (process.send) {
|
||||||
|
process.send({
|
||||||
|
__sapper__: true,
|
||||||
|
url: req.url,
|
||||||
|
method: req.method,
|
||||||
|
type: 'text/html',
|
||||||
|
body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.write(template.slice(i, start));
|
const chunk = template.slice(i, start);
|
||||||
|
body += chunk;
|
||||||
|
res.write(chunk);
|
||||||
|
|
||||||
const end = template.indexOf('%', start + 1);
|
const end = template.indexOf('%', start + 1);
|
||||||
if (end === -1) {
|
if (end === -1) {
|
||||||
@@ -61,8 +78,9 @@ export default function create_templates() {
|
|||||||
const match = /sapper\.(\w+)/.exec(tag);
|
const match = /sapper\.(\w+)/.exec(tag);
|
||||||
if (!match || !(match[1] in data)) throw new Error(`Bad template`); // TODO ditto
|
if (!match || !(match[1] in data)) throw new Error(`Bad template`); // TODO ditto
|
||||||
|
|
||||||
return Promise.resolve(data[match[1]]).then(datamatch => {
|
return Promise.resolve(data[match[1]]).then(chunk => {
|
||||||
res.write(datamatch);
|
body += chunk;
|
||||||
|
res.write(chunk);
|
||||||
i = end + 1;
|
i = end + 1;
|
||||||
return stream_inner();
|
return stream_inner();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ export type Route = {
|
|||||||
|
|
||||||
export type Template = {
|
export type Template = {
|
||||||
render: (data: Record<string, string>) => string;
|
render: (data: Record<string, string>) => string;
|
||||||
stream: (res, data: Record<string, string | Promise<string>>) => void;
|
stream: (req, res, data: Record<string, string | Promise<string>>) => void;
|
||||||
};
|
};
|
||||||
@@ -112,7 +112,7 @@ function get_route_handler(chunks: Record<string, string>, get_assets: () => Ass
|
|||||||
return { rendered: mod.render(data), serialized };
|
return { rendered: mod.render(data), serialized };
|
||||||
});
|
});
|
||||||
|
|
||||||
return template.stream(res, {
|
return template.stream(req, res, {
|
||||||
scripts: promise.then(({ serialized }) => {
|
scripts: promise.then(({ serialized }) => {
|
||||||
const main = `<script src='/client/${chunks.main}'></script>`;
|
const main = `<script src='/client/${chunks.main}'></script>`;
|
||||||
|
|
||||||
@@ -137,6 +137,17 @@ function get_route_handler(chunks: Record<string, string>, get_assets: () => Ass
|
|||||||
});
|
});
|
||||||
|
|
||||||
res.end(page);
|
res.end(page);
|
||||||
|
|
||||||
|
if (process.send) {
|
||||||
|
process.send({
|
||||||
|
__sapper__: true,
|
||||||
|
url: req.url,
|
||||||
|
method: req.method,
|
||||||
|
status: 200,
|
||||||
|
type: 'text/html',
|
||||||
|
body: page
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +158,37 @@ function get_route_handler(chunks: Record<string, string>, get_assets: () => Ass
|
|||||||
const method_export = method === 'delete' ? 'del' : method;
|
const method_export = method === 'delete' ? 'del' : method;
|
||||||
const handler = mod[method_export];
|
const handler = mod[method_export];
|
||||||
if (handler) {
|
if (handler) {
|
||||||
|
if (process.env.SAPPER_EXPORT) {
|
||||||
|
const { write, end, setHeader } = res;
|
||||||
|
const chunks: any[] = [];
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
|
||||||
|
// intercept data so that it can be exported
|
||||||
|
res.write = function(chunk: any) {
|
||||||
|
chunks.push(new Buffer(chunk));
|
||||||
|
write.apply(res, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
res.setHeader = function(name: string, value: string) {
|
||||||
|
headers[name.toLowerCase()] = value;
|
||||||
|
setHeader.apply(res, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
res.end = function(chunk?: any) {
|
||||||
|
if (chunk) chunks.push(new Buffer(chunk));
|
||||||
|
end.apply(res, arguments);
|
||||||
|
|
||||||
|
process.send({
|
||||||
|
__sapper__: true,
|
||||||
|
url: req.url,
|
||||||
|
method: req.method,
|
||||||
|
status: res.statusCode,
|
||||||
|
type: headers['content-type'],
|
||||||
|
body: Buffer.concat(chunks).toString()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
handler(req, res, next);
|
handler(req, res, next);
|
||||||
} else {
|
} else {
|
||||||
// no matching handler for method — 404
|
// no matching handler for method — 404
|
||||||
@@ -168,6 +210,8 @@ function get_route_handler(chunks: Record<string, string>, get_assets: () => Ass
|
|||||||
// no matching route — 404
|
// no matching route — 404
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
res.statusCode = 500;
|
res.statusCode = 500;
|
||||||
res.setHeader('Content-Type', 'text/html');
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
// This file is generated by Sapper — do not edit it!
|
|
||||||
export const routes = [
|
|
||||||
{ pattern: /^\/?$/, params: () => ({}), load: () => import(/* webpackChunkName: "_" */ '../../routes/index.html') },
|
|
||||||
{ pattern: /^\/4xx\/?$/, params: () => ({}), load: () => import(/* webpackChunkName: "_4xx" */ '../../routes/4xx.html') },
|
|
||||||
{ pattern: /^\/about\/?$/, params: () => ({}), load: () => import(/* webpackChunkName: "about" */ '../../routes/about.html') },
|
|
||||||
{ pattern: /^\/show-url\/?$/, params: () => ({}), load: () => import(/* webpackChunkName: "show_url" */ '../../routes/show-url.html') },
|
|
||||||
{ pattern: /^\/slow-preload\/?$/, params: () => ({}), load: () => import(/* webpackChunkName: "slow_preload" */ '../../routes/slow-preload.html') },
|
|
||||||
{ pattern: /^\/delete-test\/?$/, params: () => ({}), load: () => import(/* webpackChunkName: "delete_test" */ '../../routes/delete-test.html') },
|
|
||||||
{ pattern: /^\/5xx\/?$/, params: () => ({}), load: () => import(/* webpackChunkName: "_5xx" */ '../../routes/5xx.html') },
|
|
||||||
{ pattern: /^\/blog\/?$/, params: () => ({}), load: () => import(/* webpackChunkName: "blog" */ '../../routes/blog/index.html') },
|
|
||||||
{ pattern: /^\/blog(?:\/([^\/]+))?\/?$/, params: match => ({ slug: match[1] }), load: () => import(/* webpackChunkName: "blog_$slug$" */ '../../routes/blog/[slug].html') }
|
|
||||||
];
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// This file is generated by Sapper — do not edit it!
|
|
||||||
import _ from '../../routes/index.html';
|
|
||||||
import _4xx from '../../routes/4xx.html';
|
|
||||||
import about from '../../routes/about.html';
|
|
||||||
import show_url from '../../routes/show-url.html';
|
|
||||||
import slow_preload from '../../routes/slow-preload.html';
|
|
||||||
import delete_test from '../../routes/delete-test.html';
|
|
||||||
import _5xx from '../../routes/5xx.html';
|
|
||||||
import blog from '../../routes/blog/index.html';
|
|
||||||
import * as api_blog_contents from '../../routes/api/blog/contents.js';
|
|
||||||
import * as api_delete_$id$ from '../../routes/api/delete/[id].js';
|
|
||||||
import * as api_blog_$slug$ from '../../routes/api/blog/[slug].js';
|
|
||||||
import blog_$slug$ from '../../routes/blog/[slug].html';
|
|
||||||
|
|
||||||
export const routes = [
|
|
||||||
{ id: '_', type: 'page', pattern: /^\/?$/, params: () => ({}), module: _ },
|
|
||||||
{ id: '_4xx', type: 'page', pattern: /^\/4xx\/?$/, params: () => ({}), module: _4xx },
|
|
||||||
{ id: 'about', type: 'page', pattern: /^\/about\/?$/, params: () => ({}), module: about },
|
|
||||||
{ id: 'show_url', type: 'page', pattern: /^\/show-url\/?$/, params: () => ({}), module: show_url },
|
|
||||||
{ id: 'slow_preload', type: 'page', pattern: /^\/slow-preload\/?$/, params: () => ({}), module: slow_preload },
|
|
||||||
{ id: 'delete_test', type: 'page', pattern: /^\/delete-test\/?$/, params: () => ({}), module: delete_test },
|
|
||||||
{ id: '_5xx', type: 'page', pattern: /^\/5xx\/?$/, params: () => ({}), module: _5xx },
|
|
||||||
{ id: 'blog', type: 'page', pattern: /^\/blog\/?$/, params: () => ({}), module: blog },
|
|
||||||
{ id: 'api_blog_contents', type: 'route', pattern: /^\/api\/blog\/contents\/?$/, params: () => ({}), module: api_blog_contents },
|
|
||||||
{ id: 'api_delete_$id$', type: 'route', pattern: /^\/api\/delete(?:\/([^\/]+))?\/?$/, params: match => ({ id: match[1] }), module: api_delete_$id$ },
|
|
||||||
{ id: 'api_blog_$slug$', type: 'route', pattern: /^\/api\/blog(?:\/([^\/]+))?\/?$/, params: match => ({ slug: match[1] }), module: api_blog_$slug$ },
|
|
||||||
{ id: 'blog_$slug$', type: 'page', pattern: /^\/blog(?:\/([^\/]+))?\/?$/, params: match => ({ slug: match[1] }), module: blog_$slug$ }
|
|
||||||
];
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
|
|
||||||
export const timestamp = 1518800295364;
|
|
||||||
|
|
||||||
export const assets = [
|
|
||||||
"favicon.png",
|
|
||||||
"global.css",
|
|
||||||
"great-success.png",
|
|
||||||
"manifest.json",
|
|
||||||
"svelte-logo-192.png",
|
|
||||||
"svelte-logo-512.png"
|
|
||||||
];
|
|
||||||
|
|
||||||
export const shell = [
|
|
||||||
"/client/_.0.3a37f8afa58c59f4bdf9.js",
|
|
||||||
"/client/blog.1.3a37f8afa58c59f4bdf9.js",
|
|
||||||
"/client/blog_$slug$.2.3a37f8afa58c59f4bdf9.js",
|
|
||||||
"/client/about.3.3a37f8afa58c59f4bdf9.js",
|
|
||||||
"/client/_5xx.4.3a37f8afa58c59f4bdf9.js",
|
|
||||||
"/client/_4xx.5.3a37f8afa58c59f4bdf9.js",
|
|
||||||
"/client/slow_preload.6.3a37f8afa58c59f4bdf9.js",
|
|
||||||
"/client/show_url.7.3a37f8afa58c59f4bdf9.js",
|
|
||||||
"/client/delete_test.8.3a37f8afa58c59f4bdf9.js",
|
|
||||||
"/client/main.3a37f8afa58c59f4bdf9.js"
|
|
||||||
];
|
|
||||||
|
|
||||||
export const routes = [
|
|
||||||
{ pattern: /^\/?$/ },
|
|
||||||
{ pattern: /^\/4xx\/?$/ },
|
|
||||||
{ pattern: /^\/about\/?$/ },
|
|
||||||
{ pattern: /^\/show-url\/?$/ },
|
|
||||||
{ pattern: /^\/slow-preload\/?$/ },
|
|
||||||
{ pattern: /^\/delete-test\/?$/ },
|
|
||||||
{ pattern: /^\/5xx\/?$/ },
|
|
||||||
{ pattern: /^\/blog\/?$/ },
|
|
||||||
{ pattern: /^\/blog(?:\/([^\/]+))?\/?$/ }
|
|
||||||
];
|
|
||||||
|
|
||||||
@@ -60,6 +60,7 @@ function run(env) {
|
|||||||
let handler;
|
let handler;
|
||||||
|
|
||||||
proc.on('message', message => {
|
proc.on('message', message => {
|
||||||
|
if (message.__sapper__) return;
|
||||||
if (handler) handler(message);
|
if (handler) handler(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user