create API for exporting

This commit is contained in:
Rich Harris
2018-05-22 11:57:24 -04:00
parent 4f49fd8d5c
commit 0dbf75f100
3 changed files with 156 additions and 99 deletions

View File

@@ -1,106 +1,35 @@
import * as child_process from 'child_process';
import * as path from 'path';
import * as sander from 'sander';
import _exporter from '../api/export';
import * as colors from 'ansi-colors';
import cheerio from 'cheerio';
import URL from 'url-parse';
import fetch from 'node-fetch';
import * as ports from 'port-authority';
import prettyBytes from 'pretty-bytes';
import { minify_html } from '../api/utils/minify_html';
import { locations } from '../config';
export async function exporter(export_dir: string, { basepath = '' }) {
const build_dir = locations.dest();
export function exporter(export_dir: string, { basepath = '' }) {
return new Promise((fulfil, reject) => {
try {
const emitter = _exporter({
build: locations.dest(),
dest: export_dir,
basepath
});
export_dir = path.join(export_dir, basepath);
emitter.on('file', event => {
console.log(`${colors.bold.cyan(event.file)} ${colors.gray(`(${prettyBytes(event.size)})`)}`);
});
// Prep output directory
sander.rimrafSync(export_dir);
emitter.on('failure', event => {
console.log(`${colors.red(`> Received ${event.status} response when fetching ${event.pathname}`)}`);
});
sander.copydirSync('assets').to(export_dir);
sander.copydirSync(build_dir, 'client').to(export_dir, 'client');
emitter.on('error', event => {
reject(event.error);
});
if (sander.existsSync(build_dir, 'service-worker.js')) {
sander.copyFileSync(build_dir, 'service-worker.js').to(export_dir, 'service-worker.js');
}
if (sander.existsSync(build_dir, 'service-worker.js.map')) {
sander.copyFileSync(build_dir, 'service-worker.js.map').to(export_dir, 'service-worker.js.map');
}
const port = await ports.find(3000);
const origin = `http://localhost:${port}`;
const proc = child_process.fork(path.resolve(`${build_dir}/server.js`), [], {
cwd: process.cwd(),
env: Object.assign({
PORT: port,
NODE_ENV: 'production',
SAPPER_DEST: build_dir,
SAPPER_EXPORT: 'true'
}, process.env)
emitter.on('done', event => {
fulfil();
});
} catch (err) {
console.log(`${colors.bold.red(`> ${err.message}`)}`);
process.exit(1);
}
});
const seen = new Set();
const saved = new Set();
proc.on('message', message => {
if (!message.__sapper__) return;
let file = new URL(message.url, origin).pathname.slice(1);
let { body } = message;
if (saved.has(file)) return;
saved.add(file);
const is_html = message.type === 'text/html';
if (is_html) {
file = file === '' ? 'index.html' : `${file}/index.html`;
body = minify_html(body);
}
console.log(`${colors.bold.cyan(file)} ${colors.gray(`(${prettyBytes(body.length)})`)}`);
sander.writeFileSync(export_dir, file, body);
});
async function handle(url: URL) {
const r = await fetch(url.href);
const range = ~~(r.status / 100);
if (range >= 4) {
console.log(`${colors.red(`> Received ${r.status} response when fetching ${url.pathname}`)}`);
return;
}
if (range === 2) {
if (r.headers.get('Content-Type') === 'text/html') {
const body = await r.text();
const $ = cheerio.load(body);
const urls: URL[] = [];
const base = new URL($('base').attr('href') || '/', url.href);
$('a[href]').each((i: number, $a) => {
const url = new URL($a.attribs.href, base.href);
if (url.origin === origin && !seen.has(url.pathname)) {
seen.add(url.pathname);
urls.push(url);
}
});
for (const url of urls) {
await handle(url);
}
}
}
}
return ports.wait(port)
.then(() => handle(new URL(`/${basepath}`, origin))) // TODO all static routes
.then(() => proc.kill());
}
}