mirror of https://github.com/YunYouJun/valaxy
feat: client redirects at vue router level (#336)
* feat: client redirects at vue router level * refactor: remove /@valaxyjs/redirects virtual module
This commit is contained in:
parent
5d21eea023
commit
0ca8f97aee
|
@ -0,0 +1,25 @@
|
|||
context('Client Redirect', {
|
||||
baseUrl: Cypress.env('theme-yun'),
|
||||
}, () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('/redirect/old1', () => {
|
||||
cy.visit('/redirect/old1')
|
||||
|
||||
cy.url().should('eq', `${Cypress.env('theme-yun')}posts/redirect`)
|
||||
})
|
||||
|
||||
it('/redirect/old2', () => {
|
||||
cy.visit('/redirect/old2')
|
||||
|
||||
cy.url().should('eq', `${Cypress.env('theme-yun')}posts/redirect`)
|
||||
})
|
||||
|
||||
it('/foo', () => {
|
||||
cy.visit('/foo')
|
||||
|
||||
cy.url().should('eq', `${Cypress.env('theme-yun')}about`)
|
||||
})
|
||||
})
|
|
@ -87,6 +87,7 @@ declare module 'vue-router/auto/routes' {
|
|||
'/posts/lots-of-images': RouteRecordInfo<'/posts/lots-of-images', '/posts/lots-of-images', Record<never, never>, Record<never, never>>,
|
||||
'/posts/markdown': RouteRecordInfo<'/posts/markdown', '/posts/markdown', Record<never, never>, Record<never, never>>,
|
||||
'/posts/post-updated': RouteRecordInfo<'/posts/post-updated', '/posts/post-updated', Record<never, never>, Record<never, never>>,
|
||||
'/posts/redirect': RouteRecordInfo<'/posts/redirect', '/posts/redirect', Record<never, never>, Record<never, never>>,
|
||||
'/posts/test': RouteRecordInfo<'/posts/test', '/posts/test', Record<never, never>, Record<never, never>>,
|
||||
'/posts/test-images': RouteRecordInfo<'/posts/test-images', '/posts/test-images', Record<never, never>, Record<never, never>>,
|
||||
'/posts/test-tags': RouteRecordInfo<'/posts/test-tags', '/posts/test-tags', Record<never, never>, Record<never, never>>,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 重定向
|
||||
from:
|
||||
- /redirect/old1
|
||||
- /redirect/old2
|
||||
---
|
|
@ -145,11 +145,14 @@ export default defineSiteConfig({
|
|||
enable: true,
|
||||
},
|
||||
|
||||
redirects: [
|
||||
{
|
||||
from: '/foo',
|
||||
to: '/about',
|
||||
},
|
||||
],
|
||||
redirects: {
|
||||
useVueRouter: true,
|
||||
rules: [
|
||||
{
|
||||
from: '/foo',
|
||||
to: '/about',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
})
|
||||
|
|
|
@ -575,28 +575,23 @@ Examples can be found in [Partial Content Encryption](/examples/partial-content-
|
|||
|
||||
### Client Redirects {lang="en"}
|
||||
|
||||
::: zh-CN
|
||||
这会生成额外的 HTML 页面,用与跳转到 valaxy 中已有的页面。
|
||||
:::
|
||||
|
||||
::: en
|
||||
This will generate additional HTML pages, used to jump to the valaxy's existing pages.
|
||||
:::
|
||||
|
||||
::: tip
|
||||
|
||||
<div lang="zh-CN">
|
||||
客户端重定向只在 SSG build 时启用
|
||||
</div>
|
||||
|
||||
<div lang="en">
|
||||
Client redirects will only be enabled in SSG build
|
||||
</div>
|
||||
|
||||
:::
|
||||
```ts
|
||||
interface Redirects {
|
||||
// https://router.vuejs.org/guide/essentials/redirect-and-alias.html
|
||||
// Whether to use VueRouter, default is true
|
||||
useVueRouter?: boolean
|
||||
rules?: RedirectRule[]
|
||||
}
|
||||
interface RedirectRule {
|
||||
// Redirect original route
|
||||
from: string | string[]
|
||||
// Redirect target route
|
||||
to: string
|
||||
}
|
||||
```
|
||||
|
||||
::: zh-CN
|
||||
例如:
|
||||
示例:
|
||||
:::
|
||||
|
||||
::: en
|
||||
|
@ -606,16 +601,19 @@ For example:
|
|||
```ts
|
||||
// site.config.ts
|
||||
export default defineSiteConfig({
|
||||
redirects: [
|
||||
{
|
||||
from: ['/foo', '/bar'],
|
||||
to: '/about',
|
||||
},
|
||||
{
|
||||
from: '/v1/about',
|
||||
to: '/about',
|
||||
},
|
||||
],
|
||||
redirects: {
|
||||
useVueRouter: true,
|
||||
rules: [
|
||||
{
|
||||
from: ['/foo', '/bar'],
|
||||
to: '/about',
|
||||
},
|
||||
{
|
||||
from: '/v1/about',
|
||||
to: '/about',
|
||||
},
|
||||
]
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
|
@ -627,6 +625,50 @@ export default defineSiteConfig({
|
|||
`/foo`, `/bar`, `/v1/about` these routes will be redirected to `/about`。
|
||||
:::
|
||||
|
||||
::: zh-CN
|
||||
你也可以在 Front Matter 中配置:
|
||||
:::
|
||||
|
||||
::: en
|
||||
You can also set it in the Front Matter:
|
||||
:::
|
||||
|
||||
```md
|
||||
<!-- pages/posts/redirect.md -->
|
||||
---
|
||||
from:
|
||||
- /redirect/old1
|
||||
- /redirect/old2
|
||||
---
|
||||
```
|
||||
|
||||
```md
|
||||
<!-- pages/posts/redirect.md -->
|
||||
---
|
||||
from: /v1/redirect
|
||||
---
|
||||
```
|
||||
|
||||
::: zh-CN
|
||||
`/redirect/old1`, `/redirect/old2`, `/v1/redirect` 这些路由会被重定向到 `/posts/redirect`。
|
||||
:::
|
||||
|
||||
::: en
|
||||
`/redirect/old1`, `/redirect/old2`, `/v1/redirect` these routes will be redirected to `/posts/redirect`。
|
||||
:::
|
||||
|
||||
::: tip
|
||||
|
||||
<div lang="zh-CN">
|
||||
在 SSG 构建时,如果 useVueRouter 为 false,则会为每一个源路由生成一个 html 文件
|
||||
</div>
|
||||
|
||||
<div lang="en">
|
||||
When building SSG, if useVueRouter is false, an html file will be generated for each original route
|
||||
</div>
|
||||
|
||||
:::
|
||||
|
||||
### 图片预览(Medium Zoom) {lang="zh-CN"}
|
||||
|
||||
### Image Preview (Medium Zoom) {lang="en"}
|
||||
|
|
|
@ -21,6 +21,8 @@ import 'uno.css'
|
|||
|
||||
import setupMain from './setup/main'
|
||||
|
||||
const valaxyConfig = initValaxyConfig()
|
||||
|
||||
/**
|
||||
* register global components
|
||||
* @param ctx
|
||||
|
@ -29,9 +31,13 @@ export function registerComponents(ctx: ViteSSGContext) {
|
|||
ctx.app.component('AppLink', AppLink)
|
||||
}
|
||||
|
||||
const { redirectRoutes, useVueRouter } = valaxyConfig.value.runtimeConfig.redirects
|
||||
if (useVueRouter)
|
||||
routes.push(...redirectRoutes)
|
||||
|
||||
// fix chinese path
|
||||
routes.forEach((i) => {
|
||||
i.children?.forEach((j) => {
|
||||
i?.children?.forEach((j) => {
|
||||
j.path = encodeURI(j.path)
|
||||
})
|
||||
})
|
||||
|
@ -66,10 +72,10 @@ export const createApp = ViteSSG(
|
|||
(ctx) => {
|
||||
// app-level provide
|
||||
const { app } = ctx
|
||||
const config = initValaxyConfig()
|
||||
app.provide(valaxyConfigSymbol, config)
|
||||
|
||||
app.provide(valaxyConfigSymbol, valaxyConfig)
|
||||
|
||||
registerComponents(ctx)
|
||||
setupMain(ctx, config)
|
||||
setupMain(ctx, valaxyConfig)
|
||||
},
|
||||
)
|
||||
|
|
|
@ -76,12 +76,14 @@ export async function postProcessForSSG(options: ResolvedValaxyOptions) {
|
|||
}
|
||||
}
|
||||
|
||||
await generateClientRedirects(options)
|
||||
if (!options.config.siteConfig.redirects?.useVueRouter)
|
||||
await generateClientRedirects(options)
|
||||
}
|
||||
|
||||
export async function generateClientRedirects(options: ResolvedValaxyOptions) {
|
||||
consola.info('generate client redirects...')
|
||||
const outputPath = resolve(options.userRoot, 'dist')
|
||||
const redirectRules = collectRedirects(options.config.siteConfig?.redirects ?? [])
|
||||
const redirectRules = collectRedirects(options.redirects)
|
||||
|
||||
const task = redirectRules.map(async (rule) => {
|
||||
const fromPath = join(outputPath, `${rule.from}.html`)
|
||||
|
|
|
@ -97,6 +97,11 @@ export const defaultSiteConfig: SiteConfig = {
|
|||
salt: webcrypto.getRandomValues(new Uint8Array(16)),
|
||||
iv: webcrypto.getRandomValues(new Uint8Array(16)),
|
||||
},
|
||||
|
||||
redirects: {
|
||||
useVueRouter: true,
|
||||
rules: [],
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,7 +26,13 @@ export const defaultValaxyConfig: ValaxyNodeConfig = {
|
|||
// markdown: {
|
||||
// excerpt: '<!-- more -->',
|
||||
// },
|
||||
runtimeConfig: { addons: {} },
|
||||
runtimeConfig: {
|
||||
addons: {},
|
||||
redirects: {
|
||||
useVueRouter: true,
|
||||
redirectRoutes: [],
|
||||
},
|
||||
},
|
||||
|
||||
modules: {
|
||||
rss: {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { ensureSuffix, uniq } from '@antfu/utils'
|
|||
import defu from 'defu'
|
||||
import { blue, cyan, magenta, yellow } from 'picocolors'
|
||||
import consola from 'consola'
|
||||
import type { DefaultTheme, RuntimeConfig } from 'valaxy/types'
|
||||
import type { DefaultTheme, RedirectItem, RuntimeConfig } from 'valaxy/types'
|
||||
import { resolveImportPath } from './utils'
|
||||
import {
|
||||
defaultValaxyConfig,
|
||||
|
@ -23,6 +23,7 @@ import { parseAddons } from './utils/addons'
|
|||
import { getThemeRoot } from './utils/theme'
|
||||
import { resolveSiteConfig } from './config/site'
|
||||
import { countPerformanceTime } from './utils/performance'
|
||||
import { collectRedirects } from './utils/clientRedirects'
|
||||
|
||||
// for cli entry
|
||||
export interface ValaxyEntryOptions {
|
||||
|
@ -88,6 +89,10 @@ export interface ResolvedValaxyOptions<ThemeConfig = DefaultTheme.Config> {
|
|||
* Record<package-name, OptionResolver>
|
||||
*/
|
||||
addons: ValaxyAddonResolver[]
|
||||
/**
|
||||
* Collect redirect rule
|
||||
*/
|
||||
redirects: RedirectItem[]
|
||||
}
|
||||
|
||||
export interface ValaxyServerOptions {
|
||||
|
@ -137,6 +142,10 @@ export async function processValaxyOptions(valaxyOptions: ResolvedValaxyOptions,
|
|||
...config,
|
||||
runtimeConfig: {
|
||||
addons: {},
|
||||
redirects: {
|
||||
useVueRouter: true,
|
||||
redirectRoutes: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
valaxyOptions.addons = addons
|
||||
|
@ -190,6 +199,8 @@ export async function resolveOptions(
|
|||
|
||||
const { config: themeConfig, configFile: themeConfigFile } = resolvedTheme
|
||||
|
||||
const redirects = collectRedirects(siteConfig.redirects?.rules)
|
||||
|
||||
// merge with valaxy
|
||||
userValaxyConfig = defu<ValaxyNodeConfig, any>({ siteConfig }, { themeConfig }, userValaxyConfig)
|
||||
|
||||
|
@ -213,13 +224,20 @@ export async function resolveOptions(
|
|||
theme,
|
||||
config: {
|
||||
...userValaxyConfig,
|
||||
runtimeConfig: { addons: {} },
|
||||
runtimeConfig: {
|
||||
addons: {},
|
||||
redirects: {
|
||||
useVueRouter: true,
|
||||
redirectRoutes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
configFile: configFile || '',
|
||||
siteConfigFile: siteConfigFile || '',
|
||||
themeConfigFile: themeConfigFile || '',
|
||||
pages: pages.sort(),
|
||||
addons: [],
|
||||
redirects,
|
||||
}
|
||||
debug(valaxyOptions)
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import { defu } from 'defu'
|
|||
import pascalCase from 'pascalcase'
|
||||
import type { DefaultTheme, PageDataPayload, Pkg, SiteConfig } from 'valaxy/types'
|
||||
import { dim, yellow } from 'picocolors'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { defaultSiteConfig, mergeValaxyConfig, resolveSiteConfig, resolveUserThemeConfig } from '../config'
|
||||
import type { ResolvedValaxyOptions, ValaxyServerOptions } from '../options'
|
||||
import { processValaxyOptions, resolveOptions, resolveThemeValaxyConfig } from '../options'
|
||||
|
@ -19,6 +20,22 @@ import type { ValaxyNodeConfig } from '../types'
|
|||
import { checkMd } from '../markdown/check'
|
||||
import { vLogger } from '../logger'
|
||||
import { countPerformanceTime } from '../utils/performance'
|
||||
import { isProd } from '../utils/env'
|
||||
|
||||
function generateConfig(options: ResolvedValaxyOptions) {
|
||||
const routes = options.redirects.map<RouteRecordRaw>((redirect) => {
|
||||
return {
|
||||
path: redirect.from,
|
||||
redirect: redirect.to,
|
||||
}
|
||||
})
|
||||
options.config.runtimeConfig.redirects = {
|
||||
useVueRouter: isProd() ? options.config.siteConfig.redirects!.useVueRouter! : true,
|
||||
redirectRoutes: routes,
|
||||
}
|
||||
|
||||
return `export default ${JSON.stringify(JSON.stringify(options.config))}`
|
||||
}
|
||||
|
||||
/**
|
||||
* for /@valaxyjs/styles
|
||||
|
@ -164,7 +181,7 @@ export function createValaxyPlugin(options: ResolvedValaxyOptions, serverOptions
|
|||
load(id) {
|
||||
if (id === '/@valaxyjs/config')
|
||||
// stringify twice for \"
|
||||
return `export default ${JSON.stringify(JSON.stringify(valaxyConfig))}`
|
||||
return generateConfig(options)
|
||||
|
||||
if (id === '/@valaxyjs/context') {
|
||||
return `export default ${JSON.stringify(JSON.stringify({
|
||||
|
|
|
@ -114,6 +114,23 @@ export function createRouterPlugin(options: ResolvedValaxyOptions) {
|
|||
if (!isDate(mdFm.updated))
|
||||
mdFm.updated = new Date(mdFm.updated!)
|
||||
|
||||
if (mdFm.from) {
|
||||
if (Array.isArray(mdFm.from)) {
|
||||
mdFm.from.forEach((from) => {
|
||||
options.redirects.push({
|
||||
from,
|
||||
to: route.fullPath,
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
options.redirects.push({
|
||||
from: mdFm.from,
|
||||
to: route.fullPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// set route meta
|
||||
route.addToMeta({
|
||||
frontmatter: mdFm,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { writeFile } from 'node:fs/promises'
|
||||
import { ensureFile } from 'fs-extra'
|
||||
import type { RedirectRule } from 'valaxy/types'
|
||||
import type { RedirectItem, RedirectRule } from 'valaxy/types'
|
||||
|
||||
function handleRoute(route: string) {
|
||||
if (route === '/')
|
||||
|
@ -10,14 +10,12 @@ function handleRoute(route: string) {
|
|||
return route
|
||||
}
|
||||
|
||||
interface RedirectItem {
|
||||
from: string
|
||||
to: string
|
||||
}
|
||||
export function collectRedirects(redirectRules?: RedirectRule[]): RedirectItem[] {
|
||||
if (!redirectRules)
|
||||
return []
|
||||
|
||||
export function collectRedirects(redirectRule: RedirectRule[]) {
|
||||
const redirects: RedirectItem[] = []
|
||||
for (const rule of redirectRule) {
|
||||
for (const rule of redirectRules) {
|
||||
if (Array.isArray(rule.from)) {
|
||||
for (const from of rule.from) {
|
||||
redirects.push({
|
||||
|
|
|
@ -60,3 +60,11 @@ declare module 'vue-router' {
|
|||
frontmatter: Post
|
||||
}
|
||||
}
|
||||
|
||||
declare module '/@valaxyjs/redirects' {
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export const redirectRoutes: RouteRecordRaw[]
|
||||
|
||||
export const useVueRouter: boolean
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { ZoomOptions } from 'medium-zoom'
|
||||
import type { FuseOptions } from '@vueuse/integrations/useFuse'
|
||||
import type { ILazyLoadOptions } from 'vanilla-lazyload'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import type { ValaxyAddon } from '../types'
|
||||
import type { DefaultTheme } from './default-theme'
|
||||
import type { PostFrontMatter } from './posts'
|
||||
|
@ -27,6 +28,11 @@ export interface RedirectRule {
|
|||
from: string | string[]
|
||||
}
|
||||
|
||||
export interface RedirectItem {
|
||||
from: string
|
||||
to: string
|
||||
}
|
||||
|
||||
// shared with valaxy node and client
|
||||
export interface SiteConfig {
|
||||
/**
|
||||
|
@ -323,7 +329,10 @@ export interface SiteConfig {
|
|||
* @description:en-US client redirect rules
|
||||
* @description:zh-CN 客户端重定向规则
|
||||
*/
|
||||
redirects?: RedirectRule[]
|
||||
redirects?: {
|
||||
useVueRouter?: boolean
|
||||
rules?: RedirectRule[]
|
||||
}
|
||||
}
|
||||
|
||||
export type PartialDeep<T> = {
|
||||
|
@ -335,6 +344,10 @@ export type PartialDeep<T> = {
|
|||
*/
|
||||
export interface RuntimeConfig {
|
||||
addons: Record<string, ValaxyAddon>
|
||||
redirects: {
|
||||
useVueRouter: boolean
|
||||
redirectRoutes: RouteRecordRaw[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface Pkg {
|
||||
|
|
|
@ -176,6 +176,11 @@ export interface PageFrontMatter extends Record<string, any> {
|
|||
* @description:zh-CN 限制代码块的高度,单位是 px
|
||||
*/
|
||||
codeHeightLimit?: number
|
||||
/**
|
||||
* @description:en-US Source path for client redirection
|
||||
* @description:zh-CN 客户端重定向的源路径
|
||||
*/
|
||||
from?: string | string[]
|
||||
}
|
||||
|
||||
export interface PostFrontMatter extends PageFrontMatter {
|
||||
|
|
Loading…
Reference in New Issue