mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-14 12:04:39 +00:00
fix exporting
This commit is contained in:
@@ -3,9 +3,7 @@ import * as path from 'path';
|
||||
import * as sander from 'sander';
|
||||
import express from 'express';
|
||||
import cheerio from 'cheerio';
|
||||
import fetch from 'node-fetch';
|
||||
import URL from 'url-parse';
|
||||
import { create_assets } from 'sapper/core.js';
|
||||
|
||||
const { OUTPUT_DIR = 'dist' } = process.env;
|
||||
|
||||
@@ -15,61 +13,50 @@ function read_json(file: string) {
|
||||
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
|
||||
sander.rimrafSync(OUTPUT_DIR);
|
||||
|
||||
sander.copydirSync('assets').to(OUTPUT_DIR);
|
||||
sander.copydirSync(dest, 'client').to(OUTPUT_DIR, 'client');
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
sander.copydirSync(dir, 'client').to(OUTPUT_DIR, 'client');
|
||||
sander.copyFileSync(dir, 'service-worker.js').to(OUTPUT_DIR, 'service-worker.js');
|
||||
|
||||
const port = await require('get-port')(3000);
|
||||
|
||||
const origin = `http://localhost:${port}`;
|
||||
|
||||
global.fetch = (url, opts) => {
|
||||
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`), [], {
|
||||
const proc = child_process.fork(path.resolve(`${dir}/server.js`), [], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
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 });
|
||||
|
||||
const seen = new Set();
|
||||
|
||||
function handle(url) {
|
||||
function handle(url: URL) {
|
||||
if (url.origin !== origin) 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)
|
||||
.then(r => {
|
||||
save(r);
|
||||
|
||||
if (r.headers.get('Content-Type') === 'text/html') {
|
||||
return r.text().then(body => {
|
||||
return r.text().then((body: string) => {
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -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}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,9 +35,11 @@ export default function create_templates() {
|
||||
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 body = '';
|
||||
|
||||
function stream_inner(): Promise<void> {
|
||||
if (i >= template.length) {
|
||||
return;
|
||||
@@ -46,11 +48,26 @@ export default function create_templates() {
|
||||
const start = template.indexOf('%sapper', i);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
res.write(template.slice(i, start));
|
||||
const chunk = template.slice(i, start);
|
||||
body += chunk;
|
||||
res.write(chunk);
|
||||
|
||||
const end = template.indexOf('%', start + 1);
|
||||
if (end === -1) {
|
||||
@@ -61,8 +78,9 @@ export default function create_templates() {
|
||||
const match = /sapper\.(\w+)/.exec(tag);
|
||||
if (!match || !(match[1] in data)) throw new Error(`Bad template`); // TODO ditto
|
||||
|
||||
return Promise.resolve(data[match[1]]).then(datamatch => {
|
||||
res.write(datamatch);
|
||||
return Promise.resolve(data[match[1]]).then(chunk => {
|
||||
body += chunk;
|
||||
res.write(chunk);
|
||||
i = end + 1;
|
||||
return stream_inner();
|
||||
});
|
||||
|
||||
@@ -11,5 +11,5 @@ export type Route = {
|
||||
|
||||
export type Template = {
|
||||
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 template.stream(res, {
|
||||
return template.stream(req, res, {
|
||||
scripts: promise.then(({ serialized }) => {
|
||||
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);
|
||||
|
||||
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 handler = mod[method_export];
|
||||
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);
|
||||
} else {
|
||||
// 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
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user