mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-18 21:45:12 +00:00
Support being mounted on a path — fixes #180
This commit is contained in:
@@ -49,7 +49,8 @@ prog.command('start [dir]')
|
||||
|
||||
prog.command('export [dest]')
|
||||
.describe('Export your app as static files (if possible)')
|
||||
.action(async (dest = 'export') => {
|
||||
.option('--basepath', 'Specify a base path')
|
||||
.action(async (dest = 'export', opts: { basepath?: string }) => {
|
||||
console.log(`> Building...`);
|
||||
|
||||
process.env.NODE_ENV = 'production';
|
||||
@@ -63,7 +64,7 @@ prog.command('export [dest]')
|
||||
console.error(`\n> Built in ${elapsed(start)}. Crawling site...`);
|
||||
|
||||
const { exporter } = await import('./cli/export');
|
||||
await exporter(dest);
|
||||
await exporter(dest, opts);
|
||||
console.error(`\n> Finished in ${elapsed(start)}. Type ${clorox.bold.cyan(`npx serve ${dest}`)} to run the app.`);
|
||||
} catch (err) {
|
||||
console.error(err ? err.details || err.stack || err.message || err : 'Unknown error');
|
||||
|
||||
@@ -34,7 +34,7 @@ export async function build() {
|
||||
if (serviceworker) {
|
||||
create_serviceworker_manifest({
|
||||
routes,
|
||||
client_files: client_stats.toJson().assets.map((chunk: { name: string }) => `/client/${chunk.name}`)
|
||||
client_files: client_stats.toJson().assets.map((chunk: { name: string }) => `client/${chunk.name}`)
|
||||
});
|
||||
|
||||
serviceworker_stats = await compile(serviceworker);
|
||||
|
||||
@@ -259,7 +259,7 @@ export async function dev(opts: { port: number, open: boolean }) {
|
||||
fs.writeFileSync(path.join(dir, 'client_info.json'), JSON.stringify(info, null, ' '));
|
||||
deferreds.client.fulfil();
|
||||
|
||||
const client_files = info.assets.map((chunk: { name: string }) => `/client/${chunk.name}`);
|
||||
const client_files = info.assets.map((chunk: { name: string }) => `client/${chunk.name}`);
|
||||
|
||||
deferreds.server.promise.then(() => {
|
||||
hot_update_server.send({
|
||||
|
||||
@@ -10,9 +10,11 @@ import prettyBytes from 'pretty-bytes';
|
||||
import { minify_html } from './utils/minify_html';
|
||||
import { locations } from '../config';
|
||||
|
||||
export async function exporter(export_dir: string) {
|
||||
export async function exporter(export_dir: string, { basepath = '' }) {
|
||||
const build_dir = locations.dest();
|
||||
|
||||
export_dir = path.join(export_dir, basepath);
|
||||
|
||||
// Prep output directory
|
||||
sander.rimrafSync(export_dir);
|
||||
|
||||
@@ -58,38 +60,43 @@ export async function exporter(export_dir: string) {
|
||||
|
||||
console.log(`${clorox.bold.cyan(file)} ${clorox.gray(`(${prettyBytes(body.length)})`)}`);
|
||||
|
||||
sander.writeFileSync(`${export_dir}/${file}`, body);
|
||||
sander.writeFileSync(export_dir, file, body);
|
||||
});
|
||||
|
||||
function handle(url: URL) {
|
||||
if (url.origin !== origin) return;
|
||||
async function handle(url: URL) {
|
||||
const r = await fetch(url.href);
|
||||
const range = ~~(r.status / 100);
|
||||
|
||||
if (seen.has(url.pathname)) return;
|
||||
seen.add(url.pathname);
|
||||
if (range >= 4) {
|
||||
console.log(`${clorox.red(`> Received ${r.status} response when fetching ${url.pathname}`)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return fetch(url.href)
|
||||
.then(r => {
|
||||
if (r.headers.get('Content-Type') === 'text/html') {
|
||||
return r.text().then((body: string) => {
|
||||
const $ = cheerio.load(body);
|
||||
const hrefs: string[] = [];
|
||||
if (range === 2) {
|
||||
if (r.headers.get('Content-Type') === 'text/html') {
|
||||
const body = await r.text();
|
||||
const $ = cheerio.load(body);
|
||||
const urls: URL[] = [];
|
||||
|
||||
$('a[href]').each((i: number, $a) => {
|
||||
hrefs.push($a.attribs.href);
|
||||
});
|
||||
const base = new URL($('base').attr('href') || '/', url.href);
|
||||
|
||||
return hrefs.reduce((promise, href) => {
|
||||
return promise.then(() => handle(new URL(href, url.href)));
|
||||
}, Promise.resolve());
|
||||
});
|
||||
$('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);
|
||||
}
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
console.log(`${clorox.red(`> Error rendering ${url.pathname}: ${err.message}`)}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ports.wait(port)
|
||||
.then(() => handle(new URL(origin))) // TODO all static routes
|
||||
.then(() => handle(new URL(`/${basepath}`, origin))) // TODO all static routes
|
||||
.then(() => proc.kill());
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { resolve } from 'url';
|
||||
import { ClientRequest, ServerResponse } from 'http';
|
||||
import mkdirp from 'mkdirp';
|
||||
import rimraf from 'rimraf';
|
||||
@@ -46,7 +47,16 @@ export default function middleware({ routes }: {
|
||||
|
||||
const middleware = compose_handlers([
|
||||
(req: Req, res: ServerResponse, next: () => void) => {
|
||||
req.pathname = req.url.replace(/\?.*/, '');
|
||||
if (req.baseUrl === undefined) {
|
||||
req.baseUrl = req.originalUrl
|
||||
? req.originalUrl.slice(0, -req.url.length)
|
||||
: '';
|
||||
}
|
||||
|
||||
if (req.path === undefined) {
|
||||
req.path = req.url.replace(/\?.*/, '');
|
||||
}
|
||||
|
||||
next();
|
||||
},
|
||||
|
||||
@@ -77,8 +87,8 @@ function serve({ prefix, pathname, cache_control }: {
|
||||
cache_control: string
|
||||
}) {
|
||||
const filter = pathname
|
||||
? (req: Req) => req.pathname === pathname
|
||||
: (req: Req) => req.pathname.startsWith(prefix);
|
||||
? (req: Req) => req.path === pathname
|
||||
: (req: Req) => req.path.startsWith(prefix);
|
||||
|
||||
const output = locations.dest();
|
||||
|
||||
@@ -90,10 +100,10 @@ function serve({ prefix, pathname, cache_control }: {
|
||||
|
||||
return (req: Req, res: ServerResponse, next: () => void) => {
|
||||
if (filter(req)) {
|
||||
const type = lookup(req.pathname);
|
||||
const type = lookup(req.path);
|
||||
|
||||
try {
|
||||
const data = read(req.pathname.slice(1));
|
||||
const data = read(req.path.slice(1));
|
||||
|
||||
res.setHeader('Content-Type', type);
|
||||
res.setHeader('Cache-Control', cache_control);
|
||||
@@ -116,7 +126,7 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
|
||||
: (str => () => str)(fs.readFileSync(`${locations.dest()}/template.html`, 'utf-8'));
|
||||
|
||||
function handle_route(route: RouteObject, req: Req, res: ServerResponse) {
|
||||
req.params = route.params(route.pattern.exec(req.pathname));
|
||||
req.params = route.params(route.pattern.exec(req.path));
|
||||
|
||||
const mod = route.module;
|
||||
|
||||
@@ -127,7 +137,7 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
|
||||
// TODO detect other stuff we can preload? images, CSS, fonts?
|
||||
const link = []
|
||||
.concat(chunks.main, chunks[route.id])
|
||||
.map(file => `</client/${file}>;rel="preload";as="script"`)
|
||||
.map(file => `<${req.baseUrl}/client/${file}>;rel="preload";as="script"`)
|
||||
.join(', ');
|
||||
|
||||
res.setHeader('Link', link);
|
||||
@@ -151,7 +161,7 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
|
||||
}).then(preloaded => {
|
||||
if (redirect) {
|
||||
res.statusCode = redirect.statusCode;
|
||||
res.setHeader('Location', redirect.location);
|
||||
res.setHeader('Location', `${req.baseUrl}/${redirect.location}`);
|
||||
res.end();
|
||||
|
||||
return;
|
||||
@@ -169,13 +179,22 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
|
||||
|
||||
let scripts = []
|
||||
.concat(chunks.main) // chunks main might be an array. it might not! thanks, webpack
|
||||
.map(file => `<script src='/client/${file}'></script>`)
|
||||
.map(file => `<script src='${req.baseUrl}/client/${file}'></script>`)
|
||||
.join('');
|
||||
|
||||
scripts = `<script>__SAPPER__ = { preloaded: ${serialized} };</script>${scripts}`;
|
||||
let inline_script = `__SAPPER__={${[
|
||||
`baseUrl: "${req.baseUrl}"`,
|
||||
mod.preload && serialized && `preloaded: ${serialized}`,
|
||||
].filter(Boolean).join(',')}}`
|
||||
|
||||
const has_service_worker = fs.existsSync(path.join(locations.dest(), 'service-worker.js'));
|
||||
if (has_service_worker) {
|
||||
`if ('serviceWorker' in navigator) navigator.serviceWorker.register('${req.baseUrl}/service-worker.js')`
|
||||
}
|
||||
|
||||
const page = template()
|
||||
.replace('%sapper.scripts%', scripts)
|
||||
.replace('%sapper.base%', `<base href="${req.baseUrl}/">`)
|
||||
.replace('%sapper.scripts%', `<script>${inline_script}</script>${scripts}`)
|
||||
.replace('%sapper.html%', html)
|
||||
.replace('%sapper.head%', `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
|
||||
.replace('%sapper.styles%', (css && css.code ? `<style>${css.code}</style>` : ''));
|
||||
@@ -282,7 +301,8 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
|
||||
const { head, css, html } = rendered;
|
||||
|
||||
const page = template()
|
||||
.replace('%sapper.scripts%', `<script src='/client/${chunks.main}'></script>`)
|
||||
.replace('%sapper.base%', `<base href="${req.baseUrl}/">`)
|
||||
.replace('%sapper.scripts%', `<script>__SAPPER__={baseUrl: "${req.baseUrl}"}</script><script src='${req.baseUrl}/client/${chunks.main}'></script>`)
|
||||
.replace('%sapper.html%', html)
|
||||
.replace('%sapper.head%', `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
|
||||
.replace('%sapper.styles%', (css && css.code ? `<style>${css.code}</style>` : ''));
|
||||
@@ -291,11 +311,9 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
|
||||
}
|
||||
|
||||
return function find_route(req: Req, res: ServerResponse) {
|
||||
const url = req.pathname;
|
||||
|
||||
try {
|
||||
for (const route of routes) {
|
||||
if (!route.error && route.pattern.test(url)) return handle_route(route, req, res);
|
||||
if (!route.error && route.pattern.test(req.path)) return handle_route(route, req, res);
|
||||
}
|
||||
|
||||
handle_error(req, res, 404, 'Not found');
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { detach, findAnchor, scroll_state, which } from './utils';
|
||||
import { Component, ComponentConstructor, Params, Query, Route, RouteData, ScrollPosition, Target } from './interfaces';
|
||||
|
||||
const manifest = typeof window !== 'undefined' && window.__SAPPER__;
|
||||
|
||||
export let component: Component;
|
||||
let target: Node;
|
||||
let routes: Route[];
|
||||
@@ -22,9 +24,12 @@ if ('scrollRestoration' in history) {
|
||||
|
||||
function select_route(url: URL): Target {
|
||||
if (url.origin !== window.location.origin) return null;
|
||||
if (!url.pathname.startsWith(manifest.baseUrl)) return null;
|
||||
|
||||
const pathname = url.pathname.slice(manifest.baseUrl.length);
|
||||
|
||||
for (const route of routes) {
|
||||
const match = route.pattern.exec(url.pathname);
|
||||
const match = route.pattern.exec(pathname);
|
||||
if (match) {
|
||||
if (route.ignore) return null;
|
||||
|
||||
@@ -80,8 +85,8 @@ function prepare_route(Component: ComponentConstructor, data: RouteData) {
|
||||
return { Component, data, redirect, error };
|
||||
}
|
||||
|
||||
if (!component && window.__SAPPER__ && window.__SAPPER__.preloaded) {
|
||||
return { Component, data: Object.assign(data, window.__SAPPER__.preloaded), redirect, error };
|
||||
if (!component && manifest.preloaded) {
|
||||
return { Component, data: Object.assign(data, manifest.preloaded), redirect, error };
|
||||
}
|
||||
|
||||
return Promise.resolve(Component.preload.call({
|
||||
@@ -203,7 +208,7 @@ let prefetching: {
|
||||
} = null;
|
||||
|
||||
export function prefetch(href: string) {
|
||||
const selected = select_route(new URL(href));
|
||||
const selected = select_route(new URL(href, document.baseURI));
|
||||
|
||||
if (selected) {
|
||||
prefetching = {
|
||||
@@ -257,7 +262,8 @@ export function init(_target: Node, _routes: Route[]) {
|
||||
}
|
||||
|
||||
export function goto(href: string, opts = { replaceState: false }) {
|
||||
const target = select_route(new URL(href, window.location.href));
|
||||
const target = select_route(new URL(href, document.baseURI));
|
||||
|
||||
if (target) {
|
||||
navigate(target, null);
|
||||
if (history) history[opts.replaceState ? 'replaceState' : 'pushState']({ id: cid }, '', href);
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
path: `${locations.dest()}/client`,
|
||||
filename: '[hash]/[name].js',
|
||||
chunkFilename: '[hash]/[name].[id].js',
|
||||
publicPath: '/client/'
|
||||
publicPath: `client/`
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user