Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

Commit

Permalink
implement --basepath flag (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Mar 16, 2018
1 parent a95ddee commit a82a744
Show file tree
Hide file tree
Showing 22 changed files with 170 additions and 92 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"compression": "^1.7.1",
"eslint": "^4.13.1",
"eslint-plugin-import": "^2.8.0",
"express": "^4.16.3",
"get-port": "^3.2.0",
"mocha": "^5.0.4",
"nightmare": "^3.0.0",
Expand Down
4 changes: 2 additions & 2 deletions runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ function handle_popstate(event) {
}
var prefetching = null;
function prefetch(href) {
var selected = select_route(new URL(href));
var selected = select_route(new URL(href, document.baseURI));
if (selected) {
prefetching = {
href: href,
Expand Down Expand Up @@ -240,7 +240,7 @@ function init(_target, _routes) {
}
function goto(href, opts) {
if (opts === void 0) { opts = { replaceState: false }; }
var target = select_route(new URL(href, window.location.href));
var target = select_route(new URL(href, document.baseURI));
if (target) {
navigate(target, null);
if (history)
Expand Down
13 changes: 10 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,23 @@ prog.command('dev')
.describe('Start a development server')
.option('-p, --port', 'Specify a port')
.option('-o, --open', 'Open a browser window')
.action(async (opts: { port: number, open: boolean }) => {
.option('--basepath', 'Specify a base path')
.action(async (opts: { port: number, open: boolean, basepath: string }) => {
if (opts.basepath) process.env.SAPPER_BASEPATH = opts.basepath;

const { dev } = await import('./cli/dev');
dev(opts);
});

prog.command('build [dest]')
.describe('Create a production-ready version of your app')
.action(async (dest = 'build') => {
.option('--basepath', 'Specify a base path')
.action(async (dest = 'build', opts: { basepath: string }) => {
console.log(`> Building...`);

process.env.NODE_ENV = 'production';
process.env.SAPPER_DEST = dest;
if (opts.basepath) process.env.SAPPER_BASEPATH = opts.basepath;

const start = Date.now();

Expand All @@ -49,11 +54,13 @@ 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';
process.env.SAPPER_DEST = '.sapper/.export';
if (opts.basepath) process.env.SAPPER_BASEPATH = opts.basepath;

const start = Date.now();

Expand Down
2 changes: 1 addition & 1 deletion src/cli/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/cli/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
8 changes: 5 additions & 3 deletions src/cli/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import fetch from 'node-fetch';
import * as ports from 'port-authority';
import prettyBytes from 'pretty-bytes';
import { minify_html } from './utils/minify_html';
import { locations } from '../config';
import { basepath, locations } from '../config';

export async function exporter(export_dir: string) {
const build_dir = locations.dest();
Expand Down Expand Up @@ -74,12 +74,14 @@ export async function exporter(export_dir: string) {
const $ = cheerio.load(body);
const hrefs: string[] = [];

const base = new URL($('base').attr('href') || '', origin);

$('a[href]').each((i: number, $a) => {
hrefs.push($a.attribs.href);
});

return hrefs.reduce((promise, href) => {
return promise.then(() => handle(new URL(href, url.href)));
return promise.then(() => handle(new URL(href, base.href)));
}, Promise.resolve());
});
}
Expand All @@ -90,6 +92,6 @@ export async function exporter(export_dir: string) {
}

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());
}
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as path from 'path';

export const dev = () => process.env.NODE_ENV !== 'production';

export const basepath = () => process.env.SAPPER_BASEPATH || '';

export const locations = {
base: () => path.resolve(process.env.SAPPER_BASE || ''),
app: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_APP || 'app'),
Expand Down
13 changes: 10 additions & 3 deletions src/core/create_manifests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as path from 'path';
import * as glob from 'glob';
import create_routes from './create_routes';
import { posixify, write_if_changed } from './utils';
import { dev, locations } from '../config';
import { basepath, dev, locations } from '../config';
import { Route } from '../interfaces';

export function create_main_manifests({ routes, dev_port }: {
Expand All @@ -24,14 +24,17 @@ export function create_serviceworker_manifest({ routes, client_files }: {
client_files: string[];
}) {
const assets = glob.sync('**', { cwd: 'assets', nodir: true });
const base = basepath()
? `/${basepath()}`
: '';

let code = `
// This file is generated by Sapper — do not edit it!
export const timestamp = ${Date.now()};
export const assets = [\n\t${assets.map((x: string) => `"${x}"`).join(',\n\t')}\n];
export const assets = [\n\t${assets.map((x: string) => `"${base}/${x}"`).join(',\n\t')}\n];
export const shell = [\n\t${client_files.map((x: string) => `"${x}"`).join(',\n\t')}\n];
export const shell = [\n\t${client_files.map((x: string) => `"${base}/${x}"`).join(',\n\t')}\n];
export const routes = [\n\t${routes.filter((r: Route) => r.type === 'page' && !/^_[45]xx$/.test(r.id)).map((r: Route) => `{ pattern: ${r.pattern} }`).join(',\n\t')}\n];
`.replace(/^\t\t/gm, '').trim();
Expand All @@ -42,6 +45,8 @@ export function create_serviceworker_manifest({ routes, client_files }: {
function generate_client(routes: Route[], path_to_routes: string, dev_port?: number) {
let code = `
// This file is generated by Sapper — do not edit it!
export const basepath = "/${basepath()}";
export const routes = [
${routes
.map(route => {
Expand Down Expand Up @@ -84,6 +89,8 @@ function generate_client(routes: Route[], path_to_routes: string, dev_port?: num
function generate_server(routes: Route[], path_to_routes: string) {
let code = `
// This file is generated by Sapper — do not edit it!
export const basepath = "${basepath() ? `/${basepath()}` : ''}";
${routes
.map(route => {
const file = posixify(`${path_to_routes}/${route.file}`);
Expand Down
4 changes: 3 additions & 1 deletion src/core/create_routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as path from 'path';
import glob from 'glob';
import { locations } from '../config';
import { basepath, locations } from '../config';
import { Route } from '../interfaces';

export default function create_routes({ files } = { files: glob.sync('**/*.*', { cwd: locations.routes(), nodir: true }) }) {
Expand All @@ -27,6 +27,8 @@ export default function create_routes({ files } = { files: glob.sync('**/*.*', {
params.push(match[1]);
}

if (basepath()) parts.unshift(...basepath().split('/'));

// TODO can we do all this with sub-parts? or does
// nesting make that impossible?
let pattern_string = '';
Expand Down
41 changes: 27 additions & 14 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import rimraf from 'rimraf';
import devalue from 'devalue';
import { lookup } from './middleware/mime';
import { create_routes, create_compilers } from './core';
import { locations, dev } from './config';
import { basepath, locations, dev } from './config';
import { Route, Template } from './interfaces';
import sourceMapSupport from 'source-map-support';

Expand Down Expand Up @@ -41,6 +41,7 @@ export default function middleware({ routes }: {
routes: RouteObject[]
}) {
const output = locations.dest();
const base = basepath() ? `/${basepath()}` : '';

const client_info = JSON.parse(fs.readFileSync(path.join(output, 'client_info.json'), 'utf-8'));

Expand All @@ -51,34 +52,38 @@ export default function middleware({ routes }: {
},

fs.existsSync(path.join(output, 'index.html')) && serve({
pathname: '/index.html',
base,
pathname: 'index.html',
cache_control: 'max-age=600'
}),

fs.existsSync(path.join(output, 'service-worker.js')) && serve({
pathname: '/service-worker.js',
base,
pathname: 'service-worker.js',
cache_control: 'max-age=600'
}),

serve({
prefix: '/client/',
base,
prefix: 'client/',
cache_control: 'max-age=31536000'
}),

get_route_handler(client_info.assetsByChunkName, routes)
get_route_handler(client_info.assetsByChunkName, routes, base)
].filter(Boolean));

return middleware;
}

function serve({ prefix, pathname, cache_control }: {
function serve({ base, prefix, pathname, cache_control }: {
base: string,
prefix?: string,
pathname?: string,
cache_control: string
}) {
const filter = pathname
? (req: Req) => req.pathname === pathname
: (req: Req) => req.pathname.startsWith(prefix);
? (req: Req) => req.pathname === `${base}/${pathname}`
: (req: Req) => req.pathname.startsWith(`${base}/${prefix}`);

const output = locations.dest();

Expand All @@ -93,7 +98,7 @@ function serve({ prefix, pathname, cache_control }: {
const type = lookup(req.pathname);

try {
const data = read(req.pathname.slice(1));
const data = read(req.pathname.slice(base.length + 1));

res.setHeader('Content-Type', type);
res.setHeader('Cache-Control', cache_control);
Expand All @@ -110,7 +115,7 @@ function serve({ prefix, pathname, cache_control }: {

const resolved = Promise.resolve();

function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]) {
function get_route_handler(chunks: Record<string, string>, routes: RouteObject[], base: string) {
const template = dev()
? () => fs.readFileSync(`${locations.app()}/template.html`, 'utf-8')
: (str => () => str)(fs.readFileSync(`${locations.dest()}/template.html`, 'utf-8'));
Expand All @@ -127,7 +132,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 => `<${base}/client/${file}>;rel="preload";as="script"`)
.join(', ');

res.setHeader('Link', link);
Expand Down Expand Up @@ -169,12 +174,19 @@ 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='${base}/client/${file}'></script>`)
.join('');

scripts = `<script>__SAPPER__ = { preloaded: ${serialized} };</script>${scripts}`;
const has_service_worker = fs.existsSync(path.join(locations.dest(), 'service-worker.js'));
const inline_script = [
mod.preload && serialized && `__SAPPER__={preloaded: ${serialized}}`,
has_service_worker && `if ('serviceWorker' in navigator) navigator.serviceWorker.register('${base}/service-worker.js')`
].filter(Boolean).join(';');

if (inline_script) scripts = `<script>${inline_script}</script>${scripts}`;

const page = template()
.replace('%sapper.base%', `<base href="${base}/">`)
.replace('%sapper.scripts%', scripts)
.replace('%sapper.html%', html)
.replace('%sapper.head%', `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
Expand Down Expand Up @@ -282,7 +294,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="${base}/">`)
.replace('%sapper.scripts%', `<script src='${base}/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>` : ''));
Expand Down
5 changes: 3 additions & 2 deletions src/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,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 = {
Expand Down Expand Up @@ -257,7 +257,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);
Expand Down
9 changes: 6 additions & 3 deletions src/webpack.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { locations, dev } from './config';
import { basepath, locations, dev } from './config';

const base = basepath() ? `/${basepath()}` : '';

export default {
dev: dev(),
Expand All @@ -15,7 +17,7 @@ export default {
path: `${locations.dest()}/client`,
filename: '[hash]/[name].js',
chunkFilename: '[hash]/[name].[id].js',
publicPath: '/client/'
publicPath: `${base}/client/`
};
}
},
Expand Down Expand Up @@ -48,7 +50,8 @@ export default {
return {
path: locations.dest(),
filename: '[name].js',
chunkFilename: '[name].[id].[hash].js'
chunkFilename: '[name].[id].[hash].js',
publicPath: base
}
}
}
Expand Down
Loading

0 comments on commit a82a744

Please sign in to comment.