refactor router

pull/1/head
Evan You 4 years ago
parent 4d09d8c3da
commit 61fdaad519

@ -10,27 +10,35 @@ const NotFound = Theme.NotFound || (() => '404 Not Found')
* contentComponent: import('vue').Component | null
* pageData: { path: string } | null
* }} Route
*
* @typedef {{
* route: Route
* go: (href: string) => Promise<void>
* }} Router
*/
/**
* @type {import('vue').InjectionKey<Route>}
* @type {import('vue').InjectionKey<Router>}
*/
const RouteSymbol = Symbol()
const RouterSymbol = Symbol()
/**
* @returns {Route}
*/
const getDefaultRoute = () => ({
path: location.pathname,
path: '/',
contentComponent: null,
pageData: null
})
export function useRouter() {
const loc = location
/**
* @returns {Router}
*/
export function initRouter() {
const route = shallowReactive(getDefaultRoute())
const inBrowser = typeof window !== 'undefined'
if (__DEV__) {
if (__DEV__ && inBrowser) {
// hot reload pageData
hot.on('vitepress:pageData', (data) => {
if (
@ -42,119 +50,151 @@ export function useRouter() {
})
}
window.addEventListener(
'click',
/**
* @param {*} e
*/
(e) => {
if (e.target.tagName === 'A') {
const { href, target } = e.target
const url = new URL(href)
if (
target !== `_blank` &&
url.protocol === loc.protocol &&
url.hostname === loc.hostname
) {
if (url.pathname === loc.pathname) {
// smooth scroll bewteen hash anchors in the same page
if (url.hash !== loc.hash) {
/**
* @param {string} href
* @returns {Promise<void>}
*/
function go(href) {
if (inBrowser) {
// save scroll position before changing url
history.replaceState({ scrollPosition: window.scrollY }, document.title)
history.pushState(null, '', href)
}
return loadPage(href)
}
/**
* @param {string} href
* @param {number} scrollPosition
* @returns {Promise<void>}
*/
function loadPage(href, scrollPosition = 0) {
const targetLoc = new URL(href)
const pendingPath = (route.path = targetLoc.pathname)
let pagePath = pendingPath.replace(/\.html$/, '')
if (pagePath.endsWith('/')) {
pagePath += 'index'
}
if (__DEV__) {
// awlays force re-fetch content in dev
pagePath += `.md?t=${Date.now()}`
} else {
pagePath += `.md.js`
}
return import(pagePath)
.then(async (m) => {
if (route.path === pendingPath) {
route.contentComponent = m.default
route.pageData = m.__pageData
await nextTick()
if (targetLoc.hash && !scrollPosition) {
/**
* @type {HTMLElement | null}
*/
const target = document.querySelector(targetLoc.hash)
if (target) {
scrollPosition = target.offsetTop
}
}
window.scrollTo({
left: 0,
top: scrollPosition,
behavior: 'auto'
})
}
})
.catch((err) => {
if (!err.message.match(/fetch/)) {
throw err
} else if (route.path === pendingPath) {
route.contentComponent = NotFound
}
})
}
if (inBrowser) {
window.addEventListener(
'click',
/**
* @param {*} e
*/
(e) => {
if (e.target.tagName === 'A') {
const { href, target } = e.target
const targetUrl = new URL(href)
const currentUrl = window.location
if (
target !== `_blank` &&
targetUrl.protocol === currentUrl.protocol &&
targetUrl.hostname === currentUrl.hostname
) {
if (targetUrl.pathname === currentUrl.pathname) {
// smooth scroll bewteen hash anchors in the same page
if (targetUrl.hash !== currentUrl.hash) {
e.preventDefault()
window.scrollTo({
left: 0,
top: e.target.offsetTop,
behavior: 'smooth'
})
}
} else {
e.preventDefault()
window.scrollTo({
left: 0,
top: e.target.offsetTop,
behavior: 'smooth'
})
go(href)
}
} else {
e.preventDefault()
// save scroll position before changing url
saveScrollPosition()
history.pushState(null, '', href)
loadPage(route)
}
}
},
{ capture: true }
)
window.addEventListener(
'popstate',
/**
* @param {*} e
*/
(e) => {
loadPage((e.state && e.state.scrollPosition) || 0)
}
},
{ capture: true }
)
window.addEventListener(
'popstate',
/**
* @param {*} e
*/
(e) => {
loadPage(route, (e.state && e.state.scrollPosition) || 0)
}
)
)
}
provide(RouteSymbol, route)
/**
* @type {Router}
*/
const router = {
route,
go
}
loadPage(route)
}
provide(RouterSymbol, router)
export function useRoute() {
return inject(RouteSymbol) || getDefaultRoute()
loadPage(location.href)
return router
}
/**
* @param {Route} route
* @param {number} scrollPosition
* @return {Router}
*/
function loadPage(route, scrollPosition = 0) {
const pendingPath = (route.path = location.pathname)
let pagePath = pendingPath.replace(/\.html$/, '')
if (pagePath.endsWith('/')) {
pagePath += 'index'
}
if (__DEV__) {
// awlays force re-fetch content in dev
pagePath += `.md?t=${Date.now()}`
} else {
pagePath += `.md.js`
export function useRouter() {
const router = inject(RouterSymbol)
if (__DEV__ && !router) {
throw new Error(
'useRouter() is called without initRouter() in an ancestor component.'
)
}
import(pagePath)
.then(async (m) => {
if (route.path === pendingPath) {
route.contentComponent = m.default
route.pageData = m.__pageData
await nextTick()
if (location.hash && !scrollPosition) {
/**
* @type {HTMLElement | null}
*/
const target = document.querySelector(location.hash)
if (target) {
scrollPosition = target.offsetTop
}
}
window.scrollTo({
left: 0,
top: scrollPosition,
behavior: 'auto'
})
}
})
.catch((err) => {
if (!err.message.match(/fetch/)) {
throw err
} else if (route.path === pendingPath) {
route.contentComponent = NotFound
}
})
// @ts-ignore
return router
}
function saveScrollPosition() {
history.replaceState(
{
scrollPosition: window.scrollY
},
document.title,
''
)
/**
* @returns {Route}
*/
export function useRoute() {
return useRouter().route
}

@ -1,17 +1,13 @@
import { createApp, h } from 'vue'
import { Content } from './components/Content'
import { useRouter } from './composables/router'
import { initRouter } from './composables/router'
import { useSiteData } from './composables/siteData'
import { usePageData } from './composables/pageData'
import Theme from '/@theme/index'
const App = {
setup() {
if (typeof window !== 'undefined') {
useRouter()
} else {
// TODO inject static route for SSR
}
initRouter()
return () => h(Theme.Layout)
}
}

@ -53,14 +53,15 @@ export async function buildClient(options: BuildOptions) {
// for each .md entry chunk, adjust its name to its correct path.
for (const name in bundle) {
const chunk = bundle[name]
if (
chunk.type === 'chunk' &&
chunk.isEntry &&
chunk.facadeModuleId &&
chunk.facadeModuleId.endsWith('.md')
) {
const relativePath = path.relative(root, chunk.facadeModuleId)
chunk.fileName = relativePath + '.js'
if (chunk.type === 'chunk') {
if (
chunk.isEntry &&
chunk.facadeModuleId &&
chunk.facadeModuleId.endsWith('.md')
) {
const relativePath = path.relative(root, chunk.facadeModuleId)
chunk.fileName = relativePath + '.js'
}
}
}
}

Loading…
Cancel
Save