Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic basepath #595

Closed
Rich-Harris opened this issue Mar 23, 2021 · 61 comments
Closed

Dynamic basepath #595

Rich-Harris opened this issue Mar 23, 2021 · 61 comments
Labels
feature request New feature or request p1-important SvelteKit cannot be used by a large number of people, basic functionality is missing, etc. paths.base bugs relating to `config.kit.paths.base` ready to implement please submit PRs for these issues!
Milestone

Comments

@Rich-Harris
Copy link
Member

Rich-Harris commented Mar 23, 2021

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: vitejs/vite#2009

@antony
Copy link
Member

antony commented Mar 23, 2021

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.

@Rich-Harris
Copy link
Member Author

Pretty sure we could make it an environment variable

@blackshot
Copy link

blackshot commented Mar 25, 2021

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

@Rich-Harris
Copy link
Member Author

@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

@blackshot
Copy link

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

@Rich-Harris
Copy link
Member Author

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
Copy link
Member Author

This is affected by the issue described in #1155

@kayakyakr
Copy link

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.

@Rich-Harris Rich-Harris modified the milestones: 1.0, post-1.0 May 1, 2021
@wighawag
Copy link
Contributor

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 : #1477

@wighawag
Copy link
Contributor

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

@KonradHoeffner
Copy link

@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.

@johannesrave
Copy link

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.

@antony
Copy link
Member

antony commented May 27, 2021 via email

@eikaramba
Copy link

eikaramba commented Jun 19, 2021

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.

@johannesrave
Copy link

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.

@eikaramba
Copy link

eikaramba commented Jun 19, 2021

@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 :)

@johannesrave
Copy link

johannesrave commented Jun 19, 2021

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.

@davidberglund
Copy link

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

@DanMacDonald
Copy link

DanMacDonald commented Sep 1, 2021

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)

@Rich-Harris
Copy link
Member Author

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.

@pavelloz
Copy link

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>

@antony
Copy link
Member

antony commented Dec 19, 2021 via email

@benmccann benmccann removed size:large significant feature with tricky design questions and multi-day implementation vite labels Jan 24, 2023
@netfl0
Copy link

netfl0 commented Jan 26, 2023

@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

I have the use case where it is not known at build time.

@atdiar
Copy link

atdiar commented Jan 26, 2023

In that case, I think it should be made available in the html (via the base tag) by whoever deploys the site and the framework should pick it up.

The way to pick it up is probably not by looking up the presence of <base> but simply getting the value of any node's Node.baseURI property on first request.

Which means in the example of the waybackmachine, they should make sure to modify/include the proper base before serving the html.

Am I mistaken?

@benmccann benmccann added the ready to implement please submit PRs for these issues! label Jan 28, 2023
@wighawag
Copy link
Contributor

@benmccann Thanks for looking into it.

I did not yet found the time to update the ipfs-adapter for 1.0 but I just got the basic demo setup with an ipfs node emulator to showcase the issues.

https://github.com/bug-reproduction/svelte-kit-static-ipfs

Note though that this only highlight some of the issues as more complex scenario will likely exhibit more

@rmarscher
Copy link

rmarscher commented Feb 11, 2023

For anyone struggling to find a workaround, I've had a little luck with using the static adapter and an empty base path. I can't get sveltekit's routing to work, but I can use a different client-side router.

In svelte.config.js (among other config settings) -

{
  kit: {
    adapter: staticAdapter(),
    paths: { base: '' }
  }
}

and in the outer +layout.ts

export const prerender = true;
export const trailingSlash = 'always';

With IPFS, the base folder name is the "content identifier" and based on a hash of the site contents. So you don't know it until after build. But... you do know that it is going to be one-level deep and follow a certain pattern.

I can use a regex pattern in my top level layout to extract the base path from the window.location and then use that to set up the client-side routing.

Maybe an adapter could do something similar with a regex pattern and get sveltekit's router working. Maybe it could inject a top-level runtime request handler that determines the base path and makes it available in $app/paths/base. Maybe it's more complicated than that though.

