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:
翊小久 2024-02-04 02:46:03 +08:00 committed by GitHub
parent 5d21eea023
commit 0ca8f97aee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 226 additions and 54 deletions

View File

@ -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`)
})
})

View File

@ -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>>,

View File

@ -0,0 +1,6 @@
---
title: 重定向
from:
- /redirect/old1
- /redirect/old2
---

View File

@ -145,11 +145,14 @@ export default defineSiteConfig({
enable: true,
},
redirects: [
{
from: '/foo',
to: '/about',
},
],
redirects: {
useVueRouter: true,
rules: [
{
from: '/foo',
to: '/about',
},
],
},
})

View File

@ -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"}

View File

@ -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)
},
)

View File

@ -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`)

View File

@ -97,6 +97,11 @@ export const defaultSiteConfig: SiteConfig = {
salt: webcrypto.getRandomValues(new Uint8Array(16)),
iv: webcrypto.getRandomValues(new Uint8Array(16)),
},
redirects: {
useVueRouter: true,
rules: [],
},
}
/**

View File

@ -26,7 +26,13 @@ export const defaultValaxyConfig: ValaxyNodeConfig = {
// markdown: {
// excerpt: '<!-- more -->',
// },
runtimeConfig: { addons: {} },
runtimeConfig: {
addons: {},
redirects: {
useVueRouter: true,
redirectRoutes: [],
},
},
modules: {
rss: {

View File

@ -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)

View File

@ -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({

View File

@ -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,

View File

@ -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({

View File

@ -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
}

View File

@ -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 {

View File

@ -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 {