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

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

Merged
merged 2 commits into from
Jul 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/guide/asset-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- <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

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).
Expand Down
5 changes: 2 additions & 3 deletions src/client/app/composables/preFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export function usePrefetch() {
if (!hasFetched.has(pathname)) {
hasFetched.add(pathname)
const pageChunkPath = pathToFile(pathname)
doFetch(pageChunkPath)
if (pageChunkPath) doFetch(pageChunkPath)
}
}
})
Expand All @@ -76,7 +76,6 @@ export function usePrefetch() {
document
.querySelectorAll<HTMLAnchorElement | SVGAElement>('#app a')
.forEach((link) => {
const { target } = link
const { hostname, pathname } = new URL(
link.href instanceof SVGAnimatedString
? link.href.animVal
Expand All @@ -91,7 +90,7 @@ export function usePrefetch() {
if (
// only prefetch same tab navigation, since a new tab will load
// the lean js chunk instead.
target !== `_blank` &&
link.target !== '_blank' &&
// only prefetch inbound links
hostname === location.hostname
) {
Expand Down
23 changes: 12 additions & 11 deletions src/client/app/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import RawTheme from '@theme/index'
import {
type App,
createApp as createClientApp,
createSSRApp,
defineComponent,
h,
onMounted,
watchEffect
watchEffect,
type App
} 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 { useCopyCode } from './composables/copyCode'
import { Content } from './components/Content'
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 {
if (theme.extends) {
Expand Down Expand Up @@ -123,6 +122,8 @@ function newRouter(): Router {
return createRouter((path) => {
let pageFilePath = pathToFile(path)

if (!pageFilePath) return null

if (isInitialPageLoad) {
initialPath = pageFilePath
}
Expand Down
14 changes: 10 additions & 4 deletions src/client/app/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
// matter and is only passed to support same-host hrefs.
const fakeHost = `http://a.com`
const fakeHost = 'http://a.com'

const getDefaultRoute = (): Route => ({
path: '/',
Expand All @@ -36,7 +36,7 @@ interface PageModule {
}

export function createRouter(
loadPageModule: (path: string) => Promise<PageModule>,
loadPageModule: (path: string) => Awaitable<PageModule | null>,
fallbackComponent?: Component
): Router {
const route = reactive(getDefaultRoute())
Expand Down Expand Up @@ -73,6 +73,9 @@ export function createRouter(
const pendingPath = (latestPendingPath = targetLoc.pathname)
try {
let page = await loadPageModule(pendingPath)
if (!page) {
throw new Error(`Page not found: ${pendingPath}`)
}
if (latestPendingPath === pendingPath) {
latestPendingPath = null

Expand Down Expand Up @@ -120,7 +123,10 @@ export function createRouter(
}
}
} 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)
}

Expand Down Expand Up @@ -176,7 +182,7 @@ export function createRouter(
!e.shiftKey &&
!e.altKey &&
!e.metaKey &&
target !== `_blank` &&
!target &&
origin === currentUrl.origin &&
// don't intercept if non-html extension is present
!(extMatch && extMatch[0] !== '.html')
Expand Down
5 changes: 3 additions & 2 deletions src/client/app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export { inBrowser } from '../shared'
/**
* 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, '/')
}

Expand All @@ -31,7 +31,7 @@ export function withBase(path: string) {
/**
* 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$/, '')
pagePath = decodeURIComponent(pagePath)
pagePath = pagePath.replace(/\/$/, '/index') // /foo/ -> /foo/index
Expand All @@ -57,6 +57,7 @@ export function pathToFile(path: string): string {
: pagePath.slice(0, -3) + '_index.md'
pageHash = __VP_HASH_MAP__[pagePath.toLowerCase()]
}
if (!pageHash) return null
pagePath = `${base}assets/${pagePath}.${pageHash}.js`
} else {
// ssr build uses much simpler name mapping
Expand Down
1 change: 1 addition & 0 deletions src/client/theme-default/components/VPLocalSearchBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ debouncedWatch(
async function fetchExcerpt(id: string) {
const file = pathToFile(id.slice(0, id.indexOf('#')))
try {
if (!file) throw new Error(`Cannot find file for id: ${id}`)
return { id, mod: await import(/*@vite-ignore*/ file) }
} catch (e) {
console.error(e)
Expand Down
2 changes: 1 addition & 1 deletion src/client/theme-default/support/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function normalizeLink(url: string): string {
}

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 =
pathname.endsWith('/') || pathname.endsWith('.html')
Expand Down