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

Proposal: Provide hostname to preload and page store #735

Closed
Conduitry opened this issue Jun 10, 2019 · 6 comments
Closed

Proposal: Provide hostname to preload and page store #735

Conduitry opened this issue Jun 10, 2019 · 6 comments
Labels

Comments

@Conduitry
Copy link
Member

It has come up a few times that people want to have slightly different pages served depending on the domain that a Sapper server is accessed via. The best advice is probably this which is definitely hacky.

Thinking about this again, this doesn't seem that hard to provide to people though. We could just add a hostname key to the page object that preload is called with (which would be req.domains.host or window.location.hostname for server and client respectively), and we could also add a hostname to the page store value that's computed in select_target(url).

Is there some reason to not provide these things? Some extra client-side bytes that won't be used the majority of the time?

@Rich-Harris
Copy link
Member

Honestly, I think the main reason this wasn't part of the original design was that I had no idea it was possible. But apparently req.headers.host will give you the hostname the site is being accessed at; the Host header is required by HTTP 1.1.

Adding extra bytes is always something to be watchful for. But it's not many bytes. If this has utility then it's probably worth adding (though I confess I'm not yet clear on what that utility is).

Maybe just host rather than hostname? It contains more information. req.headers.host should be equivalent to window.location.host, if I understand correctly.

@Conduitry
Copy link
Member Author

Yep, calling it host rather than hostname and getting it on the client from location.host makes more sense, as req.header.host and location.host will both include the port number (if it's something non-standard) and location.hostname will not.

Only lightly tested, but here is what I have so far:

diff --git a/runtime/src/app/app.ts b/runtime/src/app/app.ts
index 8e15c0b..091e9fe 100644
--- a/runtime/src/app/app.ts
+++ b/runtime/src/app/app.ts
@@ -121,7 +121,7 @@ export function select_target(url: URL): Target {
 			const part = route.parts[route.parts.length - 1];
 			const params = part.params ? part.params(match) : {};

-			const page = { path, query, params };
+			const page = { host: location.host, path, query, params };

 			return { href: url.href, route, match, page };
 		}
@@ -129,7 +129,7 @@ export function select_target(url: URL): Target {
 }

 export function handle_error(url: URL) {
-	const { pathname, search } = location;
+	const { host, pathname, search } = location;
 	const { session, preloaded, status, error } = initial_data;

 	if (!root_preloaded) {
@@ -154,7 +154,7 @@ export function handle_error(url: URL) {

 	}
 	const query = extract_query(search);
-	render(null, [], props, { path: pathname, query, params: {} });
+	render(null, [], props, { host, path: pathname, query, params: {} });
 }

 export function scroll_state() {
@@ -301,6 +301,7 @@ export async function hydrate_target(target: Target): Promise<{

 	if (!root_preloaded) {
 		root_preloaded = initial_data.preloaded[0] || root_preload.call(preload_context, {
+			host: page.host,
 			path: page.path,
 			query: page.query,
 			params: {}
@@ -338,6 +339,7 @@ export async function hydrate_target(target: Target): Promise<{
 			if (ready || !initial_data.preloaded[i + 1]) {
 				preloaded = preload
 					? await preload.call(preload_context, {
+						host: page.host,
 						path: page.path,
 						query: page.query,
 						params: part.params ? part.params(target.match) : {}
diff --git a/runtime/src/app/types.ts b/runtime/src/app/types.ts
index 9a78e18..51a787d 100644
--- a/runtime/src/app/types.ts
+++ b/runtime/src/app/types.ts
@@ -56,6 +56,7 @@ export type Redirect = {
 };

 export type Page = {
+	host: string;
 	path: string;
 	params: Record<string, string>;
 	query: Record<string, string | string[]>;
diff --git a/runtime/src/server/middleware/get_page_handler.ts b/runtime/src/server/middleware/get_page_handler.ts
index c7c88e4..53f4136 100644
--- a/runtime/src/server/middleware/get_page_handler.ts
+++ b/runtime/src/server/middleware/get_page_handler.ts
@@ -149,6 +149,7 @@ export function get_page_handler(
 		try {
 			const root_preloaded = manifest.root_preload
 				? manifest.root_preload.call(preload_context, {
+					host: req.headers.host,
 					path: req.path,
 					query: req.query,
 					params: {}
@@ -168,6 +169,7 @@ export function get_page_handler(

 					return part.preload
 						? part.preload.call(preload_context, {
+							host: req.headers.host,
 							path: req.path,
 							query: req.query,
 							params
@@ -218,6 +220,7 @@ export function get_page_handler(
 				stores: {
 					page: {
 						subscribe: writable({
+							host: req.headers.host,
 							path: req.path,
 							query: req.query,
 							params

@Conduitry
Copy link
Member Author

As for its utility - a couple of people have asked about being able to slightly vary their app depending on the domain. In chat earlier this afternoon https://discordapp.com/channels/457912077277855764/473466028106579978/587745595171667971 - And the underlying question some months ago in #533 was actually about being able to this.redirect in preload based on the domain. Or I can see someone wanting to do some regionalization of their app using subdomains.

This is another piece of information that's available on both the server and the client, but in different ways, and we'd be able to abstract away those differences.

@Conduitry
Copy link
Member Author

I'm not really sure how this would work with exported sites. Maybe a new --host option for sapper export that just sets the host header sent by the crawler? Client-side stuff would continue to work the same. If you served an app on a different domain than was set when it was crawled, that'd be on you.

@Conduitry
Copy link
Member Author

Initial sapper export --host implementation:

diff --git a/src/api/export.ts b/src/api/export.ts
index 921b51d..f840a94 100644
--- a/src/api/export.ts
+++ b/src/api/export.ts
@@ -18,6 +18,7 @@ type Opts = {
 	cwd?: string,
 	static?: string,
 	basepath?: string,
+	host_header?: string,
 	timeout?: number | false,
 	concurrent?: number,
 	oninfo?: ({ message }: { message: string }) => void;
@@ -44,6 +45,7 @@ async function _export({
 	build_dir = '__sapper__/build',
 	export_dir = '__sapper__/export',
 	basepath = '',
+	host_header,
 	timeout = 5000,
 	concurrent = 8,
 	oninfo = noop,
@@ -70,6 +72,7 @@ async function _export({
 	const protocol = 'http:';
 	const host = `localhost:${port}`;
 	const origin = `${protocol}//${host}`;
+	if (!host_header) host_header = host;
 
 	const root = resolve(origin, basepath);
 	if (!root.href.endsWith('/')) root.href += '/';
@@ -140,6 +143,7 @@ async function _export({
 
 			const r = await Promise.race([
 				fetch(url.href, {
+					headers: { host: host_header },
 					redirect: 'manual'
 				}),
 				timeout_deferred.promise
diff --git a/src/cli.ts b/src/cli.ts
index 045bd36..2ff4f1c 100755
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -194,6 +194,7 @@ prog.command('export [dest]')
 	.describe('Export your app as static files (if possible)')
 	.option('--build', '(Re)build app before exporting', true)
 	.option('--basepath', 'Specify a base path')
+	.option('--host', 'Host header to use when crawling site')
 	.option('--concurrent', 'Concurrent requests', 8)
 	.option('--timeout', 'Milliseconds to wait for a page (--no-timeout to disable)', 5000)
 	.option('--legacy', 'Create separate legacy build')
@@ -210,6 +211,7 @@ prog.command('export [dest]')
 		legacy: boolean,
 		bundler?: 'rollup' | 'webpack',
 		basepath?: string,
+		host?: string,
 		concurrent: number,
 		timeout: number | false,
 		cwd: string,
@@ -236,6 +238,7 @@ prog.command('export [dest]')
 				build_dir: opts['build-dir'],
 				export_dir: dest,
 				basepath: opts.basepath,
+				host_header: opts.host,
 				timeout: opts.timeout,
 				concurrent: opts.concurrent,

@Conduitry
Copy link
Member Author

Implemented in 0.27.5.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants