kit icon indicating copy to clipboard operation
kit copied to clipboard

Dynamic basepath

Open Rich-Harris opened this issue 4 years ago • 31 comments

Moving this here:

One blocking issue I've run into with the current Sapper is that I can't rewrite urls from various sites that we want to host a "whitelabel" version of our app on, because the basepath has to be set in stone at build time:

https://example.com/some-path/ <-- does not work https://subdomain.example.com/ <-- works fine

rather than host a different app for people who want to mount it under a subpath, ideally basepath could be a runtime concern. However I don't know if this is feasible, but it's worth considering.

Cool. For me it is to support ipfs website both when served through an ENS domain or through a gateway with ipfs/ base path for example, both url point to the same exact content : https://ipfs.io/ipfs/bafybeieeuq3av6jdys2q2zwhfv5hutcqejczr46wzjikd5vulfaxfi72r4/ https://jolly-roger.eth.link/ for the hash version, I have to compute at runtime the basepath since it is not possible to know the hash of an ipfs website at build time

https://ptb.discord.com/channels/457912077277855764/750388563354583071/771786495152357386

paths.base is baked into the app at build time. Changing this would likely need to be done in Vite: https://github.com/vitejs/vite/issues/2009

Rich-Harris avatar Mar 23 '21 02:03 Rich-Harris

Yeah - this is super critical from my perspective, we have an ever growing range of people who use our main site with branding, and they hate subdomains because it's arguably not as good for SEO. Sapper doesn't support using a sub-path as the root without a totally separate build and deployment.

antony avatar Mar 23 '21 10:03 antony

Pretty sure we could make it an environment variable

Rich-Harris avatar Mar 23 '21 11:03 Rich-Harris

this is critical for deploying exported static files into github pages because generated urls for custom repos are https://{username}.github.io/{repository} without it assets will be fetched at root level instead of subpath

blackshot avatar Mar 25 '21 01:03 blackshot

@blackshot in those cases, repository is known at build time. Just configure paths.base accordingly. This issue is about cases where the basepath isn't known at build time

Rich-Harris avatar Mar 25 '21 01:03 Rich-Harris

sorry, my bad. i didn't understand it well.

blackshot avatar Mar 25 '21 12:03 blackshot

To recap discussion elsewhere, environment variables solve the white-labelling problem in cases where pages are being server-rendered, but they don't solve the problem in cases where there are no environment variables (e.g. prerendering). For applications like IPFS, we (apparently — I know nothing about IPFS) don't know the basepath until the app is running in the browser.

Presumably we could construct the basepath by subtracting the initial pathname from location.pathname...

<script type="module">
  import { start } from '../../_app/start-xyz123.js';
  const path = '/foo/bar/baz'; // known at render time
  const base = location.pathname.slice(-path.length);

  start({
    target: document.body,
    paths: { base, assets: base || '/.' },
    // ...
  });
</script>

...then if location.pathname is something like /basepath/foo/bar/baz then we know that base is /basepath without needing to include that information in the HTML. Feels brittle but as long as you trust that pages and assets end up in the expected place relative to each other then it ought to work perfectly.

Throwaway thought: this could be useful in the context of i18n, where the basepath is a language identifier like /de or whatever.

Rich-Harris avatar Mar 30 '21 02:03 Rich-Harris

This is affected by the issue described in #1155

Rich-Harris avatar Apr 25 '21 01:04 Rich-Harris

Another use case that this breaks is when trying to launch a sveltekit app from a chrome extension. This is a big deal in a content script, especially, where it's trying to preload those links via the embedded page's url.

I'm actually skipping the generated page to run the start script directly, but the module preload still throws a bunch of errors, and all of the css fails to load.

I'm still working out exactly what needs to happen and if there's a workaround, but this is pretty much putting the kibosh on my dreams of running sveltekit from an extension.

kayakyakr avatar Apr 30 '21 21:04 kayakyakr

By the way the method @Rich-Harris you describe in your comment is exactly how sveltejs-adapter-ipfs handle it currently :

It inject similar code directly in the html and it works nicely : https://github.com/wighawag/sveltejs-adapter-ipfs/blob/d52856d230796e09e560e183b7b0ddb9d7e08482/lib.js#L115

There are other problem that need to be tackled with to be able to have a static website completely independent of the path it will be hosted on.

For example font generated css currently use absolute path : https://github.com/sveltejs/kit/issues/1477

wighawag avatar May 17 '21 12:05 wighawag

I updated my ipfs adapter repo with link to the various issues in svelte kit,

it describe what is missing in svelte-kit for proper support of ipfs (and other hosting platform that require basepath to be dynamic) : https://github.com/wighawag/sveltejs-adapter-ipfs

It currentl implements the solutions as a post-process steps and so it is currently very brittle.

I also setup a demo repo that uses the adapter succesfully : https://github.com/wighawag/sveltekit-ipfs-demo

wighawag avatar May 17 '21 20:05 wighawag

@blackshot in those cases, repository is known at build time. Just configure paths.base accordingly. This issue is about cases where the basepath isn't known at build time

But then I cannot do npm run dev anymore because I prepend base to my redirects and links and this breaks it on localhost.

KonradHoeffner avatar May 25 '21 15:05 KonradHoeffner

My application is a prerendered site which is deployed on a webspace at my Uni. When I change the basepath to the subdirectory on that webspace where the app will be located, CSS works but the HTML links to the routes break (they link to root which 404's). When I leave out the basepath, CSS breaks. Also I'd like to be able to keep using yarn dev.

I guess a solution for me could be what @KonradHoeffner is doing plus somehow checking for dev vs. prod and then prepend base depending on that. It would be cool though if the whole thing was more or less location agnostic... I probably lack understanding why that is a daft idea, it's just what I expected going into the project.

johannesrave avatar May 26 '21 21:05 johannesrave

No its perfectly reasonable. I made a start but work got in the way. I hope to pick it up again as soon as I can.

antony avatar May 27 '21 18:05 antony

When I change the basepath to the subdirectory on that webspace where the app will be located, CSS works but the HTML links to the routes break (they link to root which 404's).

I have the exact same problem now. i am just trying to deploy a svelte kit app with adapter-node to mydomain.com/svelteapp and unfortunately all links are broken. is this here the correct ticket however for that? because as far as i understand it, this ticket goes further and wants to fix dynamic basepath during rendering, whereas this problem i am facing is merely lack of considering the base path in the router when navigating.

eikaramba avatar Jun 19 '21 13:06 eikaramba

I wanted to get the dev server and the uploaded build to work in the setup for my university-project. I'm checking for dev now with this env-variable, as 'dev' doesn't work in svelte.config.js, then setting base depending on that.

import adapter_static from '@sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';
import path from 'path';

const dev = process.env.NODE_ENV == 'development';
const base = dev ? '' : '/path/on/server';

/** @type {import('@sveltejs/kit').Config} */
const config = {
    preprocess: preprocess(),

    kit: {
        adapter: adapter_static({
            // default options are shown
            pages: 'build',
            assets: 'build',
            fallback: null
        }),
        target: '#svelte',
        paths: {
            base: base
        }
    }
}

export default config;

Then I import base from other components/pages and prefix my links with it. This works, if I remember to set it on all the links:

<script lang="ts">
    import { auth } from '$lib/stores.js';
    import { goto } from '$app/navigation';
    import { onMount } from "svelte";
    import { base } from '$app/paths'

    onMount(() => {
        if (!$auth) {
            console.log("Not authenticated, going back to login.")
            goto(base + '/login');
        }
    })
    
    // or like so:
    <RoundButton link={base + '/banking'} name={'SEND BUCKS'}/>
</script>

Like I said, I'm not sure I'm qualified really to discuss this stuff, but just saying this works for me if anyone else is looking for a workaround and finds this.

johannesrave avatar Jun 19 '21 18:06 johannesrave

@johannesrave of course for programmaticaly invoking the navigation it works and is one workaround. however all static links like <a href="/test"> would either not work or need additional hacks like <a href="{base}/test"> - so possible yes, but not really maintainable or "sveltesque" IMHO :)

eikaramba avatar Jun 19 '21 21:06 eikaramba

I absolutely agree, I'm just glad it's running for the moment :P

Also there are two things in this, I think. One being the svelte-kit basepath so CSS and stuff/assets are found, which is what you CAN set in paths.base, even though one needs to check for dev env manually. The other issue are links in the markup. Maybe in the workaround I could solve the second one better using a conditional <base>-tag in __layout instead of prepending base to every single page-link... playing with that now. Then it would be only the one manual path you have to maintain.

EDIT: Spent another few hours down that rabbithole. The <base>... it does nothing! I put <svelte:head><base href="{base}"></svelte:head> in __layout, looked at the generated HTML in the browser, and also tried editing it there. <base> is (reactively?) moved to the end of the <header>, and I assume, some other <base> generated from what it set in svelte.config.js supercedes it - so the links in markup still point to domain.tld when hovering (and clicking obviously) instead of domain.tld/base/path/.

When I manually move it to the top (in the browser inspector!) the links show up right on hover, including the correct domain path. When moving the element up in the generated html in build, it is moved towards the end of <header>again when the site is loaded, so something scripty is going on to move it there?

It's been pretty fruitless, and I'll stop trying to work on this at the root and just go with my silly prepended hrefs until someone qualified fixes this in a release.

johannesrave avatar Jun 19 '21 21:06 johannesrave

Came here when realizing I need to prefix all URLs with '/static/' or replace '/./' with '/static/' for using SvelteKit builds with Python (with the Flask framework). I put static assets in static/static/ in the SvelteKit app dir to get those right at least (so those will resolve to mydomain.com/static/... after build). But I need to customize the other URLs for prod builds. I know this issue is about dynamically handling basepath during runtime, but hoping to at least have static basepath configurable at some point

davidberglund avatar Jul 02 '21 11:07 davidberglund

Trying to deploy a SPA to arweave, similar to IPFS, having to prepend ./ to all my paths to allow hosting relative to a basepath and it's not entirely working...

Worth noting here, arweave is a protocol for permanent storage, once you upload a file it's there permanently. It provides the foundation for something called the 'permaweb'. Files are submitted with transactions whose transaction id is a hash of the transactions fields. The transaction id ultimately becomes the basepath when the file is hosted, but once you know what that id is, you can't go back and edit your files to include it without violating the hash. It's a chicken and egg thing. 🥚 🐔

So from a transaction id: l62soGCK7qbfDjoZ0zQDZjPp4zaZI5YlT_iURFfj5ZE you might end up with the following path...

https://s6w2zidarlxknxyohim5gnadmyz6tyzwterzmjkp7ckeiv7d4wiq.arweave.net/l62soGCK7qbfDjoZ0zQDZjPp4zaZI5YlT_iURFfj5ZE

...where the transaction id becomes part of the basepath.

It would be great to be able to host Svelte SPAs without having to specify a base path explicitly ahead of time to support the "developing permaweb SPAs on arweave" usecase. 👍

Edit: I was able to use @wighawag 's sveltejs-adapter-ipfs to unblock myself. Saved my bacon so thanks for that! (I had some remaining relpaths to deal with but was able to handle it in user code)

DanMacDonald avatar Sep 01 '21 19:09 DanMacDonald

not fully caught up on this thread but wanted to add a further thought: apps that expect the basepath to be something specific are liable to break when they're archived. The Wayback Machine has URLs like https://web.archive.org/web/20200331000251/https://www.yoursite.com/, and naturally that causes stuff to break. I've been bitten by this today (though not by a SvelteKit app).

If you're doing SSR that's not necessarily catastrophic — it means that the router won't fire, but hopefully at least your content and styles will behave correctly. But it'd be a hell of a lot better for future historians if client-side routers were designed with portability in mind.

Rich-Harris avatar Sep 15 '21 19:09 Rich-Harris

I think, at least for the assets it is very important to be able to set the base path to CDN during runtime. In webpack case we are using https://github.com/agoldis/webpack-require-from and it is a huge help. When you have a template repo that you clone and then do a quick project from, deploy on a server and you do not know the CDN's host beforehand it saves a manual labor, writing docs for it and some brain power, because there is nothing to remember.

Keeping CDN url in the envs/package.json is suboptimal to say the least, especially when you have multiple environments to deploy to (qa, staging, prod), because you have to suddenly have 3 envs in your repo AND build three times to deploy to them. Which is a waste of CI time and dev time, on waiting (ergo, money).

Example from webpack usage is also a very concise one:

webpack.config.js

 new WebpackRequireFrom({
      variableName: 'window.__CONTEXT__.cdnUrl',
    }),

Template:

   <script>window.__CONTEXT__ = { cdnUrl: "{{ '' | asset_url }}" };</script>

pavelloz avatar Dec 19 '21 15:12 pavelloz

You can already set the cdn/assets url in sveltekit config. This is about basepath which relates to the path which the application itself runs from.

Setting things on the window object is fine for clientside but won't solve SSR, so the we pack solution you posted won't work for sveltekit. I'm not 100% sure how secure it is either - it strikes me that loading a malicious clientside app might be made trivial by overriding this variable?

antony avatar Dec 19 '21 16:12 antony

Why are absolute URLs even required in the first place? I am mainly not a frontend developer, but when I do create websites, I always use relative paths like import x from '../MyClass'; and I have never had a problem with that. This allows the same website to run locally from the browser over the file protocol, over a local webserver regardless of which root folder it is launched from, on the target domain and on GitHub pages all at the same time. Wouldn't that solve all those problems without requiring any kind of configuration?

KonradHoeffner avatar Dec 20 '21 07:12 KonradHoeffner

@KonradHoeffner Im trying to use only relative urls as well, im pretty fresh to SvelteKit, still experimenting with outputs (im using static site generator adapter) and how it can be hosted on our stack, but i remember having issues with webpack (hence plugin usage - it tells webpack where to look for async loaded chunks). Maybe im raising alarm over nothing and it will turn out to be unnecessary

@antony no security issues there. I dont know what "a malicious clientside app" is/could be, but if it had access to the website JS context changing asset variable is the least of concerns of the user at this point ;)

Apparently webpack even has native setting for it, not need to use plugin as i did: https://github.com/webpack/webpack/issues/443#issuecomment-54113862 This kind of tells me there is a need for those kinds of features - runtime vs build time, to not build 10 versions if you want to deploy the same frontend to 10 different environments with different cdn hosts.

pavelloz avatar Dec 20 '21 15:12 pavelloz

We aren't talking about paths used at development time. We are talking about the url that the html loads the javascript assets from and considers the browser "Base url". Right now you can't serve the same built application at https://example.com and https://example.com/some/path - it just won't work.

antony avatar Dec 20 '21 15:12 antony

Where to load chunks from can be defined in kit config. But the application still needs to know it's own path. That can't be relative - yet.

I'll try to build an example as soon as I get a chance. It will help both with explaining the issue, and with working on a solution.

antony avatar Dec 20 '21 15:12 antony

I dont know what "a malicious clientside app" is/could be

Suppose I can pass an unsanitised variable to your app. A line of js which would execute. I can spend a long time developing a worthwhile exploit based on your existing code, or, I can trivially override a variable to point to my own copy of your app's clientside code.

Both are bad but one is significantly worse.

I don't know how normal it is to define the app's path as a global variable, but it seems like a very easy exploit to me, exploitable with a very commonly made mistake.

