mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-11 19:04:30 +00:00
fix exporting
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@ yarn.lock
|
||||
node_modules
|
||||
cypress/screenshots
|
||||
test/app/.sapper
|
||||
test/app/app/manifest
|
||||
runtime.js
|
||||
runtime.js.map
|
||||
cli.js
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
proc.on('message', message => {
|
||||
if (message.__sapper__) return;
|
||||
if (handler) handler(message);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user