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

perf: implement custom routes #1447

Merged
merged 42 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
46a6d2d
feat: introduce VPLink and virtual routes
Mister-Hope Dec 4, 2023
aedc63a
style: fix linter
Mister-Hope Dec 4, 2023
6920338
test: update tests
Mister-Hope Dec 4, 2023
7a36432
Merge branch 'main' into new-router
meteorlxy Dec 10, 2023
ca7eb8c
fix: fix redirect issue
Mister-Hope Dec 11, 2023
34bb3a1
feat: add e2e test
Mister-Hope Dec 11, 2023
57d59c7
Merge branch 'main' into new-router
Mister-Hope Dec 14, 2023
9684cf5
test: update e2e test
Mister-Hope Dec 14, 2023
95df8bb
Merge branch 'main' into new-router
meteorlxy Dec 14, 2023
aab1ee2
Merge branch 'main' into new-router
meteorlxy Dec 19, 2023
4aa2362
Merge branch 'main' into new-router
meteorlxy Dec 20, 2023
cc44ec9
Merge branch 'main' into new-router
Mister-Hope Dec 29, 2023
2d5bac1
chore: update e2e test
Mister-Hope Dec 29, 2023
ca01f9c
test: fix tests
Mister-Hope Dec 29, 2023
e65f531
Merge branch 'main' into new-router
meteorlxy Jan 2, 2024
a6fe43d
refactor: remove extra methods
meteorlxy Jan 7, 2024
46bba07
chore: tweaks
meteorlxy Jan 7, 2024
880ccba
revert: "refactor: remove extra methods"
Mister-Hope Jan 24, 2024
1f83964
test: fix e2e test
Mister-Hope Jan 24, 2024
36e10bf
feat: merge comp and data into 1 chunk
Mister-Hope Jan 24, 2024
a7ac256
Merge branch 'main' into new-router
Mister-Hope Jan 24, 2024
986e329
fix: fix e2e
Mister-Hope Jan 24, 2024
f9bdfbd
Merge branch 'main' into new-router
Mister-Hope Jan 24, 2024
c9fd53d
fix: add missing imports
Mister-Hope Jan 24, 2024
1f1bde6
Merge branch 'main' into new-router
Mister-Hope Jan 26, 2024
30eb916
Merge branch 'main' into new-router
meteorlxy Jan 26, 2024
69fb015
chore: tweak
meteorlxy Jan 26, 2024
96decf0
chore: remove componentChunkName
Mister-Hope Jan 28, 2024
92307b7
chore: tweaks
meteorlxy Jan 31, 2024
c3ba0cb
chore: remove page key
meteorlxy Jan 31, 2024
45c85de
chore: remove page key related code
meteorlxy Jan 31, 2024
10df16c
chore: updates
meteorlxy Feb 1, 2024
f3f6e9e
chore: updates
meteorlxy Feb 2, 2024
1d7ab6c
chore: tweaks
meteorlxy Feb 2, 2024
db2a90f
chore: updates
meteorlxy Feb 2, 2024
c5a61de
chore: tweaks
meteorlxy Feb 2, 2024
521139e
fix: fix normalizePath and resolvePageRedirects
meteorlxy Feb 2, 2024
9cdca77
feat: rename pagesMap to routes, revert page meta back to route meta
meteorlxy Feb 2, 2024
41448c4
chore: tweaks
meteorlxy Feb 3, 2024
362ba4b
feat: add notFound property to resolved route
meteorlxy Feb 3, 2024
8537155
chore: tweaks
meteorlxy Feb 3, 2024
769859c
chore: tweak comments
meteorlxy Feb 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions e2e/docs/.vuepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,14 @@ export default defineUserConfig({
bundler: E2E_BUNDLER === 'webpack' ? webpackBundler() : viteBundler(),

theme: e2eTheme(),

extendsPage: (page) => {
if (page.path === '/page-data/route-meta.html') {
page.routeMeta = {
a: 1,
b: 2,
...page.routeMeta,
}
}
},
})
4 changes: 4 additions & 0 deletions e2e/docs/404.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
routeMeta:
foo: bar
---
5 changes: 5 additions & 0 deletions e2e/docs/page-data/route-meta.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
routeMeta:
a: 0
c: 3
---
43 changes: 43 additions & 0 deletions e2e/docs/router/resolve-route.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## resolve

