vitepress 1.6.3 → 2.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -128,16 +128,13 @@ function newRouter() {
128
128
  if (inBrowser) {
129
129
  createApp().then(({ app, router, data }) => {
130
130
  // wait until page component is fetched before mounting
131
- router.go().then(() => {
131
+ router.go(location.href, { initialLoad: true }).then(() => {
132
132
  // dynamically update head tags
133
133
  useUpdateHead(router.route, data.site);
134
134
  app.mount('#app');
135
135
  // scroll to hash on new tab during dev
136
136
  if (import.meta.env.DEV && location.hash) {
137
- const target = document.getElementById(decodeURIComponent(location.hash).slice(1));
138
- if (target) {
139
- scrollTo(target, location.hash);
140
- }
137
+ scrollTo(location.hash);
141
138
  }
142
139
  });
143
140
  });
@@ -8,6 +8,8 @@ export const RouterSymbol = Symbol();
8
8
  const fakeHost = 'http://a.com';
9
9
  const getDefaultRoute = () => ({
10
10
  path: '/',
11
+ hash: '',
12
+ query: '',
11
13
  component: null,
12
14
  data: notFoundPageData
13
15
  });
@@ -15,20 +17,16 @@ export function createRouter(loadPageModule, fallbackComponent) {
15
17
  const route = reactive(getDefaultRoute());
16
18
  const router = {
17
19
  route,
18
- go
19
- };
20
- async function go(href = inBrowser ? location.href : '/') {
21
- href = normalizeHref(href);
22
- if ((await router.onBeforeRouteChange?.(href)) === false)
23
- return;
24
- if (inBrowser && href !== normalizeHref(location.href)) {
25
- // save scroll position before changing url
26
- history.replaceState({ scrollPosition: window.scrollY }, '');
27
- history.pushState({}, '', href);
20
+ async go(href, options) {
21
+ href = normalizeHref(href);
22
+ if ((await router.onBeforeRouteChange?.(href)) === false)
23
+ return;
24
+ if (!inBrowser || (await changeRoute(href, options)))
25
+ await loadPage(href);
26
+ syncRouteQueryAndHash();
27
+ await router.onAfterRouteChange?.(href);
28
28
  }
29
- await loadPage(href);
30
- await (router.onAfterRouteChange ?? router.onAfterRouteChanged)?.(href);
31
- }
29
+ };
32
30
  let latestPendingPath = null;
33
31
  async function loadPage(href, scrollPosition = 0, isRetry = false) {
34
32
  if ((await router.onBeforePageLoad?.(href)) === false)
@@ -37,21 +35,20 @@ export function createRouter(loadPageModule, fallbackComponent) {
37
35
  const pendingPath = (latestPendingPath = targetLoc.pathname);
38
36
  try {
39
37
  let page = await loadPageModule(pendingPath);
40
- if (!page) {
38
+ if (!page)
41
39
  throw new Error(`Page not found: ${pendingPath}`);
42
- }
43
40
  if (latestPendingPath === pendingPath) {
44
41
  latestPendingPath = null;
45
42
  const { default: comp, __pageData } = page;
46
- if (!comp) {
43
+ if (!comp)
47
44
  throw new Error(`Invalid route component: ${comp}`);
48
- }
49
45
  await router.onAfterPageLoad?.(href);
50
46
  route.path = inBrowser ? pendingPath : withBase(pendingPath);
51
47
  route.component = markRaw(comp);
52
48
  route.data = import.meta.env.PROD
53
49
  ? markRaw(__pageData)
54
50
  : readonly(__pageData);
51
+ syncRouteQueryAndHash(targetLoc);
55
52
  if (inBrowser) {
56
53
  nextTick(() => {
57
54
  let actualPathname = siteDataRef.value.base +
@@ -64,20 +61,7 @@ export function createRouter(loadPageModule, fallbackComponent) {
64
61
  href = actualPathname + targetLoc.search + targetLoc.hash;
65
62
  history.replaceState({}, '', href);
66
63
  }
67
- if (targetLoc.hash && !scrollPosition) {
68
- let target = null;
69
- try {
70
- target = document.getElementById(decodeURIComponent(targetLoc.hash).slice(1));
71
- }
72
- catch (e) {
73
- console.warn(e);
74
- }
75
- if (target) {
76
- scrollTo(target, targetLoc.hash);
77
- return;
78
- }
79
- }
80
- window.scrollTo(0, scrollPosition);
64
+ return scrollTo(targetLoc.hash, false, scrollPosition);
81
65
  });
82
66
  }
83
67
  }
@@ -110,13 +94,19 @@ export function createRouter(loadPageModule, fallbackComponent) {
110
94
  .replace(/^\//, '')
111
95
  : '404.md';
112
96
  route.data = { ...notFoundPageData, relativePath };
97
+ syncRouteQueryAndHash(targetLoc);
113
98
  }
114
99
  }
115
100
  }
101
+ function syncRouteQueryAndHash(loc = inBrowser
102
+ ? location
103
+ : { search: '', hash: '' }) {
104
+ route.query = loc.search;
105
+ route.hash = decodeURIComponent(loc.hash);
106
+ }
116
107
  if (inBrowser) {
117
- if (history.state === null) {
108
+ if (history.state === null)
118
109
  history.replaceState({}, '');
119
- }
120
110
  window.addEventListener('click', (e) => {
121
111
  if (e.defaultPrevented ||
122
112
  !(e.target instanceof Element) ||
@@ -125,46 +115,29 @@ export function createRouter(loadPageModule, fallbackComponent) {
125
115
  e.ctrlKey ||
126
116
  e.shiftKey ||
127
117
  e.altKey ||
128
- e.metaKey)
118
+ e.metaKey) {
129
119
  return;
120
+ }
130
121
  const link = e.target.closest('a');
131
122
  if (!link ||
132
123
  link.closest('.vp-raw') ||
133
124
  link.hasAttribute('download') ||
134
- link.hasAttribute('target'))
125
+ link.hasAttribute('target')) {
135
126
  return;
127
+ }
136
128
  const linkHref = link.getAttribute('href') ??
137
129
  (link instanceof SVGAElement ? link.getAttribute('xlink:href') : null);
138
130
  if (linkHref == null)
139
131
  return;
140
- const { href, origin, pathname, hash, search } = new URL(linkHref, link.baseURI);
141
- const currentUrl = new URL(location.href); // copy to keep old data
132
+ const { href, origin, pathname } = new URL(linkHref, link.baseURI);
133
+ const currentLoc = new URL(location.href); // copy to keep old data
142
134
  // only intercept inbound html links
143
- if (origin === currentUrl.origin && treatAsHtml(pathname)) {
135
+ if (origin === currentLoc.origin && treatAsHtml(pathname)) {
144
136
  e.preventDefault();
145
- if (pathname === currentUrl.pathname &&
146
- search === currentUrl.search) {
147
- // scroll between hash anchors in the same page
148
- // avoid duplicate history entries when the hash is same
149
- if (hash !== currentUrl.hash) {
150
- history.pushState({}, '', href);
151
- // still emit the event so we can listen to it in themes
152
- window.dispatchEvent(new HashChangeEvent('hashchange', {
153
- oldURL: currentUrl.href,
154
- newURL: href
155
- }));
156
- }
157
- if (hash) {
158
- // use smooth scroll when clicking on header anchor links
159
- scrollTo(link, hash, link.classList.contains('header-anchor'));
160
- }
161
- else {
162
- window.scrollTo(0, 0);
163
- }
164
- }
165
- else {
166
- go(href);
167
- }
137
+ router.go(href, {
138
+ // use smooth scroll when clicking on header anchor links
139
+ smoothScroll: link.classList.contains('header-anchor')
140
+ });
168
141
  }
169
142
  }, { capture: true });
170
143
  window.addEventListener('popstate', async (e) => {
@@ -172,10 +145,12 @@ export function createRouter(loadPageModule, fallbackComponent) {
172
145
  return;
173
146
  const href = normalizeHref(location.href);
174
147
  await loadPage(href, (e.state && e.state.scrollPosition) || 0);
175
- await (router.onAfterRouteChange ?? router.onAfterRouteChanged)?.(href);
148
+ syncRouteQueryAndHash();
149
+ await router.onAfterRouteChange?.(href);
176
150
  });
177
151
  window.addEventListener('hashchange', (e) => {
178
152
  e.preventDefault();
153
+ syncRouteQueryAndHash();
179
154
  });
180
155
  }
181
156
  handleHMR(route);
@@ -183,20 +158,21 @@ export function createRouter(loadPageModule, fallbackComponent) {
183
158
  }
184
159
  export function useRouter() {
185
160
  const router = inject(RouterSymbol);
186
- if (!router) {
161
+ if (!router)
187
162
  throw new Error('useRouter() is called without provider.');
188
- }
189
163
  return router;
190
164
  }
191
165
  export function useRoute() {
192
166
  return useRouter().route;
193
167
  }
194
- export function scrollTo(el, hash, smooth = false) {
168
+ export function scrollTo(hash, smooth = false, scrollPosition = 0) {
169
+ if (!hash || scrollPosition) {
170
+ window.scrollTo(0, scrollPosition);
171
+ return;
172
+ }
195
173
  let target = null;
196
174
  try {
197
- target = el.classList.contains('header-anchor')
198
- ? el
199
- : document.getElementById(decodeURIComponent(hash).slice(1));
175
+ target = document.getElementById(decodeURIComponent(hash).slice(1));
200
176
  }
201
177
  catch (e) {
202
178
  console.warn(e);
@@ -222,9 +198,8 @@ function handleHMR(route) {
222
198
  if (import.meta.hot) {
223
199
  // hot reload pageData
224
200
  import.meta.hot.on('vitepress:pageData', (payload) => {
225
- if (shouldHotReload(payload)) {
201
+ if (shouldHotReload(payload))
226
202
  route.data = payload.pageData;
227
- }
228
203
  });
229
204
  }
230
205
  }
@@ -239,9 +214,39 @@ function normalizeHref(href) {
239
214
  const url = new URL(href, fakeHost);
240
215
  url.pathname = url.pathname.replace(/(^|\/)index(\.html)?$/, '$1');
241
216
  // ensure correct deep link so page refresh lands on correct files.
242
- if (siteDataRef.value.cleanUrls)
217
+ if (siteDataRef.value.cleanUrls) {
243
218
  url.pathname = url.pathname.replace(/\.html$/, '');
244
- else if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html'))
219
+ }
220
+ else if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) {
245
221
  url.pathname += '.html';
222
+ }
246
223
  return url.pathname + url.search + url.hash;
247
224
  }
225
+ async function changeRoute(href, { smoothScroll = false, initialLoad = false } = {}) {
226
+ const loc = normalizeHref(location.href);
227
+ const { pathname, hash } = new URL(href, fakeHost);
228
+ const currentLoc = new URL(loc, fakeHost);
229
+ if (href === loc) {
230
+ if (!initialLoad) {
231
+ scrollTo(hash, smoothScroll);
232
+ return false;
233
+ }
234
+ }
235
+ else {
236
+ // save scroll position before changing URL
237
+ history.replaceState({ scrollPosition: window.scrollY }, '');
238
+ history.pushState({}, '', href);
239
+ if (pathname === currentLoc.pathname) {
240
+ // scroll between hash anchors on the same page, avoid duplicate entries
241
+ if (hash !== currentLoc.hash) {
242
+ window.dispatchEvent(new HashChangeEvent('hashchange', {
243
+ oldURL: currentLoc.href,
244
+ newURL: href
245
+ }));
246
+ scrollTo(hash, smoothScroll);
247
+ }
248
+ return false;
249
+ }
250
+ }
251
+ return true;
252
+ }
@@ -11,6 +11,8 @@ declare function escapeHtml(str: string): string;
11
11
 
12
12
  interface Route {
13
13
  path: string;
14
+ hash: string;
15
+ query: string;
14
16
  data: PageData;
15
17
  component: Component | null;
16
18
  }
@@ -22,7 +24,10 @@ interface Router {
22
24
  /**
23
25
  * Navigate to a new URL.
24
26
  */
25
- go: (to?: string) => Promise<void>;
27
+ go: (to: string, options?: {
28
+ initialLoad?: boolean;
29
+ smoothScroll?: boolean;
30
+ }) => Promise<void>;
26
31
  /**
27
32
  * Called before the route changes. Return `false` to cancel the navigation.
28
33
  */
@@ -40,10 +45,6 @@ interface Router {
40
45
  * Called after the route changes.
41
46
  */
42
47
  onAfterRouteChange?: (to: string) => Awaitable<void>;
43
- /**
44
- * @deprecated use `onAfterRouteChange` instead
45
- */
46
- onAfterRouteChanged?: (to: string) => Awaitable<void>;
47
48
  }
48
49
  declare function useRouter(): Router;
49
50
  declare function useRoute(): Route;
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import docsearch from '@docsearch/js'
3
- import { useRoute, useRouter } from 'vitepress'
3
+ import { useRouter } from 'vitepress'
4
4
  import type { DefaultTheme } from 'vitepress/theme'
5
5
  import { nextTick, onMounted, watch } from 'vue'
6
6
  import { useData } from '../composables/data'
@@ -10,7 +10,6 @@ const props = defineProps<{
10
10
  }>()
11
11
 
12
12
  const router = useRouter()
13
- const route = useRoute()
14
13
  const { site, localeIndex, lang } = useData()
15
14
 
16
15
  type DocSearchProps = Parameters<typeof docsearch>[0]
@@ -51,17 +50,7 @@ function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) {
51
50
 
52
51
  navigator: {
53
52
  navigate({ itemUrl }) {
54
- const { pathname: hitPathname } = new URL(
55
- window.location.origin + itemUrl
56
- )
57
-
58
- // router doesn't handle same-page navigation so we use the native
59
- // browser location API for anchor navigation
60
- if (route.path === hitPathname) {
61
- window.location.assign(window.location.origin + itemUrl)
62
- } else {
63
- router.go(itemUrl)
64
- }
53
+ router.go(itemUrl)
65
54
  }
66
55
  },
67
56
 
@@ -71,17 +60,6 @@ function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) {
71
60
  url: getRelativePath(item.url)
72
61
  })
73
62
  })
74
- },
75
-
76
- hitComponent({ hit, children }) {
77
- return {
78
- __v: null,
79
- type: 'a',
80
- ref: undefined,
81
- constructor: undefined,
82
- key: undefined,
83
- props: { href: hit.url, children }
84
- }
85
63
  }
86
64
  }) as DocSearchProps
87
65