make sapper.base optional
Is your feature request related to a problem? Please describe. sapper.base was introduced to solve https://github.com/sveltejs/sapper/issues/180 over a year ago. it is not optional for sapper to compile (i'm not sure why, but im sure there are good reasons). however, this causes screwy things to happen for relative/anchor links.
in discord, rich has voiced willingness:
unindented03/15/2019 is there a way to avoid including %sapper.base% and have prefetching still work? that
tag is making me really unhappy it means I can't have relative links anywhere it also fucks up my anchor links
rich_harris03/15/2019 not in the current version. would love to get rid of it, it needs some care though
Describe the solution you'd like
make <base> be optional, if not supplied by the user.
Describe alternatives you've considered none
How important is this feature to you? moderate. i'm autogenerating some links and having to account for base is making it pretty tricky.
updated - workaround is to just monkey patch the relative anchor links for now on mount
// hack for adding location onto anchor links bc of base element
import { onMount } from 'svelte'
onMount(async () => {
;[...document.querySelectorAll('a[href^="#"]')].map(
x => (x.href = document.location + new URL(x.href).hash)
)
})
old
workaround: put this in _layout
<script>
import { onMount } from 'svelte'
onMount(async () => {
const base = [...document.head.children].find(x => x.nodeName === 'BASE')
if (base) base.remove()
})
</script>
i found that if i did this it would mostly work but i would occasionally get 500 errors here and there. i dont know how to reliably reproduce them but its frequent enough that clearly something is wrong (requesting /client/foo/bar instead of /foo/bar)
A <base> also makes url() based refs in SVGs tricksy too fwiw; without converting to absolute urls Safari can't deal.
I came here to repeat what @alexdilley said. Safari doesn't like the <base> tag, it stops the fills from working on the browser and returns fallback fills for SVGs.
@danielhaim1 - i was able to get fills to work. But I had to add style="fill: currentColor;" to the SVG itself. Likewise, I could not use classes within SVGs, I had to apply the stylings inline to the style tag unless it could use something that cascaded, like the fill (with currentColor on the SVG itself).
I just spent a few hours trying to get my anchor links to work, only to find out that %sapper.base% was the root cause, but when I remove it Sapper doesn't work then :(
@Rich-Harris From what I've read on #180 this was introduced to help multiple sapper clients on the same server find their own files, but this is now causing a few headaches:
- Even if your using a single Sapper client on the server, you are still required to have
%sapper.base% - To get relative and anchor links to work, extra work has to be done (both in the server and client)
- Any resources with src attribute reference's to relative files are
404'd
# root path
/
# image's real location on live server
/docs/example-1.png
# in `/docs/` (a route), this is where `<img src='example-1.png'>` points to
/example-1.png
# and without `<base>` the same img tag's src attribute points to
/docs/example-1.png
If your like me and only using one instance of Sapper on the server (sapper dev), and then exporting (sapper export), having to go through all these headaches because of one unneeded feature shouldn't even be happening 😢
Building on @sw-yx's workaround:
<!-- src/components/FixAnchorLinks.svelte -->
<script>
import { onMount } from "svelte";
onMount( () => {
document.querySelectorAll( "a" ).forEach( a => {
if ( ! a.hash || ! document.querySelectorAll( a.hash ).length ) return;
a.href = window.location + a.hash;
} );
} );
</script>
<!-- src/routes/documentation.svelte -->
<script>
import FixAnchorLinks from "../components/FixAnchorLinks.svelte";
</script>
<FixAnchorLinks />
As a component, this fix's anchor links that navigate to a section on the same page.
NOTE: Only use this component on pages that contain anchor links!
This line
a.href = window.location + a.hash;
is better as
a.href = window.location.origin + window.location.pathname + a.hash
I was getting an issue where hashes were appended to the same hash, breaking the navigation. This should resolve that for anyone else with that issue, AFAIK.
Another bug caused by <base href=/> when using Sapper as a static site generator on Github pages (i.e. gh-pages -d __sapper__/export) is the error disallowed MIME type for the client.js file, see https://medium.com/@ppm1988/fix-disallowed-mime-type-error-angular-github-pages-7a8e9769fe75
Tripped over this as well for skip-links. Seems like we can leverage the path store instead of client-side patching:
<script>
import { stores } from '@sapper/app';
const { page } = stores();
</script>
<a href={`${$page.path}#maincontent`}>Skip to main content (Press Enter)</a>
<main id="maincontent">
<slot />
</main>
My server.js
import sirv from "sirv";
import polka from "polka";
import compression from "compression";
import * as sapper from "@sapper/server";
import path from "path";
import fs from "fs";
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === "development";
const server_path = __dirname;
const root_path = path.resolve(__dirname, "../../../");
const client_path = path.resolve(server_path, "../client");
console.log({ root_path, __dirname, client_path });
function sendFile(filePath, res) {// 这里到时候再完善
if (filePath.endsWith(".js")) {
res.setHeader("content-type", "application/javascript; charset=utf-8");
}
fs.createReadStream(filePath).pipe(res);
}
polka() // You can also use Express
.use(function file_server(req, res, next) {
const file_path = path.resolve(root_path, "./" + req.url);
if (req.method === "GET") {
fs.exists(file_path, (r) => {
if (r) {
sendFile(file_path, res);
} else {
if (/\/client\//.test(req.url)) {
const fileName = req.url.replace(/.*\/client\//, "");
const client_file_path = path.resolve(client_path, "./" + fileName);
sendFile(client_file_path, res);
} else {
next(); // move on
}
}
});
} else {
next(); // move on
}
})
.use(compression({ threshold: 0 }), sirv("static", { dev }), sapper.middleware())
.listen(PORT, (err) => {
if (err) console.log("error", err);
});
// 不得不说我在这耗费了一些时间,前面的几个解决方案是对爬虫不怎么友好的。所以只能自己写一段了。 I have to say that I spent some time here, and the first few solutions are not very crawler-friendly.So I have to write a paragraph by myself.
ah good solution @eps1lon
Not the exact functionality that is being looked for here, but maybe a helpful alternate solution if you just want to use a button to navigate to anchor tags inside a page without adding a fragment or mutating the url. just using some super basic vanilla js (can easily make this more robust from here):
<script>
const scrollToAnchor = targetID => {
const distanceToTop = el => Math.floor(el.getBoundingClientRect().top);
const targetAnchor = document.querySelector(targetID);
if (!targetAnchor) return;
window.scrollBy({ top: distanceToTop(targetAnchor), left: 0 });
};
</script>
<!-- somewhere in page -->
<button on:click={()=>scrollToAnchor('#example-anchor')}>Scroll To Anchor</button>
<!-- somewhere else in page -->
<div id="example-anchor">
div to scroll to
</div>
Not sure if this is relevant, when I open my statically exported site with lynx I get a warning Alert!: HREF in BASE tag is not an absolute URL. I also tried setting it manually with sapper export --basepath / but am still getting the error. Apart from that warning in Lynx the site works fine though.