### Path

#### Index

- Clean URL: {{ JSON.stringify(resolveRoute('/')) }}
- HTML: {{ JSON.stringify(resolveRoute('/index.html')) }}
- Markdown: {{ JSON.stringify(resolveRoute('/README.md')) }}

#### Non-Index

- Clean URL: {{ JSON.stringify(resolveRoute('/router/resolve-route')) }}
- HTML: {{ JSON.stringify(resolveRoute('/router/resolve-route.html')) }}
- Markdown: {{ JSON.stringify(resolveRoute('/router/resolve-route.md')) }}

#### Non-ASCII

- Clean URL: {{ JSON.stringify(resolveRoute('/routes/non-ascii-paths/中文目录名/中文文件名')) }}
- HTML: {{ JSON.stringify(resolveRoute('/routes/non-ascii-paths/中文目录名/中文文件名.html')) }}
- Markdown: {{ JSON.stringify(resolveRoute('/routes/non-ascii-paths/中文目录名/中文文件名.md')) }}

#### Non-ASCII Encoded

- Clean URL: {{ JSON.stringify(resolveRoute(encodeURI('/routes/non-ascii-paths/中文目录名/中文文件名'))) }}
- HTML: {{ JSON.stringify(resolveRoute(encodeURI('/routes/non-ascii-paths/中文目录名/中文文件名.html'))) }}
- Markdown: {{ JSON.stringify(resolveRoute(encodeURI('/routes/non-ascii-paths/中文目录名/中文文件名.md'))) }}

#### Non-Existent

- Clean URL: {{ JSON.stringify(resolveRoute('/non-existent')) }}
- HTML: {{ JSON.stringify(resolveRoute('/non-existent.html')) }}
- Markdown: {{ JSON.stringify(resolveRoute('/non-existent.md')) }}

#### Route Meta

- Clean URL: {{ JSON.stringify(resolveRoute('/page-data/route-meta')) }}
- HTML: {{ JSON.stringify(resolveRoute('/page-data/route-meta.html')) }}
- Markdown: {{ JSON.stringify(resolveRoute('/page-data/route-meta.md')) }}

<script setup>
import { resolveRoute } from 'vuepress/client'
</script>
66 changes: 66 additions & 0 deletions e2e/tests/router/resolve-route.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const testCases = [
{
selector: '#index',
expected: {
path: '/',
meta: {},
notFound: false,
},
},
{
selector: '#non-index',
expected: {
path: '/router/resolve-route.html',
meta: {},
notFound: false,
},
},
{
selector: '#non-ascii',
expected: {
path: encodeURI('/routes/non-ascii-paths/中文目录名/中文文件名.html'),
meta: {},
notFound: false,
},
},
{
selector: '#non-ascii-encoded',
expected: {
path: encodeURI('/routes/non-ascii-paths/中文目录名/中文文件名.html'),
meta: {},
notFound: false,
},
},
{
selector: '#non-existent',
expected: {
path: '/non-existent.html',
meta: { foo: 'bar' },
notFound: true,
},
},
{
selector: '#route-meta',
expected: {
path: '/page-data/route-meta.html',
meta: { a: 0, b: 2, c: 3 },
notFound: false,
},
},
]

const parseResolvedRouteFromElement = (el: Cypress.JQueryWithSelector) =>
JSON.parse(/: (\{.*\})\s*$/.exec(el.text())![1])

