fix(client): bypass client router for links explicitly specifying target (#2563)

BREAKING CHANGE: specifying `target="_self"` for internal links will now perform full reload.
pull/2566/head
Divyansh Singh 2 years ago committed by GitHub
parent 03855dd0cf
commit e95015f598
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -31,6 +31,20 @@ There is one exception to this: if you have an HTML page in `public` and link to
- [/pure.html](/pure.html) - [/pure.html](/pure.html)
- <pathname:///pure.html> - <pathname:///pure.html>
Note that `pathname://` is only supported in Markdown links. Also, `pathname://` will open the link in a new tab by default. You can use `target="_self"` instead to open it in the same tab:
**Input**
```md
[Link to pure.html](/pure.html){target="_self"}
<!-- there is no need to specify pathname:// if the target is explicitly specified -->
```
**Output**
[Link to pure.html](/pure.html){target="_self"}
## Base URL ## Base URL
If your site is deployed to a non-root URL, you will need to set the `base` option in `.vitepress/config.js`. For example, if you plan to deploy your site to `https://foo.github.io/bar/`, then `base` should be set to `'/bar/'` (it should always start and end with a slash). If your site is deployed to a non-root URL, you will need to set the `base` option in `.vitepress/config.js`. For example, if you plan to deploy your site to `https://foo.github.io/bar/`, then `base` should be set to `'/bar/'` (it should always start and end with a slash).

@ -66,7 +66,7 @@ export function usePrefetch() {
if (!hasFetched.has(pathname)) { if (!hasFetched.has(pathname)) {
hasFetched.add(pathname) hasFetched.add(pathname)
const pageChunkPath = pathToFile(pathname) const pageChunkPath = pathToFile(pathname)
doFetch(pageChunkPath) if (pageChunkPath) doFetch(pageChunkPath)
} }
} }
}) })
@ -76,7 +76,6 @@ export function usePrefetch() {
document document
.querySelectorAll<HTMLAnchorElement | SVGAElement>('#app a') .querySelectorAll<HTMLAnchorElement | SVGAElement>('#app a')
.forEach((link) => { .forEach((link) => {
const { target } = link
const { hostname, pathname } = new URL( const { hostname, pathname } = new URL(
link.href instanceof SVGAnimatedString link.href instanceof SVGAnimatedString
? link.href.animVal ? link.href.animVal
@ -91,7 +90,7 @@ export function usePrefetch() {
if ( if (
// only prefetch same tab navigation, since a new tab will load // only prefetch same tab navigation, since a new tab will load
// the lean js chunk instead. // the lean js chunk instead.
target !== `_blank` && link.target !== '_blank' &&
// only prefetch inbound links // only prefetch inbound links
hostname === location.hostname hostname === location.hostname
) { ) {

@ -1,23 +1,22 @@
import RawTheme from '@theme/index'
import { import {
type App,
createApp as createClientApp, createApp as createClientApp,
createSSRApp, createSSRApp,
defineComponent, defineComponent,
h, h,
onMounted, onMounted,
watchEffect watchEffect,
type App
} from 'vue' } from 'vue'
import RawTheme from '@theme/index'
import { inBrowser, pathToFile } from './utils'
import { type Router, RouterSymbol, createRouter, scrollTo } from './router'
import { siteDataRef, useData } from './data'
import { useUpdateHead } from './composables/head'
import { usePrefetch } from './composables/preFetch'
import { dataSymbol, initData } from './data'
import { Content } from './components/Content'
import { ClientOnly } from './components/ClientOnly' import { ClientOnly } from './components/ClientOnly'
import { useCopyCode } from './composables/copyCode' import { Content } from './components/Content'
import { useCodeGroups } from './composables/codeGroups' import { useCodeGroups } from './composables/codeGroups'
import { useCopyCode } from './composables/copyCode'
import { useUpdateHead } from './composables/head'
import { usePrefetch } from './composables/preFetch'
import { dataSymbol, initData, siteDataRef, useData } from './data'
import { RouterSymbol, createRouter, scrollTo, type Router } from './router'
import { inBrowser, pathToFile } from './utils'
function resolveThemeExtends(theme: typeof RawTheme): typeof RawTheme { function resolveThemeExtends(theme: typeof RawTheme): typeof RawTheme {
if (theme.extends) { if (theme.extends) {
@ -123,6 +122,8 @@ function newRouter(): Router {
return createRouter((path) => { return createRouter((path) => {
let pageFilePath = pathToFile(path) let pageFilePath = pathToFile(path)
if (!pageFilePath) return null
if (isInitialPageLoad) { if (isInitialPageLoad) {
initialPath = pageFilePath initialPath = pageFilePath
} }

@ -22,7 +22,7 @@ export const RouterSymbol: InjectionKey<Router> = Symbol()
// we are just using URL to parse the pathname and hash - the base doesn't // we are just using URL to parse the pathname and hash - the base doesn't
// matter and is only passed to support same-host hrefs. // matter and is only passed to support same-host hrefs.
const fakeHost = `http://a.com` const fakeHost = 'http://a.com'
const getDefaultRoute = (): Route => ({ const getDefaultRoute = (): Route => ({
path: '/', path: '/',
@ -36,7 +36,7 @@ interface PageModule {
} }
export function createRouter( export function createRouter(
loadPageModule: (path: string) => Promise<PageModule>, loadPageModule: (path: string) => Awaitable<PageModule | null>,
fallbackComponent?: Component fallbackComponent?: Component
): Router { ): Router {
const route = reactive(getDefaultRoute()) const route = reactive(getDefaultRoute())
@ -73,6 +73,9 @@ export function createRouter(
const pendingPath = (latestPendingPath = targetLoc.pathname) const pendingPath = (latestPendingPath = targetLoc.pathname)
try { try {
let page = await loadPageModule(pendingPath) let page = await loadPageModule(pendingPath)
if (!page) {
throw new Error(`Page not found: ${pendingPath}`)
}
if (latestPendingPath === pendingPath) { if (latestPendingPath === pendingPath) {
latestPendingPath = null latestPendingPath = null
@ -120,7 +123,10 @@ export function createRouter(
} }
} }
} catch (err: any) { } catch (err: any) {
if (!/fetch/.test(err.message) && !/^\/404(\.html|\/)?$/.test(href)) { if (
!/fetch|Page not found/.test(err.message) &&
!/^\/404(\.html|\/)?$/.test(href)
) {
console.error(err) console.error(err)
} }
@ -176,7 +182,7 @@ export function createRouter(
!e.shiftKey && !e.shiftKey &&
!e.altKey && !e.altKey &&
!e.metaKey && !e.metaKey &&
target !== `_blank` && !target &&
origin === currentUrl.origin && origin === currentUrl.origin &&
// don't intercept if non-html extension is present // don't intercept if non-html extension is present
!(extMatch && extMatch[0] !== '.html') !(extMatch && extMatch[0] !== '.html')

@ -18,7 +18,7 @@ export { inBrowser } from '../shared'
/** /**
* Join two paths by resolving the slash collision. * Join two paths by resolving the slash collision.
*/ */
export function joinPath(base: string, path: string): string { export function joinPath(base: string, path: string) {
return `${base}${path}`.replace(/\/+/g, '/') return `${base}${path}`.replace(/\/+/g, '/')
} }
@ -31,7 +31,7 @@ export function withBase(path: string) {
/** /**
* Converts a url path to the corresponding js chunk filename. * Converts a url path to the corresponding js chunk filename.
*/ */
export function pathToFile(path: string): string { export function pathToFile(path: string) {
let pagePath = path.replace(/\.html$/, '') let pagePath = path.replace(/\.html$/, '')
pagePath = decodeURIComponent(pagePath) pagePath = decodeURIComponent(pagePath)
pagePath = pagePath.replace(/\/$/, '/index') // /foo/ -> /foo/index pagePath = pagePath.replace(/\/$/, '/index') // /foo/ -> /foo/index
@ -57,6 +57,7 @@ export function pathToFile(path: string): string {
: pagePath.slice(0, -3) + '_index.md' : pagePath.slice(0, -3) + '_index.md'
pageHash = __VP_HASH_MAP__[pagePath.toLowerCase()] pageHash = __VP_HASH_MAP__[pagePath.toLowerCase()]
} }
if (!pageHash) return null
pagePath = `${base}assets/${pagePath}.${pageHash}.js` pagePath = `${base}assets/${pagePath}.${pageHash}.js`
} else { } else {
// ssr build uses much simpler name mapping // ssr build uses much simpler name mapping

@ -230,6 +230,7 @@ debouncedWatch(
async function fetchExcerpt(id: string) { async function fetchExcerpt(id: string) {
const file = pathToFile(id.slice(0, id.indexOf('#'))) const file = pathToFile(id.slice(0, id.indexOf('#')))
try { try {
if (!file) throw new Error(`Cannot find file for id: ${id}`)
return { id, mod: await import(/*@vite-ignore*/ file) } return { id, mod: await import(/*@vite-ignore*/ file) }
} catch (e) { } catch (e) {
console.error(e) console.error(e)

@ -33,7 +33,7 @@ export function normalizeLink(url: string): string {
} }
const { site } = useData() const { site } = useData()
const { pathname, search, hash } = new URL(url, 'http://example.com') const { pathname, search, hash } = new URL(url, 'http://a.com')
const normalizedPath = const normalizedPath =
pathname.endsWith('/') || pathname.endsWith('.html') pathname.endsWith('/') || pathname.endsWith('.html')

Loading…
Cancel
Save