feat: support client redirect (#314)

* feat: valaxy-addon-client-redirects

* feat: move client redirect feature into valaxy
This commit is contained in:
翊小久 2024-01-13 23:21:58 +08:00 committed by GitHub
parent 5d9b43a788
commit 615f0c5a89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 158 additions and 1 deletions

View File

@ -144,4 +144,11 @@ export default defineSiteConfig({
encrypt: { encrypt: {
enable: true, enable: true,
}, },
redirects: [
{
from: '/foo',
to: '/about',
},
],
}) })

View File

@ -539,7 +539,15 @@ password: your_password
::: :::
::: tip ::: tip
<div lang="zh-CN">
如果在文章的 Front Matter 中设置了 `password`,文章中的部分加密将被忽略。
</div>
<div lang="en">
If you set `password` in Front Matter, partial encryption will be ignored. If you set `password` in Front Matter, partial encryption will be ignored.
</div>
::: :::
::: zh-CN ::: zh-CN
@ -558,6 +566,62 @@ Wrap content to be encrypted in `<!-- valaxy-encrypt-start:your_password --><!--
Examples can be found in [Partial Content Encryption](/examples/partial-content-encryption)。 Examples can be found in [Partial Content Encryption](/examples/partial-content-encryption)。
::: :::
### 客户端重定向 {lang="zh-CN"}
### 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>
:::
::: zh-CN
例如:
:::
::: en
For example:
:::
```ts
// site.config.ts
export default defineSiteConfig({
redirects: [
{
from: ['/foo', '/bar'],
to: '/about',
},
{
from: '/v1/about',
to: '/about',
},
],
})
```
::: zh-CN
`/foo`, `/bar`, `/v1/about` 这些路由会被重定向到 `/about`
:::
::: en
`/foo`, `/bar`, `/v1/about` these routes will be redirected to `/about`
:::
## 主题配置 {lang="zh-CN"} ## 主题配置 {lang="zh-CN"}
## Theme Config {lang="en"} ## Theme Config {lang="en"}

View File

@ -1,4 +1,4 @@
import { resolve } from 'node:path' import { join, resolve } from 'node:path'
import type { InlineConfig } from 'vite' import type { InlineConfig } from 'vite'
import { mergeConfig as mergeViteConfig, build as viteBuild } from 'vite' import { mergeConfig as mergeViteConfig, build as viteBuild } from 'vite'
import { build as viteSsgBuild } from 'vite-ssg/node' import { build as viteSsgBuild } from 'vite-ssg/node'
@ -8,6 +8,7 @@ import fs from 'fs-extra'
import consola from 'consola' import consola from 'consola'
import type { ResolvedValaxyOptions } from './options' import type { ResolvedValaxyOptions } from './options'
import { ViteValaxyPlugins } from './plugins/preset' import { ViteValaxyPlugins } from './plugins/preset'
import { collectRedirects, writeRedirectFiles } from './utils/clientRedirects'
export async function build( export async function build(
options: ResolvedValaxyOptions, options: ResolvedValaxyOptions,
@ -55,6 +56,7 @@ export async function ssgBuild(
/** /**
* post process for ssg fix extra string like `/html>` `ml>` `l>` * post process for ssg fix extra string like `/html>` `ml>` `l>`
* handle tasks after ssg build
* todo find why * todo find why
* @param options * @param options
*/ */
@ -72,4 +74,22 @@ export async function postProcessForSSG(options: ResolvedValaxyOptions) {
await fs.writeFile(indexPath, indexFile.slice(0, htmlTagStart + htmlTag.length), 'utf-8') await fs.writeFile(indexPath, indexFile.slice(0, htmlTagStart + htmlTag.length), 'utf-8')
} }
} }
await generateClientRedirects(options)
}
export async function generateClientRedirects(options: ResolvedValaxyOptions) {
const outputPath = resolve(options.userRoot, 'dist')
const redirectRules = collectRedirects(options.config.siteConfig?.redirects ?? [])
const task = redirectRules.map(async (rule) => {
const fromPath = join(outputPath, `${rule.from}.html`)
const toPath = join(outputPath, `${rule.to}.html`)
const routeExist = await fs.pathExists(toPath)
if (!routeExist)
throw new Error(`the route of '${rule.to}' not exists`)
await writeRedirectFiles(rule.to, fromPath)
})
await Promise.all(task)
} }

View File

@ -0,0 +1,55 @@
import { writeFile } from 'node:fs/promises'
import { ensureFile } from 'fs-extra'
import type { RedirectRule } from '../../types'
function handleRoute(route: string) {
if (route === '/')
return '/index'
if (route.endsWith('/'))
return route.slice(0, -1)
return route
}
interface RedirectItem {
from: string
to: string
}
export function collectRedirects(redirectRule: RedirectRule[]) {
const redirects: RedirectItem[] = []
for (const rule of redirectRule) {
if (Array.isArray(rule.from)) {
for (const from of rule.from) {
redirects.push({
from: handleRoute(from),
to: handleRoute(rule.to),
})
}
}
else {
redirects.push({
from: handleRoute(rule.from),
to: handleRoute(rule.to),
})
}
}
return redirects
}
export async function writeRedirectFiles(route: string, filePath: string) {
await ensureFile(filePath)
await writeFile(filePath, `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0; url=${route}">
<link rel="canonical" href="${route}">
</head>
<script>
window.location.href = '${route}' + window.location.search + window.location.hash
</script>
</html>
`)
}

View File

@ -22,6 +22,11 @@ export interface SocialLink {
color: string color: string
} }
export interface RedirectRule {
to: string
from: string | string[]
}
// shared with valaxy node and client // shared with valaxy node and client
export interface SiteConfig { export interface SiteConfig {
/** /**
@ -313,6 +318,12 @@ export interface SiteConfig {
* @description:zh-CN px * @description:zh-CN px
*/ */
codeHeightLimit?: number codeHeightLimit?: number
/**
* @description:en-US client redirect rules
* @description:zh-CN
*/
redirects?: RedirectRule[]
} }
export type PartialDeep<T> = { export type PartialDeep<T> = {