diff --git a/runtime/src/app/app.ts b/runtime/src/app/app.ts
index 95b882fd0..b38ab72a5 100644
--- a/runtime/src/app/app.ts
+++ b/runtime/src/app/app.ts
@@ -93,18 +93,71 @@ export function extract_query(search: string) {
return query;
}
-export function select_target(url: URL): Target {
- if (url.origin !== location.origin) return null;
- if (!url.pathname.startsWith(initial_data.baseUrl)) return null;
+function countBaseDepth(baseUrl: string): number {
+ let depth = 0;
+ const splitted = baseUrl.split('/');
+ for (let split of splitted) {
+ if (split === '..') {
+ depth++;
+ }
+ }
+ return depth;
+}
+
+function trimSlashes(path: string): string {
+ if (path.startsWith('/')) {
+ path = path.slice(1);
+ }
+ if (path.endsWith('/')) {
+ path = path.slice(0, path.length - 1);
+ }
+ return path;
+}
- let path = url.pathname.slice(initial_data.baseUrl.length);
+let pathToCut;
+export function select_target(url: URL, start: boolean = false): Target {
+ if (url.origin !== location.origin) return null;
- if (path === '') {
- path = '/';
+ let path = url.pathname;
+ let pathname = url.pathname;
+ let normalPath = pathname;
+ if (initial_data.baseUrl.startsWith('.')) {
+ if(path.endsWith('index.html')) {
+ path = path.slice(0, path.length-10);
+ }
+ if (start) {
+ const baseDepth = countBaseDepth(initial_data.baseUrl);
+ const splitPath = trimSlashes(path).split('/');
+ pathToCut = '/' + splitPath.slice(0, splitPath.length-baseDepth).join('/');
+ }
+ path = path.substr(pathToCut.length);
+ if (!path.startsWith('/')) {
+ path = '/' + path;
+ }
+ normalPath = path;
+ if(!path.endsWith('/')) {
+ path = path + '/';
+ }
+ if(pathToCut === '/') {
+ pathname = path;
+ } else {
+ pathname = pathToCut + path;
+ }
+ if (path === '') {
+ path = '/';
+ pathname = pathToCut;
+ }
+ } else {
+ if (!path.startsWith(initial_data.baseUrl)) return null;
+ path = path.slice(initial_data.baseUrl.length);
+ if (path === '') {
+ path = '/';
+ }
+ normalPath = path;
}
// avoid accidental clashes between server routes and page routes
- if (ignore.some(pattern => pattern.test(path))) return;
+ if (ignore.some(pattern => pattern.test(normalPath))) return;
for (let i = 0; i < routes.length; i += 1) {
const route = routes[i];
@@ -118,7 +171,7 @@ export function select_target(url: URL): Target {
const page = { host: location.host, path, query, params };
- return { href: url.href, route, match, page };
+ return { href: url.href, pathname, route, match, page };
}
}
}
diff --git a/runtime/src/app/goto/index.ts b/runtime/src/app/goto/index.ts
index 0241b2c65..695fc0f5b 100644
--- a/runtime/src/app/goto/index.ts
+++ b/runtime/src/app/goto/index.ts
@@ -4,7 +4,7 @@ export default function goto(href: string, opts = { replaceState: false }) {
const target = select_target(new URL(href, document.baseURI));
if (target) {
- history[opts.replaceState ? 'replaceState' : 'pushState']({ id: cid }, '', href);
+ history[opts.replaceState ? 'replaceState' : 'pushState']({ id: cid }, '', target.pathname);
return navigate(target, null).then(() => {});
}
diff --git a/runtime/src/app/start/index.ts b/runtime/src/app/start/index.ts
index 858eb32ea..a36ff4a80 100644
--- a/runtime/src/app/start/index.ts
+++ b/runtime/src/app/start/index.ts
@@ -39,7 +39,7 @@ export default function start(opts: {
if (initial_data.error) return handle_error(url);
- const target = select_target(url);
+ const target = select_target(url, true);
if (target) return navigate(target, uid, true, hash);
});
}
@@ -100,7 +100,7 @@ function handle_click(event: MouseEvent) {
const noscroll = a.hasAttribute('sapper-noscroll');
navigate(target, null, noscroll, url.hash);
event.preventDefault();
- history.pushState({ id: cid }, '', url.href);
+ history.pushState({ id: cid }, '', target.pathname);
}
}
diff --git a/runtime/src/app/types.ts b/runtime/src/app/types.ts
index 51a787dc3..fed219dcd 100644
--- a/runtime/src/app/types.ts
+++ b/runtime/src/app/types.ts
@@ -48,6 +48,7 @@ export type Target = {
route: Route;
match: RegExpExecArray;
page: Page;
+ pathname: string;
};
export type Redirect = {
diff --git a/runtime/src/server/middleware/get_page_handler.ts b/runtime/src/server/middleware/get_page_handler.ts
index 53f413640..f9e24d913 100644
--- a/runtime/src/server/middleware/get_page_handler.ts
+++ b/runtime/src/server/middleware/get_page_handler.ts
@@ -45,6 +45,11 @@ export function get_page_handler(
}
async function handle_page(page: Page, req: Req, res: Res, status = 200, error: Error | string = null) {
+ let baseHref = req.baseUrl;
+ if(req.params && req.params.baseHref) {
+ baseHref = req.params.baseHref;
+ }
+ const prefix = baseHref.startsWith('.') ? '' : baseHref + '/';
const is_service_worker_index = req.path === '/service-worker-index.html';
const build_info: {
bundler: 'rollup' | 'webpack',
@@ -72,7 +77,7 @@ export function get_page_handler(
// TODO add dependencies and CSS
const link = preloaded_chunks
.filter(file => file && !file.match(/\.map$/))
- .map(file => `<${req.baseUrl}/client/${file}>;rel="modulepreload"`)
+ .map(file => `<${prefix}client/${file}>;rel="modulepreload"`)
.join(', ');
res.setHeader('Link', link);
@@ -81,7 +86,7 @@ export function get_page_handler(
.filter(file => file && !file.match(/\.map$/))
.map((file) => {
const as = /\.css$/.test(file) ? 'style' : 'script';
- return `<${req.baseUrl}/client/${file}>;rel="preload";as="${as}"`;
+ return `<${prefix}client/${file}>;rel="preload";as="${as}"`;
})
.join(', ');
@@ -269,29 +274,78 @@ export function get_page_handler(
let script = `__SAPPER__={${[
error && `error:${serialized.error},status:${status}`,
- `baseUrl:"${req.baseUrl}"`,
+ `baseUrl:"${baseHref}"`,
serialized.preloaded && `preloaded:${serialized.preloaded}`,
serialized.session && `session:${serialized.session}`
].filter(Boolean).join(',')}};`;
if (has_service_worker) {
- script += `if('serviceWorker' in navigator)navigator.serviceWorker.register('${req.baseUrl}/service-worker.js');`;
+ script += `if('serviceWorker' in navigator)navigator.serviceWorker.register('${prefix}service-worker.js');`;
}
const file = [].concat(build_info.assets.main).filter(file => file && /\.js$/.test(file))[0];
- const main = `${req.baseUrl}/client/${file}`;
+ const main = `${prefix}client/${file}`;
if (build_info.bundler === 'rollup') {
if (build_info.legacy_assets) {
- const legacy_main = `${req.baseUrl}/client/legacy/${build_info.legacy_assets.main}`;
- script += `(function(){try{eval("async function x(){}");var main="${main}"}catch(e){main="${legacy_main}"};var s=document.createElement("script");try{new Function("if(0)import('')")();s.src=main;s.type="module";s.crossOrigin="use-credentials";}catch(e){s.src="${req.baseUrl}/client/shimport@${build_info.shimport}.js";s.setAttribute("data-main",main);}document.head.appendChild(s);}());`;
+ const legacy_main = `${prefix}client/legacy/${build_info.legacy_assets.main}`;
+ script += `(function(){try{eval("async function x(){}");var main="${main}"}catch(e){main="${legacy_main}"};var s=document.createElement("script");try{new Function("if(0)import('')")();s.src=main;s.type="module";s.crossOrigin="use-credentials";}catch(e){s.src="${prefix}client/shimport@${build_info.shimport}.js";s.setAttribute("data-main",main);}document.head.appendChild(s);}());`;
} else {
- script += `var s=document.createElement("script");try{new Function("if(0)import('')")();s.src="${main}";s.type="module";s.crossOrigin="use-credentials";}catch(e){s.src="${req.baseUrl}/client/shimport@${build_info.shimport}.js";s.setAttribute("data-main","${main}")}document.head.appendChild(s)`;
+ script += `var s=document.createElement("script");try{new Function("if(0)import('')")();s.src="${main}";s.type="module";s.crossOrigin="use-credentials";}catch(e){s.src="${prefix}client/shimport@${build_info.shimport}.js";s.setAttribute("data-main","${main}")}document.head.appendChild(s)`;
}
} else {
script += `
+ `;
+ }
+
let styles: string;
// TODO make this consistent across apps
@@ -321,8 +375,8 @@ export function get_page_handler(
const nonce_attr = (res.locals && res.locals.nonce) ? ` nonce="${res.locals.nonce}"` : '';
const body = template()
- .replace('%sapper.base%', () => ``)
- .replace('%sapper.scripts%', () => ``)
+ .replace('%sapper.base%', () => ``)
+ .replace('%sapper.scripts%', () => `${pre_script}`)
.replace('%sapper.html%', () => html)
.replace('%sapper.head%', () => `${head}`)
.replace('%sapper.styles%', () => styles);
diff --git a/runtime/src/server/middleware/index.ts b/runtime/src/server/middleware/index.ts
index f7bbea4ff..5b2acd415 100644
--- a/runtime/src/server/middleware/index.ts
+++ b/runtime/src/server/middleware/index.ts
@@ -14,8 +14,26 @@ export default function middleware(opts: {
let emitted_basepath = false;
+ const useRelativeBasePath = process.env['SAPPER_RELATIVE_BASEPATH'] === 'true';
+
return compose_handlers(ignore, [
(req: Req, res: Res, next: () => void) => {
+ if (useRelativeBasePath) {
+ if (!req.params) {
+ req.params = {}
+ }
+ if(req.url === '/') {
+ req.params.baseHref = '.';
+ } else {
+ const numParts = req.path.split('/').length;
+ if (numParts > 1) {
+ req.params.baseHref = '..';
+ for (let i = 2; i < numParts; i++) {
+ req.params.baseHref += '/..';
+ }
+ }
+ }
+ }
if (req.baseUrl === undefined) {
let { originalUrl } = req;
if (req.url === '/' && originalUrl[originalUrl.length - 1] !== '/') {
diff --git a/src/api/export.ts b/src/api/export.ts
index 008224910..9fdf193f2 100644
--- a/src/api/export.ts
+++ b/src/api/export.ts
@@ -24,6 +24,7 @@ type Opts = {
oninfo?: ({ message }: { message: string }) => void;
onfile?: ({ file, size, status }: { file: string, size: number, status: number }) => void;
entry?: string;
+ relative_basepath?: boolean;
};
type Ref = {
@@ -55,7 +56,8 @@ async function _export({
concurrent = 8,
oninfo = noop,
onfile = noop,
- entry = '/'
+ entry = '/',
+ relative_basepath = false,
}: Opts = {}) {
basepath = basepath.replace(/^\//, '')
@@ -94,7 +96,8 @@ async function _export({
env: Object.assign({
PORT: port,
NODE_ENV: 'production',
- SAPPER_EXPORT: 'true'
+ SAPPER_EXPORT: 'true',
+ SAPPER_RELATIVE_BASEPATH: relative_basepath ? 'true' : undefined,
}, process.env)
});
diff --git a/src/cli.ts b/src/cli.ts
index 97c95df76..bac20af52 100755
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -207,6 +207,7 @@ prog.command('export [dest]')
.option('--build-dir', 'Intermediate build directory', '__sapper__/build')
.option('--ext', 'Custom page route extensions (space separated)', '.svelte .html')
.option('--entry', 'Custom entry points (space separated)', '/')
+ .option('--relative-basepath', 'so exported site can be put in any basepath', false)
.action(async (dest = '__sapper__/export', opts: {
build: boolean,
legacy: boolean,
@@ -222,7 +223,8 @@ prog.command('export [dest]')
output: string,
'build-dir': string,
ext: string
- entry: string
+ entry: string,
+ 'relative-basepath': boolean,
}) => {
try {
if (opts.build) {
@@ -244,7 +246,7 @@ prog.command('export [dest]')
timeout: opts.timeout,
concurrent: opts.concurrent,
entry: opts.entry,
-
+ relative_basepath: opts['relative-basepath'],
oninfo: event => {
console.log(colors.bold().cyan(`> ${event.message}`));
},