[Edit: some of the ideas in #8559 around setting base combined with optional route parameters / rest parameters seem like they could solve this use case and allow using sveltekit routing]

@wighawag
Copy link
Contributor

hi @rmarscher, thanks for chiming in

I just tried adding paths: { base: '' } in the demo just posted above (https://github.com/bug-reproduction/svelte-kit-static-ipfs) but the issues mentioned there remain.

Re adapter, have a look at the adapter I created here : https://github.com/wighawag/sveltejs-adapter-ipfs

It is now quite old and might be outdated to work with v1 but it perform the IPFS base path extraction you mention and inject it. My next step is to figure out what of the processing step I can remove now that svelte-kit is in v1.

@wighawag
Copy link
Contributor

wighawag commented Feb 11, 2023

Just some update after trying to get sveltejs-adapter-ipfs works with v1

While I can continue to inject the base at runtime and have routing works with IPFS, I get issues with +page.ts load function / +server.ts GET (which works fine with basic adapter-static and prerender), so this seems to be something to also consider when handling dynamic base path. Planning to update the demo to add this server load function (which I normally use for my statically generated blog) to add to the requirements the solution we want to come up with should support

EDIT:

I updated the demo to showcase the issue with +server.ts/+page.ts pre-rendered data : https://github.com/bug-reproduction/svelte-kit-static-ipfs

I think this demo cover most of the issues. I ll try to isolate more if I can find (I tried the service worker and seems that sveltekit v1 handles them better now, even with the builtin registration)

I could try to get around the issues again with my sveltejs-adapter-ipfs but it would be a lot better to have that properly handled in svelte-kit itself.

Let use this demo as a first benchmark to achieve.

you can play with it here by the way:

Work fine when on the root: https://bafybeia5gowaldvs5mwxdr2wbq7a2ssrwjsapkevy5xlmn2khu3luczkjm.ipfs.dweb.link/

Does not work on a path: https://cloudflare-ipfs.com/ipfs/bafybeia5gowaldvs5mwxdr2wbq7a2ssrwjsapkevy5xlmn2khu3luczkjm/

@wighawag
Copy link
Contributor

I continued my effort and got my adapter working with sveltekit 1.0 again
It is available on npm : https://www.npmjs.com/package/sveltejs-adapter-ipfs

But note that it is not full proof.

I documented what svelte kit need to fix in details here : https://github.com/bug-reproduction/svelte-kit-static-ipfs/tree/fixes#fixes

I am not familiar with sveltekit code base to fix it myself but more than happy to help

@wighawag wighawag mentioned this issue Feb 21, 2023
5 tasks
@benmccann
Copy link
Member

benmccann commented Feb 28, 2023

@sveltejs/kit version 1.9.0 now has improved dynamic base support via the paths.relative option: https://kit.svelte.dev/docs/configuration#paths

Thanks @wighawag for the PR! I'm not sure if there's anything left to be done or if this issue should be closed. Please let me know. Thanks!

@DanMacDonald
Copy link

@wighawag a true champion 🙇‍♂️

@Rich-Harris
Copy link
Member Author

Going to go ahead and close this issue — if there are bits that we've missed, please open new ones!

@wighawag
Copy link
Contributor

Thanks @Rich-Harris and @benmccann for implementing it! This is a really cool feature for a framework like sveltekit to have.

@wighawag a true champion bowing_man

@DanMacDonald if you look at the commit history my contribution was minuscule but happy to be part of it :)

@DanMacDonald
Copy link

Not just recent commits, your static-adapter for IPFS was also a big help. 🍻

@janabimustafa
Copy link

janabimustafa commented Mar 9, 2023

@benmccann It appears that this issue has been disrupted by the solution introduced in issue #2958. Specifically, when using a reverse-proxy with a path such as /site-pr-15, the start.js file fails to load properly while the app.js file functions correctly. This issue seems to stem from the fact that the solution doesn't adequately address situations where paths.relative is enabled but paths.base is not set.

@geoffrich
Copy link
Member

@janabimustafa I don't have a good grasp of your issue, but please open a new bug/feature request as appropriate. A comment on a long-running closed issue like this will easily get lost.

@msdrigg
Copy link

msdrigg commented Aug 9, 2023

I read through this thread and the linked docs at https://kit.svelte.dev/docs/configuration#paths, but I don't think this solves my problem. My problem is that I want to deploy my site on a dynamic subdirectory. For example I would like to deploy the site at https://example.com/pr/1234352 where 1234352 is autogenerated after the site is built. I don't see a way to do this with the current features of svelte-kit and adapter-static.

This may seem like a niche issue, but I am not the only one having it (https://www.reddit.com/r/sveltejs/comments/vd4qcu/how_to_run_svelte_app_from_unknown_sub_directory/).

Ideally I would like to express to svelte to ignore the first two paths /pr/1234352, but I don't see a way to do this.

Maybe the paths.relative is the solution I am looking for, but the docs are not clear as to how I could use this to achieve what I need. Can anybody point me in the right direction?

@wighawag
Copy link
Contributor

wighawag commented Aug 9, 2023

@msdrigg
Copy link

msdrigg commented Aug 9, 2023

Okay relative paths solved my issue.

I was still having an issue due to #10235

Thank you for pointing me to your source I could reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or request p1-important SvelteKit cannot be used by a large number of people, basic functionality is missing, etc. paths.base bugs relating to `config.kit.paths.base` ready to implement please submit PRs for these issues!
Projects
None yet
Development

No branches or pull requests