antony avatar Dec 20 '21 15:12 antony

@antony Changing variable means having JS access to the site in its context. Having that situation = game over. Not because CDN path can be changed, but because EVERYTHING can be changed, added, stolen, downloaded and executed. ;)

pavelloz avatar Dec 20 '21 16:12 pavelloz

Ok, after couple hours of trying to make it work i think the resulting issue is the usual one, described above.

  1. Im generating static site.
  2. I want to use assets/async chunks from different domain
  3. Even if i put images in static/ and hardcode paths (like recommended in one of GH issues), JS loads them from relative path to app origin (not cdn).

Test.svelte

<img src="/assets/images/awards/2021/ISTC.png" />

Resulting html is correctly, the same: <img src="/assets/images/awards/2021/ISTC.png">

So i ran find/replace and deployed again, to use CDN version, so html looks now: <img src="https://uploads.staging.oregon.platform-os.com/instances/7043/assets/images/awards/2021/ISTC.png">

Now browser tried to load CDN version, then version from relative path from JS (doubling requests). Not always both are downloaded. image

What im trying to achieve, and probably not only me, is to avoid loading assets from the domain with cookies, with no CDN. Thats why i would like to setup cdn path in runtime for dynamic assets, including images.

I would love to run my find/replace script also on JS files, but during build time i do not know CDN url. The fact that i have to resort to those kinds of hacks points into a hole in functionality that is IMO pretty standard nowadays.

PS. And maybe this is completely different issue as @antony suggested here https://github.com/sveltejs/kit/issues/595#issuecomment-998043219 :)

pavelloz avatar Dec 20 '21 23:12 pavelloz

There are a ton of comments here. I've updated the issue description to clarify that this would likely need to be handled in Vite: https://github.com/vitejs/vite/issues/2009. Anyone who's interested can take a look at sending a PR for that issue

benmccann avatar Mar 01 '22 18:03 benmccann