it('should resolve routes correctly', () => {
cy.visit('/router/resolve-route.html')

testCases.forEach(({ selector, expected }) => {
cy.get(`.e2e-theme-content ${selector} + ul > li`).each((el) => {
const resolvedRoute = parseResolvedRouteFromElement(el)
expect(resolvedRoute.path).to.equal(expected.path)
expect(resolvedRoute.meta).to.deep.equal(expected.meta)
expect(resolvedRoute.notFound).to.equal(expected.notFound)
})
})
})
4 changes: 1 addition & 3 deletions packages/bundler-vite/src/build/resolvePageChunkFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ export const resolvePageChunkFiles = ({
output
.filter(
(item): item is OutputChunk =>
item.type === 'chunk' &&
(item.facadeModuleId === page.componentFilePath ||
item.facadeModuleId === page.dataFilePath),
item.type === 'chunk' && item.facadeModuleId === page.chunkFilePath,
)
.flatMap(({ fileName, imports, dynamicImports }) => [
fileName,
Expand Down
14 changes: 5 additions & 9 deletions packages/cli/src/commands/dev/handlePageAdd.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import {
createPage,
preparePageChunk,
preparePageComponent,
preparePageData,
preparePagesComponents,
preparePagesData,
preparePagesRoutes,
prepareRoutes,
} from '@vuepress/core'
import type { App, Page } from '@vuepress/core'

Expand Down Expand Up @@ -33,12 +31,10 @@ export const handlePageAdd = async (

// prepare page files
await preparePageComponent(app, page)
await preparePageData(app, page)
await preparePageChunk(app, page)

// prepare pages entry
await preparePagesComponents(app)
await preparePagesData(app)
await preparePagesRoutes(app)
// prepare routes file
await prepareRoutes(app)

return page
}
18 changes: 5 additions & 13 deletions packages/cli/src/commands/dev/handlePageChange.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import {
createPage,
preparePageChunk,
preparePageComponent,
preparePageData,
preparePagesComponents,
preparePagesData,
preparePagesRoutes,
prepareRoutes,
} from '@vuepress/core'
import type { App, Page } from '@vuepress/core'

Expand Down Expand Up @@ -36,21 +34,15 @@ export const handlePageChange = async (

// prepare page files
await preparePageComponent(app, pageNew)
await preparePageData(app, pageNew)
await preparePageChunk(app, pageNew)

const isPathChanged = pageOld.path !== pageNew.path
const isRouteMetaChanged =
JSON.stringify(pageOld.routeMeta) !== JSON.stringify(pageNew.routeMeta)

// prepare pages entry if the path is changed
if (isPathChanged) {
await preparePagesComponents(app)
await preparePagesData(app)
}

// prepare pages routes if the path or routeMeta is changed
// prepare routes file if the path or route meta is changed
if (isPathChanged || isRouteMetaChanged) {
await preparePagesRoutes(app)
await prepareRoutes(app)
}

return [pageOld, pageNew]
Expand Down
12 changes: 3 additions & 9 deletions packages/cli/src/commands/dev/handlePageUnlink.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
preparePagesComponents,
preparePagesData,
preparePagesRoutes,
} from '@vuepress/core'
import { prepareRoutes } from '@vuepress/core'
import type { App, Page } from '@vuepress/core'

/**
Expand All @@ -25,10 +21,8 @@ export const handlePageUnlink = async (
// remove the old page
app.pages.splice(pageIndex, 1)

// re-prepare page files
await preparePagesComponents(app)
await preparePagesData(app)
await preparePagesRoutes(app)
// re-prepare routes file
await prepareRoutes(app)

return page
}
4 changes: 1 addition & 3 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@
"external": [
"@internal/clientConfigs",
"@internal/layoutComponents",
"@internal/pagesComponents",
"@internal/pagesData",
"@internal/pagesRoutes",
"@internal/routes",
"@internal/siteData"
],
"format": [
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { clientConfigs } from '@internal/clientConfigs'
import { createApp, createSSRApp, h } from 'vue'
import { RouterView } from 'vue-router'
import { siteData } from './composables/index.js'
import { createVueRouter } from './router.js'
import { createVueRouter } from './createVueRouter.js'
import { setupGlobalComponents } from './setupGlobalComponents.js'
import { setupGlobalComputed } from './setupGlobalComputed.js'
import { setupUpdateHead } from './setupUpdateHead.js'
Expand Down
28 changes: 10 additions & 18 deletions packages/client/src/components/Content.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { pagesComponents } from '@internal/pagesComponents'
import { computed, defineComponent, h } from 'vue'
import { computed, defineAsyncComponent, defineComponent, h } from 'vue'
import { usePageData } from '../composables/index.js'
import { resolveRoute } from '../router/index.js'

/**
* Markdown rendered content
Expand All @@ -10,28 +10,20 @@ export const Content = defineComponent({
name: 'Content',

props: {
pageKey: {
path: {
type: String,
required: false,
default: '',
},
},

setup(props) {
const page = usePageData()
const pageComponent = computed(
() => pagesComponents[props.pageKey || page.value.key],
)
return () =>
pageComponent.value
? // use page component
h(pageComponent.value)
: // fallback content
h(
'div',
__VUEPRESS_DEV__
? 'Page does not exist. This is a fallback content.'
: '404 Not Found',
)
const pageData = usePageData()
const pageComponent = computed(() => {
const route = resolveRoute(props.path || pageData.value.path)
return defineAsyncComponent(() => route.loader().then(({ comp }) => comp))
})

return () => h(pageComponent.value)
},
})
53 changes: 53 additions & 0 deletions packages/client/src/components/VPLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { h } from 'vue'
import type { FunctionalComponent, VNode } from 'vue'
import { useRouter } from 'vue-router'
import { withBase } from '../helpers/index.js'
import { resolveRoutePath } from '../router/index.js'

/**
* Forked from https://github.com/vuejs/router/blob/941b2131e80550009e5221d4db9f366b1fea3fd5/packages/router/src/RouterLink.ts#L293
*/
const guardEvent = (event: MouseEvent): boolean | void => {
// don't redirect with control keys
if (event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) return
// don't redirect when preventDefault called
if (event.defaultPrevented) return
// don't redirect on right click
if (event.button !== undefined && event.button !== 0) return
// don't redirect if `target="_blank"`
if (event.currentTarget) {
const target = (event.currentTarget as HTMLElement).getAttribute('target')
if (target?.match(/\b_blank\b/i)) return
}
event.preventDefault()
return true
}

export interface VPLinkProps {
to: string
}

export const VPLink: FunctionalComponent<
VPLinkProps,
Record<never, never>,
{
default: () => string | VNode | (string | VNode)[]
}
> = ({ to = '' }, { slots }) => {
const router = useRouter()
const path = withBase(resolveRoutePath(to))

return h(
'a',
{
class: 'vp-link',
href: path,
onClick: (event: MouseEvent = {} as MouseEvent) => {
guardEvent(event) ? router.push(to).catch() : Promise.resolve()
},
},
slots.default?.(),
)
}

VPLink.displayName = 'VPLink'
1 change: 1 addition & 0 deletions packages/client/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './ClientOnly.js'
export * from './Content.js'
export * from './VPLink.js'
2 changes: 1 addition & 1 deletion packages/client/src/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export * from './pageHead.js'
export * from './pageHeadTitle.js'
export * from './pageLang.js'
export * from './pageLayout.js'
export * from './pagesData.js'
export * from './routeLocale.js'
export * from './routes.js'
export * from './siteData.js'
export * from './siteLocaleData.js'
export * from './updateHead.js'
14 changes: 1 addition & 13 deletions packages/client/src/composables/pageData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { PageData } from '@vuepress/shared'
import type { InjectionKey, Ref } from 'vue'
import { inject, readonly } from 'vue'
import { inject } from 'vue'

export type { PageData }

Expand All @@ -17,18 +17,6 @@ export const pageDataSymbol: InjectionKey<PageDataRef> = Symbol(
__VUEPRESS_DEV__ ? 'pageData' : '',
)

/**
* Empty page data to be used as the fallback value
*/
export const pageDataEmpty = readonly({
key: '',
path: '',
title: '',
lang: '',
frontmatter: {},
headers: [],
} as PageData) as PageData

/**
* Returns the ref of the data of current page
*/
Expand Down
Loading
Loading