# VALAXY > Valaxy is a next-generation static blog framework powered by Vue, Vite, and TypeScript. ## Math 渲染引擎评估与加载策略 - **Date**: 2026-02-23 - **Tags**: performance, katex, mathjax, dev-notes ## 背景 Valaxy 需要支持 Markdown 中的数学公式渲染。此前仅支持 KaTeX,VitePress 则选择了 MathJax3。本文评估两种引擎的优劣,以及 KaTeX 的加载策略。 ## KaTeX vs MathJax3 对比 ### 渲染性能 | 维度 | KaTeX | MathJax3 | |------|-------|----------| | 渲染速度 | 极快(专为速度优化) | 较慢(功能更全面) | | 渲染位置 | Node 端构建时渲染为 HTML | Node 端构建时渲染为 SVG | | 客户端 JS | 零(构建时已渲染完成) | 零(构建时已渲染完成) | 两者在 Valaxy 中都是**构建时渲染**,不依赖客户端 JS,因此运行时性能差异不大。 ### 输出格式与依赖 | 维度 | KaTeX | MathJax3 | |------|-------|----------| | **输出格式** | HTML + CSS spans | **SVG**(自包含矢量图) | | **外部 CSS** | 需要 `katex.min.css`(~1.2KB gzip) | **无** | | **字体文件** | ~20 个 woff2(浏览器按需加载) | **无**(SVG 内嵌字形) | | **FOUC 风险** | CSS 未加载前有闪烁风险 | **无**(SVG 自包含) | | **按需特性** | 需要全局加载 CSS | **天然按需**——无公式页面零开销 | ### 功能完整性 | 维度 | KaTeX | MathJax3 | |------|-------|----------| | LaTeX 覆盖度 | 大部分常用命令 | 更全面,支持更多扩展 | | 交换图/XyJax | 不支持 | 支持(通过 XyJax-v3) | | `\ce{}` 化学 | 需要额外扩展 | 内置支持 | | `\cancel`/`\xcancel` | 支持 | 支持 | | 自定义宏 | 支持 | 支持(更灵活) | | 可访问性 | MathML 输出 | MathML + SVG | ### 依赖体积(npm 包大小) | | KaTeX | MathJax3 (`markdown-it-mathjax3`) | |---|---|---| | 安装大小 | `katex`: ~3.5MB(含字体) | `markdown-it-mathjax3@4`: ~40MB(`mathjax-full`) | | 客户端影响 | ~1.2KB CSS(gzip) | 零 | > MathJax 的 npm 安装体积更大,但这仅影响 `node_modules`,不影响客户端产物。 ### 为什么 VitePress 选择 MathJax3? 1. **零运行时依赖**:SVG 内联在 HTML 中,无需 CSS/字体/JS 2. **天然按需**:无公式页面完全零开销 3. **通用文档工具**:大多数文档站不使用数学公式,MathJax 的 SVG 方案确保零影响 4. **`math: false` 默认关闭**:仅需要时才安装和启用 ### Valaxy 的选择:KaTeX + MathJax 分离配置 Valaxy 通过两个独立配置分别控制两种引擎,语义清晰,对齐 VitePress: ```ts // valaxy.config.ts // KaTeX(默认开启) export default defineValaxyConfig({ features: { katex: true }, }) // MathJax3(对齐 VitePress,零 CSS 依赖) // 需先安装:pnpm add markdown-it-mathjax3 export default defineValaxyConfig({ math: true, }) // 禁用所有数学渲染 export default defineValaxyConfig({ features: { katex: false }, }) ``` - `features.katex` — 控制 KaTeX(Valaxy 原有配置,保持不变) - `math` — 控制 MathJax(对齐 VitePress `markdown.math`) - 两者互斥:启用 `math` 时 KaTeX 自动禁用 --- ## KaTeX 加载策略评估 ### 前提 当选择 KaTeX 引擎时,首页首屏不需要渲染数学公式,但 `katex.min.css`(~25KB)及字体文件会在所有页面全局加载。评估是否应改为按需加载。 ### 方案对比 #### 方案 A:全局条件加载(当前方案) 在 `virtual/styles.ts` 中,当 math engine 为 `katex` 时全局引入 `katex.min.css` + `katex.scss`。 #### 方案 B:按需加载 从全局样式移除 KaTeX CSS,通过 node 端正则检测文章内容中的数学公式语法、客户端 DOM 检测 `.katex` 元素,按需动态 `import()` CSS。 #### 对比 | 维度 | 全局加载 | 按需加载 | |------|---------|---------| | 首屏性能 | ~1.2KB gzip CSS | 无数学页面零开销 | | 实现复杂度 | 一行 import | node 检测 + composable + DOM 检测,分布 5+ 文件 | | 可靠性 | 不可能遗漏 | 依赖正则和 DOM 检测的完备性 | | FOUC 风险 | 无 | 列表页 DOM 检测路径存在短暂 FOUC | | SSG 友好度 | 完美 | 文章详情页 OK;列表页 `onMounted` 路径不参与 SSR | | 维护成本 | 低 | 中——多处代码联动,正则需持续维护 | ### 按需加载方案的具体问题 #### 1. 正则检测误判与遗漏(严重) ```ts const hasInlineMath = /(? ## KaTeX vs MathJax3 对比 {#katex-vs-mathjax3-对比} ### 渲染性能 {#渲染性能} | 维度 | KaTeX | MathJax3 | |------|-------|----------| | 渲染速度 | 极快(专为速度优化) | 较慢(功能更全面) | | 渲染位置 | Node 端构建时渲染为 HTML | Node 端构建时渲染为 SVG | | 客户端 JS | 零(构建时已渲染完成) | 零(构建时已渲染完成) | 两者在 Valaxy 中都是**构建时渲染**,不依赖客户端 JS,因此运行时性能差异不大。 ### 输出格式与依赖 {#输出格式与依赖} | 维度 | KaTeX | MathJax3 | |------|-------|----------| | **输出格式** | HTML + CSS spans | **SVG**(自包含矢量图) | | **外部 CSS** | 需要 `katex.min.css`(~1.2KB gzip) | **无** | | **字体文件** | ~20 个 woff2(浏览器按需加载) | **无**(SVG 内嵌字形) | | **FOUC 风险** | CSS 未加载前有闪烁风险 | **无**(SVG 自包含) | | **按需特性** | 需要全局加载 CSS | **天然按需**——无公式页面零开销 | ### 功能完整性 {#功能完整性} | 维度 | KaTeX | MathJax3 | |------|-------|----------| | LaTeX 覆盖度 | 大部分常用命令 | 更全面,支持更多扩展 | | 交换图/XyJax | 不支持 | 支持(通过 XyJax-v3) | | `\ce{}` 化学 | 需要额外扩展 | 内置支持 | | `\cancel`/`\xcancel` | 支持 | 支持 | | 自定义宏 | 支持 | 支持(更灵活) | | 可访问性 | MathML 输出 | MathML + SVG | ### 依赖体积(npm 包大小) {#依赖体积npm-包大小} | | KaTeX | MathJax3 (`markdown-it-mathjax3`) | |---|---|---| | 安装大小 | `katex`: ~3.5MB(含字体) | `markdown-it-mathjax3@4`: ~40MB(`mathjax-full`) | | 客户端影响 | ~1.2KB CSS(gzip) | 零 | > MathJax 的 npm 安装体积更大,但这仅影响 `node_modules`,不影响客户端产物。 ### 为什么 VitePress 选择 MathJax3? {#为什么-vitepress-选择-mathjax3} 1. **零运行时依赖**:SVG 内联在 HTML 中,无需 CSS/字体/JS 2. **天然按需**:无公式页面完全零开销 3. **通用文档工具**:大多数文档站不使用数学公式,MathJax 的 SVG 方案确保零影响 4. **`math: false` 默认关闭**:仅需要时才安装和启用 ### Valaxy 的选择:KaTeX + MathJax 分离配置 {#valaxy-的选择katex-mathjax-分离配置} Valaxy 通过两个独立配置分别控制两种引擎,语义清晰,对齐 VitePress: ```ts // valaxy.config.ts // KaTeX(默认开启) export default defineValaxyConfig({ features: { katex: true }, }) // MathJax3(对齐 VitePress,零 CSS 依赖) // 需先安装:pnpm add markdown-it-mathjax3 export default defineValaxyConfig({ math: true, }) // 禁用所有数学渲染 export default defineValaxyConfig({ features: { katex: false }, }) ``` - `features.katex` — 控制 KaTeX(Valaxy 原有配置,保持不变) - `math` — 控制 MathJax(对齐 VitePress `markdown.math`) - 两者互斥:启用 `math` 时 KaTeX 自动禁用 --- ## KaTeX 加载策略评估 {#katex-加载策略评估} ### 前提 {#前提} 当选择 KaTeX 引擎时,首页首屏不需要渲染数学公式,但 `katex.min.css`(~25KB)及字体文件会在所有页面全局加载。评估是否应改为按需加载。 ### 方案对比 {#方案对比} #### 方案 A:全局条件加载(当前方案) {#方案-a全局条件加载当前方案} 在 `virtual/styles.ts` 中,当 math engine 为 `katex` 时全局引入 `katex.min.css` + `katex.scss`。 #### 方案 B:按需加载 {#方案-b按需加载} 从全局样式移除 KaTeX CSS,通过 node 端正则检测文章内容中的数学公式语法、客户端 DOM 检测 `.katex` 元素,按需动态 `import()` CSS。 #### 对比 {#对比} | 维度 | 全局加载 | 按需加载 | |------|---------|---------| | 首屏性能 | ~1.2KB gzip CSS | 无数学页面零开销 | | 实现复杂度 | 一行 import | node 检测 + composable + DOM 检测,分布 5+ 文件 | | 可靠性 | 不可能遗漏 | 依赖正则和 DOM 检测的完备性 | | FOUC 风险 | 无 | 列表页 DOM 检测路径存在短暂 FOUC | | SSG 友好度 | 完美 | 文章详情页 OK;列表页 `onMounted` 路径不参与 SSR | | 维护成本 | 低 | 中——多处代码联动,正则需持续维护 | ### 按需加载方案的具体问题 {#按需加载方案的具体问题} #### 1. 正则检测误判与遗漏(严重) {#1-正则检测误判与遗漏严重} ```ts const hasInlineMath = /(? > **i18n in One Page** In order to make [Valaxy](https://github.com/YunYouJun/valaxy) an international project, i18n is essential. Common i18n schemes are maintained separately using different paths (e.g. `/zh-CN/`) or resolving different domain names (`cn.xxx.xxx`). > In addition, the [crowdin](https://crowdin.com/) platform can be used to assist users with multilingual translations. But for blogs, this is obviously all a hassle. When you need i18n, you have to maintain articles in multiple directories at the same time. You also have to maintain the same content when the same examples exist between articles. Very inelegant. In Valaxy, the The standalone fields of the site (e.g. Table of Contents) are implemented based on [vue-i18n](https://vue-i18n.intlify.dev/). The large text sections of the article content section use a different CSS i18n scheme. [I want to see the result first.](#result) ## Vue-i18n Config Vite Vue-i18n plugin [@intlify/unplugin-vue-i18n](https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n): ```ts import path from 'node:path' import VueI18n from '@intlify/unplugin-vue-i18n/vite' import { defineConfig } from 'vite' export default defineConfig({ plugins: [ VueI18n({ runtimeOnly: true, compositionOnly: true, include: [path.resolve(__dirname, 'locales/**')], }), ], }) ``` Write `zh-CN.yml` and `en.yml` in `locales`. ```yaml # zh-CN.yml sidebar: toc: 文章目录 ``` ```yaml # en.yml sidebar: toc: Table of Contents ``` and initialized in the main entry file (e.g. `main.ts`). ```ts /* * All i18n resources specified in the plugin `include` option can be loaded * at once using the import syntax */ import messages from '@intlify/unplugin-vue-i18n/messages' // import { createApp } from 'vue' // import App from './App.vue' import { createI18n } from 'vue-i18n' const i18n = createI18n({ legacy: false, locale: 'en', messages, }) // const app = createApp(App) app.use(i18n) ``` You can then use `t('')` in Vue to translate the text of the corresponding field. ```vue ``` ### Messages when SSG `vue-i18n` supports importing multiple languages by using the virtual module `@intlify/unplugin-vue-i18n/messages`. Unfortunately, it doesn't support SSR perfectly.[#78 | intlify/bundle-tools](https://github.com/intlify/bundle-tools/issues/78) And Vite's `import.meta.globEager` import must use a static string. ```ts {3} const messages = Object.fromEntries( Object.entries( import.meta.globEager('../../locales/*.y(a)?ml') ) .map(([key, value]) => { const yaml = key.endsWith('.yaml') return [key.slice(14, yaml ? -5 : -4), value.default] }), ) ``` It works when there is a defined directory, but Valaxy also needs to merge Valaxy's own `locales` with the theme's `locales` and user-defined `locales`. This means that we cannot use variables to splice strings for import, and it is difficult to determine the relative location of where these `locales` are for different package managers with different directory structures. So I implemented it in the form of a plugin virtual module (`@valaxyjs/locales`): > The principle of the Vite virtual module is actually a spliced string. ```ts import type { Plugin } from 'vite' // import the locales data in each directory in turn and merge them function generateLocales(roots: string[]) { const imports: string[] = [ 'const messages = { "zh-CN": {}, en: {} }', ] const languages = ['zh-CN', 'en'] roots.forEach((root, i) => { languages.forEach((lang) => { const langYml = `${root}/locales/${lang}.yml` if (fs.existsSync(langYml) && fs.readFileSync(langYml, 'utf-8')) { const varName = lang.replace('-', '') + i // in windows, you need to change slash // more info you can refer 'packages/valaxy/src/node/plugins/index.ts' imports.push(`import ${varName} from "${langYml}"`) imports.push(`Object.assign(messages['${lang}'], ${varName})`) } }) }) imports.push('export default messages') return imports.join('\n') } export function createValaxyPlugin(options: ResolvedValaxyOptions): Plugin { // ... const roots = [options.clientRoot, options.themeRoot, options.userRoot] return { name: 'Valaxy', load(id) { // ... if (id === '/@valaxyjs/locales') return generateLocales(roots) }, async handleHotUpdate(ctx) { // ... }, } } ``` Finally load in the i18n initialization file: ```ts // i18n.ts import messages from '/@valaxyjs/locales' const i18n = createI18n({ legacy: false, locale: 'en', messages, }) app.use(i18n) ``` ## CSS i18n - Another solution > CSS i18n - Another complementary solution While the article section has large sections of text, the scenario of `vue-i18n` lies in some separate field translations. And the traditional way of managing them independently in separate files is not really convenient for blogs. In most cases, you don't want to create a dedicated folder to manage it. So I tried to solve the problem using pure CSS. ::: tip IDEA That is, with the help of CSS rules, the content of the corresponding block is displayed according to the corresponding language. The general solution: set fence to pre-compile Markdown via [markdown-it-container](https://github.com/markdown-it/markdown-it-container). Wrap new `
`s for the paragraphs that need to be i18n and hide them by default with CSS. When the page initializes or switches languages, add the corresponding language class to html and write the corresponding CSS to display the corresponding language block under that class. ::: **Advantages**: - Can be maintained in the same Markdown file, easy to write - Pre-loading and real-time switching - URLs remain unchanged, easy to manage and share, and switch without refreshing the page - Progressive translation (only part of the content is translated and can share example content, etc.) - When you are writing a document in the same file, GitHub Copilot (VSCode Extension) can even help you complete the translation! **Disadvantages**: - Multi-language content is rendered in the same page, adding redundancy (but I think the tiny size is perfectly acceptable) ### Result **The effect is as follows** (click the button to switch). Another i18n method. > More info... English --- **Written like this**: ```md Another i18n method. More info... English ``` ### Steps To be able to handle i18n with CSS, we use markdown-it-container's fence to wrap Markdown content that needs to participate in i18n. ```ts export function containerPlugin(md: MarkdownIt) { // ... const languages = ['zh-CN', 'en'] languages.forEach((lang) => { md.use(container, lang, { render: (tokens: Token[], idx: number) => tokens[idx].nesting === 1 ? `
\n` : '
\n', }) }) } ``` This allows: ```md ``` Be `
`. > [lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) is a standard field in HTML. To avoid class naming conflicts, we can use the CSS attribute query. First, hide all i18n: ```scss html[lang] { .markdown-body { div[lang] { display: none; } } } ``` Write CSS/SCSS rules and set html `lang` to display elements in the corresponding language when it is the corresponding language. ```scss $languages: zh-CN, en; @each $lang in $languages { html[lang="#{$lang}"] { // only for markdown .markdown-body { div[lang="#{$lang}"] { display: block; } } } } ``` To help users remember their language, please also don't forget to initialize. ```html {9} ... ``` When switching languages, the following can be done. ```ts function toggleLocales(lang: val) { // ... // save locale localStorage.setItem('valaxy-locale', lang) // set html lang document.documentElement.setAttribute('lang', lang) } ``` It's worth mentioning that when looking at the `lang` documentation, I accidentally found that `:lang` is also a supported selector. So `[lang="xxx"]` in the CSS above could also be replaced with `:lang(xxx)`. However, `:lang()` will also hit the default language `div` (which has no lang field but is in a tag containing lang), so to be safe we should still use the class attribute query. I think vue-i18n complements CSS i18n and could be a very good solution for i18n switching within a single page. Why not give it a try? ## 如何实现 CSS i18n? - **Date**: 2022-04-09 - **Categories**: Valaxy 开发笔记 - **Tags**: valaxy, i18n, 笔记 ::: tip You can click this button to toggle locales. :::
> 在一个页面中实现 i18n 为了使 [Valaxy](https://github.com/YunYouJun/valaxy) 成为一个国际化的项目,i18n 是必不可少的。 常见的 i18n 方案为采用不同的路径(如 `/zh-CN/`)或解析不同的域名(`cn.xxx.xxx`)来分别维护。 > 此外还可使用 [crowdin](https://crowdin.com/) 平台辅助用户进行多语言翻译。 但对于博客来说,这显然都很麻烦。 当你需要 i18n 时,你不得不同时维护多个目录下的文章。 当文章间存在相同的示例时,你还需要维护相同的内容。非常不优雅。 Valaxy 中, 站点的独立字段部分(如文章目录:Table of Contents)基于 [vue-i18n](https://vue-i18n.intlify.dev/) 实现, 而文章内容部分的大段文本则采用另一种 CSS i18n 的方案。 [我想先看看效果](#result) ## Vue-i18n {#vue-i18n} 配置 Vite Vue-i18n 插件 [@intlify/unplugin-vue-i18n](https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n): ```ts import path from 'node:path' import VueI18n from '@intlify/unplugin-vue-i18n/vite' import { defineConfig } from 'vite' export default defineConfig({ plugins: [ VueI18n({ runtimeOnly: true, compositionOnly: true, include: [path.resolve(__dirname, 'locales/**')], }), ], }) ``` 在 `locales` 目录下配置 `zh-CN.yml` 与 `en.yml`: ```yaml # zh-CN.yml sidebar: toc: 文章目录 ``` ```yaml # en.yml sidebar: toc: Table of Contents ``` 并在主入口文件(如 `main.ts`)中初始化: ```ts /* * All i18n resources specified in the plugin `include` option can be loaded * at once using the import syntax */ import messages from '@intlify/unplugin-vue-i18n/messages' // import { createApp } from 'vue' // import App from './App.vue' import { createI18n } from 'vue-i18n' const i18n = createI18n({ legacy: false, locale: 'en', messages, }) // const app = createApp(App) app.use(i18n) ``` 此时即可在 Vue 中使用 `t('')` 来翻译对应字段文本。 ```vue ``` ### Messages when SSG {#messages-when-ssg} `vue-i18n` 支持使用虚拟模块 `@intlify/unplugin-vue-i18n/messages` 的方式来导入多语言。 可惜的是,它并没有完美地支持 SSR。[#78 | intlify/bundle-tools](https://github.com/intlify/bundle-tools/issues/78) 而 Vite 的 `import.meta.globEager` 导入必须使用静态字符串。 ```ts {3} const messages = Object.fromEntries( Object.entries( import.meta.globEager('../../locales/*.y(a)?ml') ) .map(([key, value]) => { const yaml = key.endsWith('.yaml') return [key.slice(14, yaml ? -5 : -4), value.default] }), ) ``` 当拥有确定目录时,它是奏效的,但 Valaxy 还需要将 Valaxy 自身的 `locales` 与主题的 `locales` 以及用户自定义的 `locales` 进行合并。 这意味着我们不能使用变量来拼接字符串进行导入,对于不同包管理器的目录结构不同,我们很难确定这些 `locales` 处于何处的相对位置。 因此我采用插件虚拟模块(`@valaxyjs/locales`)的形式实现(依次导入各目录下的 locales 数据并合并): > Vite 虚拟模块的原理其实就是拼接字符串。 ```ts import type { Plugin } from 'vite' // import the locales data in each directory in turn and merge them function generateLocales(roots: string[]) { const imports: string[] = [ 'const messages = { "zh-CN": {}, en: {} }', ] const languages = ['zh-CN', 'en'] roots.forEach((root, i) => { languages.forEach((lang) => { const langYml = `${root}/locales/${lang}.yml` if (fs.existsSync(langYml) && fs.readFileSync(langYml, 'utf-8')) { const varName = lang.replace('-', '') + i // in windows, you need to change slash // more info you can refer 'packages/valaxy/src/node/plugins/index.ts' imports.push(`import ${varName} from "${langYml}"`) imports.push(`Object.assign(messages['${lang}'], ${varName})`) } }) }) imports.push('export default messages') return imports.join('\n') } export function createValaxyPlugin(options: ResolvedValaxyOptions): Plugin { // ... const roots = [options.clientRoot, options.themeRoot, options.userRoot] return { name: 'Valaxy', load(id) { // ... if (id === '/@valaxyjs/locales') return generateLocales(roots) }, async handleHotUpdate(ctx) { // ... }, } } ``` 最后在 i18n 的初始化文件加载: ```ts // i18n.ts import messages from '/@valaxyjs/locales' const i18n = createI18n({ legacy: false, locale: 'en', messages, }) app.use(i18n) ``` ## CSS i18n - Another solution {#css-i18n---another-solution} > CSS i18n - 另一种互补解决方案 文章部分拥有大段的文本,而 `vue-i18n` 的场景则在于一些独立的字段翻译。 而传统的分文件独立管理的方式,对于博客来说其实并不方便。 大多数情况,你并不会想专门建立一个文件夹来管理它。 因此我尝试使用纯 CSS 解决该问题。 ::: tip 思路 即借助 CSS 规则,根据对应语言,显示对应区块内容。 大体方案:通过 [markdown-it-container](https://github.com/markdown-it/markdown-it-container) 设置 fence 预编译 Markdown, 当页面初始化或切换语言时,为 html 添加对应语言类,编写对应 CSS 以在该类下显示对应语言的区块。 ::: When the page initializes or switches languages, add the corresponding language class to html and write the corresponding CSS to display the corresponding language block under that class. ::: **优势**: - 可在同一个 Markdown 文件中进行维护,书写便捷 - 预加载与实时切换 - URL 不变,便于管理与分享,且切换无需刷新页面 - 渐进式翻译(只翻译部分内容并可共用示例内容等) - 当你在同一个文件编写文档时,GitHub Copilot (VSCode 插件) 甚至很容易帮助你补全翻译! **劣势**: - 多语言内容被渲染在同一页面中,增加冗余(但我觉得这微小的体积完全是可以接受的) ### Result {#result} **效果如下**(点击按钮切换): 另一种 i18n 方案。 > 更多内容:... 中文 --- **书写方式**如下: ```md 另一种 i18n 方案。 更多内容:... 中文 ``` ### Steps {#steps} **实现步骤** 为了能够借助 CSS 处理 i18n,我们借助 markdown-it-container 的 fence 包裹 Markdown 中需要参与 i18n 的内容。 ```ts export function containerPlugin(md: MarkdownIt) { // ... const languages = ['zh-CN', 'en'] languages.forEach((lang) => { md.use(container, lang, { render: (tokens: Token[], idx: number) => tokens[idx].nesting === 1 ? `
\n` : '
\n', }) }) } ``` 这可以使: ```md 中文 ``` > [lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) 是 HTML 的一个标准字段。 为避免 class 命名冲突,我们可以采用 CSS attribute 的查询方式。 首先将 i18n 全部隐藏: ::: ::: en > [lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) is a standard field in HTML. To avoid class naming conflicts, we can use the CSS attribute query. First, hide all i18n: ::: ```scss html[lang] { .markdown-body { div[lang] { display: none; } } } ``` ::: zh-CN 编写 CSS/SCSS 规则,设定 html `lang` 为对应语言时,显示对应语言的元素即可。 ::: ::: en Write CSS/SCSS rules and set html `lang` to display elements in the corresponding language when it is the corresponding language. ::: ```scss $languages: zh-CN, en; @each $lang in $languages { html[lang="#{$lang}"] { // only for markdown .markdown-body { div[lang="#{$lang}"] { display: block; } } } } ``` ::: zh-CN 为了帮助用户记住自己的语言,还请不要忘记初始化。 ::: ::: en To help users remember their language, please also don't forget to initialize. ::: ```html {9} ... ``` ::: zh-CN 切换语言时则可做如下处理: ::: ::: en When switching languages, the following can be done. ::: ```ts function toggleLocales(lang: val) { // ... // save locale localStorage.setItem('valaxy-locale', lang) // set html lang document.documentElement.setAttribute('lang', lang) } ``` ::: zh-CN 值得一提的是,在查看 `lang` 文档时,我意外地发现 `:lang` 也是一种支持的选择器。 因此上述的 CSS 中 `[lang="xxx"]` 也可以替换为 `:lang(xxx)`。 但是 `:lang()` 也会命中默认语言的 `div`(没有 lang 字段,但处于含有 lang 的标签中),因此为了安全,我们还是应该使用 class 的属性查询。 ::: ::: en It's worth mentioning that when looking at the `lang` documentation, I accidentally found that `:lang` is also a supported selector. So `[lang="xxx"]` in the CSS above could also be replaced with `:lang(xxx)`. However, `:lang()` will also hit the default language `div` (which has no lang field but is in a tag containing lang), so to be safe we should still use the class attribute query. ::: ::: zh-CN 我认为 vue-i18n 与 CSS i18n 的互补,可以非常好地解决单页内的 i18n 切换。 不妨一试? ::: ::: en I think vue-i18n complements CSS i18n and could be a very good solution for i18n switching within a single page. Why not give it a try? ::: ## Why Valaxy - **Date**: 2022-03-22 - **Categories**: getting-started - **Tags**: valaxy, 笔记 ## What is Valaxy? Valaxy aims to be a next generation of static blogging frameworks/generators. ::: info - V + galaxy = Valaxy - V: it based on vue + vite - galaxy: I hope it can be like a platform tool, hosting everyone's blog, as beautiful as the galaxy ::: My blog was previously built on Hexo, but as modern front-end frameworks continue to advance, Hexo's workflow and development experience have begun to lag behind. So I decided to build a new [hexo-theme-yun](https://github.com/YunYouJun/hexo-theme-yun/) based on Vue and Vite. My previous intention was to refactor the theme using a modern front-end framework, but the separation from Hexo also meant that I had to redo some of the rendering work that Hexo itself had done. So if I do that, why not develop a static site generator for blogs by the way? So I decided to call it Valaxy. Is this a reinventing the wheel? I don't think so. ## Why Valaxy? Next Generation Static Blog Framework/Generator 「Two things to tell you」, first, compared with Hexo, Valaxy is superior in both development experience and speed, second, compared with VitePress/VuePress, Valaxy has more integration features for blogs, such as article list hook, automatic routing and component registration, overlay layout and theme, etc. It seems hard to understand the advantages of Valaxy. I will compare Valaxy with the existing Hexo (popular static blog framework) and vitepress / vuepress (popular static site generator), and explain the advantages of Valaxy. ```ts import type { UserConfig } from 'valaxy' import type { UserThemeConfig } from 'valaxy-theme-yun' export default defineValaxyConfig({ theme: 'yun', themeConfig: { banner: { enable: true, title: '云游君的小站', }, }, }) ``` ## Why not ...? > Wordpress/Typecho, etc. are dynamic blogs that require additional server support. Their features and target audience are quite different, so they are not included in the comparison. ### [Hexo](https://hexo.io/)/[Hugo](https://gohugo.io/)/[Jekyll](https://jekyllrb.com/) I badly need the HMR and PJAX development experience that modern front-end frameworks provide, as well as TypeScript's type hints, but Hexo seems to have gotten a little stuck in the past, and doing something based on it would be limiting. Hugo is also a great static site generator, but I have no need to use Go. Of course, the ESBuild used in packaging is based on the Go implementation. But that's not for me to worry about. Jekyll is a bit of a veteran, but again I don't use Ruby, and it doesn't seem to be easy, and there are some issues with the development experience. The fact that GitHub has native support for it is a big advantage, but I intend to use GitHub Actions to achieve a nearly consistent experience in this regard. ### VuePress/VitePress [VitePress](https://vitepress.dev/) is almost the successor to VuePress. VitePress is a great static site generator, which is built for documentation, but lacks some convenient customization features for blogs, such as RSS, file automatic routing (vue-router), plugin (widget) mechanism, article list/category/tag hooks, custom override layout, override components, single-page switching i18n, KaTeX, etc. ### [iles](https://github.com/ElMassimo/iles) After completing the development of Valaxy's basic structures, I learned about iles from my group friend, which is very similar to many features I have archived. It has more features than Vitepress and is also suitable for writing a document with more interaction. However, its positioning is still static site generator, which is different from that of Valaxy static blog generator. In addition, Valaxy also provides more blog oriented features such as article list, pagination, tag and category, and supports extension and customization of blog topics. ### [Astro](https://astro.build/) Astro is a content-driven web framework. It has more versatility than Valaxy's blog-oriented positioning. > Thanks to its [islands architecture](https://docs.astro.build/en/concepts/islands/), Astro can combine any front-end framework (such as React, Vue, Svelte, etc.) with static content. In fact, Valaxy and Astro are two different technical routes for building blogs. Valaxy uses Vue-based Vite [SSG](https://vuejs.org/guide/extras/ways-of-using-vue#jamstack-ssg) (static site generation) to build blogs, and currently does not consider supporting other front-end frameworks. Currently, Valaxy adopts a single-page SSG, which generates a separate HTML file for each blog page, and after entering from any entry point, it activates as a [SPA](https://vuejs.org/guide/extras/ways-of-using-vue#single-page-application-spa) while displaying the content of the current page. After that, it only needs to update the page content partially without reloading the entire page. In metaphorical terms, each page of Astro is an independent island, and the site is an archipelago. Valaxy's site is more like a whole planet, with each page being an entry point to the planet. > - We can open the [Astro documentation website](https://docs.astro.build/en/getting-started/) and open the browser's developer tools, uncheck the "Preserve log" option, then switch to the left directory and check its network requests. > At this point, you will find that Astro reloads the entire page and generates new requests every time you switch pages, and the position of the left directory may change. > - You can also open the [Valaxy documentation website](https://valaxy.site/guide/getting-started) and repeat the above operation. > At this point, you will find that Valaxy does not reload the entire page when switching pages, but only loads part of the scripts and updates the page content. > > Video demonstration: [Why not Astro? | #596](https://github.com/YunYouJun/valaxy/discussions/596) This is a trade-off between two architectures. Unsurprisingly, Astro's first screen loading speed is faster, but there may be some experience interruptions when switching pages. Valaxy's first screen loading speed is between Astro and traditional SPAs, but it provides a SPA-like experience when switching pages. ## Thanks The implementation of Valaxy is based on or referenced from the following projects: - [Vue](https://github.com/vuejs/core) - [VueUse](https://github.com/vueuse/vueuse) - [Vite](https://github.com/vitejs/vite) - [VitePress](https://github.com/vuejs/vitepress) - [Vitesse](https://github.com/antfu/vitesse) - [Slidev](https://github.com/slidevjs/slidev) ## 为什么选 Valaxy - **Date**: 2022-03-22 - **Categories**: getting-started - **Tags**: valaxy, 笔记 ## 什么是 Valaxy? {#what-is-valaxy} Valaxy 的目标是成为新一代的静态博客框架/生成器。 ::: info - V + galaxy = Valaxy - V: it based on vue + vite - galaxy: 我希望它可以像一个平台工具,承载每个人的博客,如同银河系一般美丽 ::: 我的博客此前构建于 Hexo 之上,但随着现代前端框架的不断进步,Hexo 的工作流与开发体验已开始落后。 因此我决定基于 Vue 与 Vite 构建新的 [hexo-theme-yun](https://github.com/YunYouJun/hexo-theme-yun/)。 此前我的目的是使用现代前端框架重构主题,但与 Hexo 的脱离也意味着我要重新完成 Hexo 本身做的一些渲染工作。 那么如果我这么做了,为什么不顺便开发一个专为博客打造的静态站点生成器呢? 因此,我决定将其叫做 Valaxy。 这是重复造轮子吗?我认为不是。 ## 为什么是 Valaxy? {#why-valaxy} 构想新一代静态博客框架/生成器。 「**告诉你两件好事吧**」: - 第一它与 Hexo 相比开发体验和速度上都更胜一筹 - 第二它与 VitePress/VuePress 相比拥有更多针对博客的集成功能,譬如文章列表钩子、自动路由与组件注册、可覆盖的布局与主题等。 我认为 Valaxy 最突出的优势在于它的热更新开发体验与可定制性,但你编写文章或博客配置时,你只需要保存,所有的变更将会即刻显示在页面上,几乎无需等待! 此外,Valaxy 的主题还较少,但以 valaxy-theme-yun 为例,你可以覆盖主题中的**任何**组件,来定制或编写你自己的主题。 一味地讲述 Valaxy 的优点似乎有些难以理解。 我将会把 Valaxy 与现有的 Hexo(流行的静态博客框架)与 VitePress/VuePress(流行的静态站点生成器)进行对比,并阐述 Valaxy 的优势。 ```ts import type { UserConfig } from 'valaxy' import type { UserThemeConfig } from 'valaxy-theme-yun' export default defineValaxyConfig({ theme: 'yun', themeConfig: { banner: { enable: true, title: '云游君的小站', }, }, }) ``` ## 为什么不是……? {#why-not} > Wordpress/Typecho 等属于动态博客,需要额外的服务器支持。它们的特性与目标群体差异较大,因此不在对比范围内。 ### [Hexo](https://hexo.io/)/[Hugo](https://gohugo.io/)/[Jekyll](https://jekyllrb.com/) {#hexohttpshexoiohugohttpsgohugoiojekyllhttpsjekyllrbcom} 我非常需要现代前端框架提供的开发热重载与 PJAX 体验,以及 TypeScript 的类型提示,但 Hexo 似乎已经有些积重难返,基于此来做一些工作将会束手束脚。 Hugo 也是很棒的静态站点生成器,但是我并没有使用 Go 的需求。当然在打包时所使用的 ESBuild 正是基于 Go 实现。但这并不需要我操心。 Jekyll 算是元老,但同样我并不使用 Ruby,且它似乎并不便捷,也同样存在一些开发体验的问题。 GitHub 为其提供了原生支持是一大优势,但我打算类似使用 GitHub Actions 来达成该方面近乎一致的体验。 ### VuePress/VitePress {#vuepressvitepress} [VitePress](https://vitepress.dev/) 几乎已成为了 VuePress 的继任者。 VitePress 是一个很棒的静态站点生成器,它为文档打造,但缺少一些针对博客的定制便捷功能。 如:RSS、文件自动路由(vue-router)、插件(挂件)机制、文章列表/分类/标签钩子、自定义覆盖布局、覆盖组件、单页切换的 i18n、KaTeX 等。 ### [iles](https://github.com/ElMassimo/iles) {#ileshttpsgithubcomelmassimoiles} iles 与 Valaxy 的一些基础结构功能很相似,它相比 Vitepress 拥有更多功能,也很适合写一个拥有更多交互的文档。 不过它的定位仍旧是静态站点生成器,这与 Valaxy 静态博客生成器的定位不同。它的维护似乎也逐渐陷入停滞。 因为 Valaxy 除此之外,还会提供文章列表、分页、标签、分类等更多面向博客的功能,并支持扩展与自定义博客主题。 ### [Astro](https://astro.build/) {#astrohttpsastrobuild} Astro 是一个内容驱动的 Web 框架。它相比 Valaxy 针对博客的定位拥有更多的泛用性。 > 得益于它的[群岛架构](https://docs.astro.build/zh-cn/concepts/islands/),Astro 可以将任何前端框架(如 React、Vue、Svelte 等)与静态内容结合起来。 事实上,Valaxy 对于博客的构建与 Astro 是两种技术路线。 Valaxy 使用基于 Vue 的 Vite [SSG](https://cn.vuejs.org/guide/extras/ways-of-using-vue#jamstack-ssg)(静态站点生成)功能来构建博客,暂不考虑支持其他前端框架。 Valaxy 目前采用的是单页 SSG,它会为每个博客页面生成一个单独的 HTML 文件,而从任一入口进入后,除了当前页面内容的展示外,也会将其激活为 [SPA](https://cn.vuejs.org/guide/extras/ways-of-using-vue#single-page-application-spa)。 而在此之后它只需要部分地更新页面内容,而无需重新加载整个页面。 用比喻的话来说,Astro 的每个页面都是一个独立的岛屿,站点则是群岛。 而 Valaxy 的站点更像是一个整体的星球,每个页面都是进入星球的入口。 > - 我们可以打开 astro 的[文档官网](https://docs.astro.build/en/getting-started/),并打开浏览器的开发者工具,取消勾选保留日志,随后切换左侧目录,并查看其网络请求。 > 此时你可以发现,每次切换页面时,Astro 都会重新加载整个页面,并产生新的请求,且左侧目录所处位置可能发生改变。 > - 你也可以打开 Valaxy 的[文档官网](https://valaxy.site/guide/getting-started),并重复上述操作。 > 此时你会发现,Valaxy 在切换页面时并不会重新加载整个页面,而是仅加载部分脚本,并更新页面内容。 > > 视频演示:[为什么不是 Astro?| #596](https://github.com/YunYouJun/valaxy/discussions/596) 这是两种架构的取舍,毫无疑问,Astro 的首屏加载速度会更快,但切换页面时可能存在部分的体验中断。 而 Valaxy 的首屏加载速度介于 Astro 与传统的 SPA 之间,但切换页面时则可拥有 SPA 的体验。 ## Thanks {#thanks} 💗 Valaxy 的实现基于或参考了以下项目: - [Vue](https://github.com/vuejs/core) - [VueUse](https://github.com/vueuse/vueuse) - [Vite](https://github.com/vitejs/vite) - [VitePress](https://github.com/vuejs/vitepress) - [Vitesse](https://github.com/antfu/vitesse) - [Slidev](https://github.com/slidevjs/slidev) ## Math Formulas - **Date**: 2020-03-23 - **Categories**: examples ::: tip Valaxy supports two math rendering engines: [KaTeX](https://katex.org/) (default, fast rendering) and [MathJax](https://www.mathjax.org/) (SVG output, no external CSS/fonts needed). The current documentation site uses MathJax for rendering. ::: ## 行内公式 $\{x | Ax = b\}$ ```md $\{x | Ax = b\}$ ``` $\mathcal{X}_{c^*}<1, \mathcal{X}_{q^*}<1$ $E = mc^2$ $\frac{\partial}{\partial t}$ ```latex $E = mc^2$ $\frac{\partial}{\partial t}$ ``` ## 行间公式 $$ E = mc^2 $$ ```latex $$ E = mc^2 $$ ``` --- $$ \vec a=\frac{d\vec v}{dt}=\frac{d(\frac{dr}{dt}\vec e*{i}+r\frac{d\theta}{dt}\vec e*{j})}{dt}=\frac{d^2r}{dt^2}\vec e*{i}+\frac{dr}{dt}\frac{d\vec e*{i}}{dt}+\frac{dr}{dt}\frac{d\theta}{dt}\vec e*{j}+r\frac{d^2\theta}{dt^2}\vec e*{j}+r\frac{d\theta}{dt}\frac{d\vec e\_{j}}{dt} $$ ```latex $$ \vec a=\frac{d\vec v}{dt}=\frac{d(\frac{dr}{dt}\vec e_{i}+r\frac{d\theta}{dt}\vec e_{j})}{dt}=\frac{d^2r}{dt^2}\vec e_{i}+\frac{dr}{dt}\frac{d\vec e_{i}}{dt}+\frac{dr}{dt}\frac{d\theta}{dt}\vec e_{j}+r\frac{d^2\theta}{dt^2}\vec e_{j}+r\frac{d\theta}{dt}\frac{d\vec e_{j}}{dt} $$ ``` $$ m_t=g_t $$ $$ V_t=1 $$ ```latex $$ m_t=g_t $$ $$ V_t=1 $$ ``` $$ \eta_t=lr*{\frac {m_t}{\sqrt V_t}}=lr*g_t $$ $$ w\_{t+1}=w_t-\eta_t=w_t-lr*{\frac {m_t}{\sqrt V_t}}=w_t-lr*g_t $$
$$ q''_s = -k \left.\frac{\partial }{\partial x} (T_1+T_2) \right|_{x=0} = \underbrace{-k \left.\frac{\partial T_1}{\partial x} \right|_{x=0}}_{q''_s} -k \left.\frac{\partial T_2}{\partial x} \right|_{x=0} \rightarrow \boxed{0 = -k \left.\frac{\partial T_2}{\partial x} \right|_{x=0}} $$
```latex $$ \eta_t=lr*{\frac {m_t}{\sqrt V_t}}=lr*g_t $$ $$ w_{t+1}=w_t-\eta_t=w_t-lr*{\frac {m_t}{\sqrt V_t}}=w_t-lr*g_t $$
$$ q''_s = -k \left.\frac{\partial }{\partial x} (T_1+T_2) \right|_{x=0} = \underbrace{-k \left.\frac{\partial T_1}{\partial x} \right|_{x=0}}_{q''_s} -k \left.\frac{\partial T_2}{\partial x} \right|_{x=0} \rightarrow \boxed{0 = -k \left.\frac{\partial T_2}{\partial x} \right|_{x=0}} $$
``` $$ \begin{bmatrix} a & b \\ c & d \end{bmatrix} $$ ```latex $$ \begin{bmatrix} a & b \\ c & d \end{bmatrix} $$ ``` $$ \begin{equation} \left\{ \begin{aligned} x=a\cos\theta\\ y=b\sin\theta\\ \end{aligned} \right. \end{equation} $$ ```latex $$ \begin{equation} \left\{ \begin{aligned} x=a\cos\theta\\ y=b\sin\theta\\ end{aligned} \right. \end{equation} $$ ``` ## MathJax-Specific Features MathJax supports more LaTeX extensions compared to KaTeX. Here are some examples of additional features available in MathJax: ### Chemical Equations $$ \ce{CO2 + C -> 2 CO} $$ ```latex $$ \ce{CO2 + C -> 2 CO} $$ ``` ### 复杂环境 $$ \begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix} $$ ```latex $$ \begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix} $$ ``` ## 数学公式 - **Date**: 2020-03-23 - **Categories**: examples ::: tip Valaxy 支持两种数学渲染引擎:[KaTeX](https://katex.org/)(默认,渲染快)和 [MathJax](https://www.mathjax.org/)(SVG 输出,无需外部 CSS/字体)。 当前文档站使用 MathJax 渲染。 ::: ## 行内公式 {#行内公式} $\{x | Ax = b\}$ ```md $\{x | Ax = b\}$ ``` $\mathcal{X}_{c^*}<1, \mathcal{X}_{q^*}<1$ $E = mc^2$ $\frac{\partial}{\partial t}$ ```latex $E = mc^2$ $\frac{\partial}{\partial t}$ ``` ## 行间公式 {#行间公式} $$ E = mc^2 $$ ```latex $$ E = mc^2 $$ ``` --- $$ \vec a=\frac{d\vec v}{dt}=\frac{d(\frac{dr}{dt}\vec e*{i}+r\frac{d\theta}{dt}\vec e*{j})}{dt}=\frac{d^2r}{dt^2}\vec e*{i}+\frac{dr}{dt}\frac{d\vec e*{i}}{dt}+\frac{dr}{dt}\frac{d\theta}{dt}\vec e*{j}+r\frac{d^2\theta}{dt^2}\vec e*{j}+r\frac{d\theta}{dt}\frac{d\vec e\_{j}}{dt} $$ ```latex $$ \vec a=\frac{d\vec v}{dt}=\frac{d(\frac{dr}{dt}\vec e_{i}+r\frac{d\theta}{dt}\vec e_{j})}{dt}=\frac{d^2r}{dt^2}\vec e_{i}+\frac{dr}{dt}\frac{d\vec e_{i}}{dt}+\frac{dr}{dt}\frac{d\theta}{dt}\vec e_{j}+r\frac{d^2\theta}{dt^2}\vec e_{j}+r\frac{d\theta}{dt}\frac{d\vec e_{j}}{dt} $$ ``` $$ m_t=g_t $$ $$ V_t=1 $$ ```latex $$ m_t=g_t $$ $$ V_t=1 $$ ``` $$ \eta_t=lr*{\frac {m_t}{\sqrt V_t}}=lr*g_t $$ $$ w\_{t+1}=w_t-\eta_t=w_t-lr*{\frac {m_t}{\sqrt V_t}}=w_t-lr*g_t $$
$$ q''_s = -k \left.\frac{\partial }{\partial x} (T_1+T_2) \right|_{x=0} = \underbrace{-k \left.\frac{\partial T_1}{\partial x} \right|_{x=0}}_{q''_s} -k \left.\frac{\partial T_2}{\partial x} \right|_{x=0} \rightarrow \boxed{0 = -k \left.\frac{\partial T_2}{\partial x} \right|_{x=0}} $$
```latex $$ \eta_t=lr*{\frac {m_t}{\sqrt V_t}}=lr*g_t $$ $$ w_{t+1}=w_t-\eta_t=w_t-lr*{\frac {m_t}{\sqrt V_t}}=w_t-lr*g_t $$
$$ q''_s = -k \left.\frac{\partial }{\partial x} (T_1+T_2) \right|_{x=0} = \underbrace{-k \left.\frac{\partial T_1}{\partial x} \right|_{x=0}}_{q''_s} -k \left.\frac{\partial T_2}{\partial x} \right|_{x=0} \rightarrow \boxed{0 = -k \left.\frac{\partial T_2}{\partial x} \right|_{x=0}} $$
``` $$ \begin{bmatrix} a & b \\ c & d \end{bmatrix} $$ ```latex $$ \begin{bmatrix} a & b \\ c & d \end{bmatrix} $$ ``` $$ \begin{equation} \left\{ \begin{aligned} x=a\cos\theta\\ y=b\sin\theta\\ \end{aligned} \right. \end{equation} $$ ```latex $$ \begin{equation} \left\{ \begin{aligned} x=a\cos\theta\\ y=b\sin\theta\\ end{aligned} \right. \end{equation} $$ ``` ## MathJax 特有功能 {#mathjax-特有功能} MathJax 相比 KaTeX 支持更多 LaTeX 扩展。以下是一些 MathJax 的额外功能示例: ### 化学方程式 {#化学方程式} $$ \ce{CO2 + C -> 2 CO} $$ ```latex $$ \ce{CO2 + C -> 2 CO} $$ ``` ### 复杂环境 {#复杂环境} $$ \begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix} $$ ```latex $$ \begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix} $$ ``` ## Valaxy Valaxy is a next-generation static blog framework powered by Vue, Vite, and TypeScript. ## AI-Assisted Development - **Categories**: dev Valaxy supports AI-assisted development workflows through [Claude Code](https://claude.com/code), making it easier to contribute to the project. ## Setup The repository includes custom Claude Code commands in `.claude/commands/` to streamline common development tasks. ## Available Commands ### Fix GitHub Issues Automatically analyze and fix GitHub issues: ```bash /fix-github-issue 1234 ``` This command will: 1. Fetch the issue details using GitHub CLI 2. Analyze the problem description 3. Search for relevant code files 4. Implement the necessary changes 5. Run tests to verify the fix 6. Ensure code quality (linting, type checking) 7. Create a descriptive commit 8. Push changes and create a pull request **Example:** ```bash /fix-github-issue 628 ``` This will automatically fix issue #628 by: - Reading the issue description - Finding affected components - Implementing the fix - Running tests - Creating a PR with proper description ## CLAUDE.md The repository includes a `CLAUDE.md` file at the root that provides: - Essential development commands - Architecture overview - Key patterns and conventions - Project-specific notes This file helps Claude Code understand the codebase structure and development workflow. ## Best Practices When using AI-assisted development: 1. **Review Changes**: Always review the changes made by AI before committing 2. **Test Thoroughly**: Ensure tests pass and manually verify critical changes 3. **Understand the Code**: Don't just accept changes - understand what was changed and why 4. **Iterative Refinement**: Work with the AI iteratively to refine solutions 5. **Follow Conventions**: The AI will follow existing code patterns, but verify consistency ## Creating Custom Commands You can create custom commands for common tasks: 1. Create a new file in `.claude/commands/` 2. Name it descriptively (e.g., `add-feature.md`) 3. Write instructions for Claude Code to follow **Example command structure:** ```markdown Please implement a new feature: $ARGUMENTS. Follow these steps: 1. Analyze requirements 2. Design the solution 3. Implement the code 4. Write tests 5. Update documentation ``` ## Tips - Use `/help` to see all available commands - The AI has access to the full codebase context - Commands can accept arguments via `$ARGUMENTS` - AI will follow patterns from `CLAUDE.md` and existing code - GitHub CLI (`gh`) is available for GitHub operations ## Limitations - AI suggestions should be reviewed by humans - Complex architectural decisions may need manual planning - Security-sensitive changes require extra scrutiny - Always test in a local environment before deploying --- **Note**: AI-assisted development is a tool to enhance productivity, not replace human judgment. Always review and understand the changes made. ## Participate in Docs - **Categories**: dev ## Documentation Guidelines Valaxy is preparing for the 1.0 release, and we look forward to your participation in writing and translating documentation. ## Documentation Organization Valaxy documentation uses a **path-based separation** approach for organizing content in different languages: - English documentation is located in `/docs/pages/` - Chinese documentation is located in `/docs/pages/zh/` For example: ``` docs/pages/guide/getting-started.md # English version docs/pages/zh/guide/getting-started.md # Chinese version ``` ### Bilingual Container Approach (for specific scenarios) Some documents (such as blog posts) may use bilingual containers to write content in both languages within the same file: ```md ::: zh-CN Chinese content ::: ::: en English content ::: ``` For more details, see [Single Page i18n](https://valaxy.site/guide/i18n) and [i18n Container Syntax](/guide/i18n#container-syntax). ## How to Translate ### 1. Create the Corresponding Chinese Document If you find an English document that doesn't have a Chinese version yet, create the corresponding file under `/docs/pages/zh/`. For example, to translate `/docs/pages/guide/ssr-compat.md`: 1. Create `/docs/pages/zh/guide/ssr-compat.md` 2. Copy the structure from the English document 3. Translate the content to Chinese 4. Keep code examples unchanged (unless comments need localization) ### 2. Keep Document Structure Consistent - **Frontmatter**: Keep the same `categories`, `top`, etc. fields, only translate `title` - **Heading Hierarchy**: Maintain the same heading structure as the English version - **Code Examples**: Usually no translation needed, keep as-is - **Links**: Internal links in Chinese docs should point to Chinese versions (e.g., `/zh/guide/...`) ### 3. Translation Tips - Technical terms can include English on first use, e.g., "SSR (Server-Side Rendering)" - Maintain technical accuracy, refer to [Vue documentation](https://vuejs.org/) translation standards - Comments in code can be localized, but keep variable and function names in English ## How to Submit Use GitHub Pull Requests to submit to valaxy. It's recommended to submit a complete markdown file or a category translation as one commit. Commit messages should start with `docs:`. Examples: - Adding new Chinese translation: `docs: add zh translation for ssr-compat` - Updating existing translation: `docs: update guide translation` - Fixing typos: `docs: fix typo in xxx.md` - Updating English docs: `docs(en): update getting-started guide` ## Documentation Preview Before submitting, preview the documentation locally: ```bash # Install dependencies pnpm install # Start documentation dev server pnpm docs:dev # Build documentation (to check for build errors) pnpm docs:build ``` Visit `http://localhost:4859` to view the documentation, and use the language toggle button in the top right to test language switching. ## FAQ - **Categories**: dev
Resolved ## `background-attachment: fixed` not supported on iOS > iOS has an issue preventing background-attachment: fixed from being used with background-size: cover. > [The Fixed Background Attachment Hack | CSS Tricks](https://css-tricks.com/the-fixed-background-attachment-hack/) Use `::before` pseudo-element instead.
## JavaScript heap out of memory During SSG build (`valaxy build --ssg`), the built-in Valaxy SSG engine runs client build, server build, and page rendering in the same process. The Vite resolved config and plugin system from the build phase remain in memory, leaving limited heap space for the rendering phase. **Minimum memory requirement: `--max-old-space-size=4096` (~4 GB)** — the engine auto-respawns with this heap when needed. ```bash # Reproduce tests pnpm test:space # demo/yun pnpm test:space:docs # docs ``` When the heap limit is below ~4 GB, the SSG engine automatically respawns the build process with `--max-old-space-size=4096` (and `--expose-gc` when available) so rendering has enough headroom. Page rendering runs at a default concurrency of 20 (configurable via `vite.ssgOptions.concurrency`). If you still encounter OOM in CI environments, raise the heap limit via `NODE_OPTIONS`: ```bash NODE_OPTIONS=--max-old-space-size=4096 pnpm build --ssg ``` ## Merge Use `defu`. Testing shows `defu` is faster than `@fastify/deepmerge`. Merging a single config: - `defu`: 0.06ms - [`@fastify/deepmerge`](https://github.com/fastify/deepmerge): 0.256ms ```bash # benchmark @fastify/deepmerge x 605,343 ops/sec ±0.87% (96 runs sampled) deepmerge x 20,312 ops/sec ±1.06% (92 runs sampled) merge-deep x 83,167 ops/sec ±1.30% (94 runs sampled) ts-deepmerge x 175,977 ops/sec ±0.57% (96 runs sampled) deepmerge-ts x 174,973 ops/sec ±0.44% (93 runs sampled) lodash.merge x 89,213 ops/sec ±0.70% (98 runs sampled) ``` ## Participate in Development - **Categories**: dev - `create-valaxy` - `create-valaxy-theme` ## AI-Assisted Development Valaxy supports AI-assisted development workflows. See [AI-Assisted Development](./ai) for details. Quick example - fix a GitHub issue automatically: ```bash /fix-github-issue 628 ``` ## Dev You must use [pnpm](https://pnpm.io/). Because we use its workspace. ```bash git clone https://github.com/YunYouJun/valaxy ``` ```bash [pnpm] cd valaxy pnpm i # esbuild watch valaxy cli & valaxy-theme-yun # and run demo # build node cli pnpm run build # pnpm dev = pnpm dev:lib + pnpm demo pnpm dev ``` ### Docs We use valaxy to build docs. Just eat our own dog food. > If you want to use more out-of-the-box for docs, you can use [VitePress](https://vitepress.dev/). ```bash # build latest valaxy cli pnpm run build pnpm run docs:build ``` If you want to display info better in two terminal (**Recommended**), follow below. ### Node ```bash # watch valaxy & valaxy-theme-yun pnpm dev:lib ``` ### Client If you only want to develop client. - Docs: `pnpm docs:dev` - Demo(theme-yun): `pnpm demo` ## LOGO LOGO = V + Galaxy - 银河 - 闪耀 - 星球 - 夜空 ## Valaxy Addons Gallery - **Categories**: addon Discover official and community addons for Valaxy. Packages maintained in the Valaxy monorepo are organized in the [Official Addons](/addons/official) guide. This gallery also includes community addons. ## valaxy-addon-girls - **Categories**: addon Theme-independent responsive character gallery addon with grid, packed-bubble, and orbit layouts. ## Interactive examples The following examples are documentation-site additions. Installation, configuration, and API details above come directly from the package README. ### Layout and note modes ### Complete 120-entry collection The live example starts with every character packed into one bubble cluster. Switch to grid to compare progressive and complete rendering. ## index # Addons ## Official Addons - **Categories**: addon Documentation sourced directly from the READMEs of addons maintained in the Valaxy monorepo. Each package page below includes its package README at build time. The README remains the single source for installation, configuration, and API details; this index only provides navigation. The [Addon Gallery](/addons/gallery) also includes community packages. - [valaxy-addon-abbrlink](/addons/official/abbrlink) - [valaxy-addon-algolia](/addons/official/algolia) - [valaxy-addon-bangumi](/addons/official/bangumi) - [valaxy-addon-components](/addons/official/components) - [valaxy-addon-feishu](/addons/official/feishu) - [valaxy-addon-girls](/addons/girls) - [valaxy-addon-lightgallery](/addons/official/lightgallery) - [valaxy-addon-meting](/addons/official/meting) - [valaxy-addon-moments](/addons/official/moments) - [valaxy-addon-twikoo](/addons/official/twikoo) - [valaxy-addon-waline](/addons/official/waline) `valaxy-addon-test` is an internal fixture/template and is intentionally omitted. ## Use Addon - **Categories**: addon ## How To Use ```bash pnpm add [valaxy-addon-package1] [valaxy-addon-package2] # npm i [valaxy-addon-package1] [valaxy-addon-package2] ``` 使用 ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { addonTest } from 'valaxy-addon-test' export default defineValaxyConfig({ addons: [ // we always recommend to use function, so that you can pass options addonTest(), 'valaxy-addon-package1', // pass addon options ['valaxy-addon-package2', { global: false }], ] }) ``` ### Addon With Options 譬如开启 Waline 评论: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { addonWaline } from 'valaxy-addon-waline' export default defineValaxyConfig({ // 启用评论 comment: { enable: true }, // 设置 valaxy-addon-waline 配置项 addons: [ addonWaline({ serverURL: 'https://your-waline-url', }), ], }) ``` ## Why Addon? - **Categories**: addon We need a plugin system that allows users to use/load only certain features quickly. ## Naming conventions Plugin name: `valaxy-addon-`. > Add-on, compared to Plug-in, typically implies modifications to the user interface, as well as being applicable only on a specific platform. > For example, the Edge Add-on Store, Slidev, and others use the naming convention "Addon". > > - [Difference Between Add-on and Plug-in](http://www.differencebetween.net/technology/difference-between-add-on-and-plug-in/) > > Valaxy fully supports the use of Vite and Vue ecosystem plugins. > In addition, we may also require support for some plugins that are specific to Valaxy, which can control the entire process before the Vite/Vue plugins are executed. > > In this scenario, the Addon API is only applicable to the Valaxy platform. ## Explanation What can addons do? For example, they can create a Live2D widget, a global music player, or modify some configurations of Vite and its built-in plugins. Addons are used to complement what Vite/Vue plugins cannot achieve or to simplify complicated configurations. ## Write an Addon - **Categories**: addon ## Getting Started ::: tip **Convention over Configuration** - Addon: Must start with `valaxy-addon-`. - Addons are similar to themes, but do less. - A site can only use one theme, but can use multiple addons. - Addons do not need to be precompiled, just publish the source files directly. ::: - `App.vue` If the addon author wants the addon to be globally mounted immediately when used, they can place the content in `valaxy-addon-/App.vue` and set `global: true` in `package.json`. - `components`: Components placed in the `components` folder will be automatically registered, but not mounted. Users can manually load and use them. > Documentation is under construction. You can refer to some existing addons in the [Addon Gallery](/addons/gallery). ### Create Addon Template ```bash pnpm create valaxy # choose template addon ``` ### Using Lifecycle Hooks As shown in the example, addons can use `valaxy.hook` to mount lifecycle hooks. This allows you to do things before/after the build and at other points. > Please refer to [Lifecycle Hooks](/guide/custom/hooks) for more information. <<< @/../packages/valaxy-addon-test/node/index.ts {11-14} [valaxy-addon-test/node/index.ts] ### Reading Addon Options on the Client {#reading-addon-options} Addons typically accept user options via `defineAddon(options)` in `valaxy.config.ts`. These options are available at runtime through `useAddonConfig(addonName)`. `useAddonConfig` is a generic, type-safe composable provided by `valaxy`. It replaces the boilerplate pattern of `useRuntimeConfig()` + `computed()` + manual type assertion. ```ts [client/options.ts] import type { MyAddonOptions } from '../types' import { useAddonConfig } from 'valaxy' export function useMyAddonConfig() { return useAddonConfig('valaxy-addon-my-addon') } ``` The return value is a `ComputedRef | undefined>` — it is `undefined` when the addon is not installed. Access the options via `.value?.options`. ```vue [components/MyComponent.vue] ``` ::: tip `useAddonConfig` must be called inside ` ``` ```json [package.json] { "name": "my-valaxy-blog", "version": "0.1.0" } ``` ```yaml [docker-compose.yml] version: '3' services: app: image: node:20 ``` ```bash [install.sh] #!/bin/bash pnpm install pnpm build ``` ## Custom Icons You can configure custom icons in `valaxy.config.ts`: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { localIconLoader } from 'vitepress-plugin-group-icons' export default defineValaxyConfig({ groupIcons: { customIcon: { // Use a local SVG file valaxy: localIconLoader(import.meta.url, './public/favicon.svg'), // Use iconify icons nodejs: 'vscode-icons:file-type-node', playwright: 'vscode-icons:file-type-playwright', typedoc: 'vscode-icons:file-type-typedoc', eslint: 'vscode-icons:file-type-eslint', dockerfile: 'vscode-icons:file-type-docker', }, }, }) ``` ## Code Groups with Icons You can also use icons inside code groups: ::: code-group ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ theme: 'yun', }) ``` ```json [package.json] { "dependencies": { "valaxy": "latest", "valaxy-theme-yun": "latest" } } ``` ```toml [netlify.toml] [build] command = "pnpm build" publish = "dist" ``` ::: ## Code height limit - **Categories**: examples Set `codeHeightLimit: 300` in Front Matter. ```md [pages/code-height-limit.md] --- codeHeightLimit: 300 --- ``` Rendering result ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' const safelist = [ 'i-ri-home-line', ] export default defineValaxyConfig({ // site config see site.config.ts or write in siteConfig // siteConfig: {}, theme: 'yun', themeConfig: { banner: { enable: true, title: '云游君的小站', }, notice: { enable: true, content: '公告测试', }, }, unocss: { safelist, }, }) ``` ## index ## Mermaid - [Mermaid](https://mermaid.js.org/) - Diagramming and charting tool ## Flowchart ```mermaid graph TD; A-->B; A-->C; B-->D; C-->D; ``` ````txt ```mermaid graph TD; A-->B; A-->C; B-->D; C-->D; ``` ```` ## Mindmap For a lightweight mind map, Mermaid already works without an additional Valaxy addon. Use Markmap instead when you specifically need to turn a longer Markdown outline into an interactive, collapsible map. ```mermaid mindmap root((Valaxy blog)) Content Posts Pages Assets Extensions Components Themes Addons Delivery SSG build RSS Deployment ``` ````txt ```mermaid mindmap root((Valaxy blog)) Content Posts Pages Assets Extensions Components Themes Addons Delivery SSG build RSS Deployment ``` ```` ## Sequence diagram ```mermaid sequenceDiagram participant Alice participant Bob Alice->>John: Hello John, how are you? loop Healthcheck John->>John: Fight against hypochondria end Note right of John: Rational thoughts
prevail! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good! ``` ````txt ```mermaid sequenceDiagram participant Alice participant Bob Alice->>John: Hello John, how are you? loop Healthcheck John->>John: Fight against hypochondria end Note right of John: Rational thoughts
prevail! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good! ``` ```` ## Gantt diagram ```mermaid gantt dateFormat YYYY-MM-DD title Adding GANTT diagram to mermaid excludes weekdays 2014-01-10 section A section Completed task :done, des1, 2014-01-06,2014-01-08 Active task :active, des2, 2014-01-09, 3d Future task : des3, after des2, 5d Future task2 : des4, after des3, 5d ``` ````txt ```mermaid gantt dateFormat YYYY-MM-DD title Adding GANTT diagram to mermaid excludes weekdays 2014-01-10 section A section Completed task :done, des1, 2014-01-06,2014-01-08 Active task :active, des2, 2014-01-09, 3d Future task : des3, after des2, 5d Future task2 : des4, after des3, 5d ``` ```` ## Class diagram ```mermaid classDiagram Class01 <|-- AveryLongClass : Cool Class03 *-- Class04 Class05 o-- Class06 Class07 .. Class08 Class09 --> C2 : Where am i? Class09 --* C3 Class09 --|> Class07 Class07 : equals() Class07 : Object[] elementData Class01 : size() Class01 : int chimp Class01 : int gorilla Class08 <--> C2: Cool label ``` ````txt ```mermaid classDiagram Class01 <|-- AveryLongClass : Cool Class03 *-- Class04 Class05 o-- Class06 Class07 .. Class08 Class09 --> C2 : Where am i? Class09 --* C3 Class09 --|> Class07 Class07 : equals() Class07 : Object[] elementData Class01 : size() Class01 : int chimp Class01 : int gorilla Class08 <--> C2: Cool label ``` ```` ## Git graph ```mermaid gitGraph commit commit branch develop commit commit commit checkout main commit commit ``` ````txt ```mermaid gitGraph commit commit branch develop commit commit commit checkout main commit commit ``` ```` ## Quadrant Chart ```mermaid quadrantChart title Reach and engagement of campaigns x-axis Low Reach --> High Reach y-axis Low Engagement --> High Engagement quadrant-1 We should expand quadrant-2 Need to promote quadrant-3 Re-evaluate quadrant-4 May be improved Campaign A: [0.3, 0.6] Campaign B: [0.45, 0.23] Campaign C: [0.57, 0.69] Campaign D: [0.78, 0.34] Campaign E: [0.40, 0.34] Campaign F: [0.35, 0.78] ``` ````txt ```mermaid quadrantChart title Reach and engagement of campaigns x-axis Low Reach --> High Reach y-axis Low Engagement --> High Engagement quadrant-1 We should expand quadrant-2 Need to promote quadrant-3 Re-evaluate quadrant-4 May be improved Campaign A: [0.3, 0.6] Campaign B: [0.45, 0.23] Campaign C: [0.57, 0.69] Campaign D: [0.78, 0.34] Campaign E: [0.40, 0.34] Campaign F: [0.35, 0.78] ``` ```` ## XY Chart ```mermaid xychart-beta title "Sales Revenue" x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec] y-axis "Revenue (in $)" 4000 --> 11000 bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] ``` ````txt ```mermaid xychart-beta title "Sales Revenue" x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec] y-axis "Revenue (in $)" 4000 --> 11000 bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] ``` ```` ## Partial Content Encryption - **Categories**: examples 密码为 `valaxy`。 ```md 我是被加密的文本。 ::: details dynamically rendered frontmatter 支持动态渲染 **Frontmatter**: {{ frontmatter }} ::: ``` ## Rendering result 我是被加密的文本。 ::: details dynamically rendered frontmatter 支持动态渲染 **Frontmatter**: {{ frontmatter }} ::: ## Example Sites - **Categories**: ecosystem ::: tip 欢迎 [提交 PR](https://github.com/YunYouJun/valaxy/blob/main/docs/assets/sites.ts) 补充你的站点! ::: ## FAQ - **Categories**: guide ## 构建失败 ### ReferenceError: document is not defined 这通常发生在使用自定义代码 `document.xxx` 或引入第三方库(仅在浏览器端可用的 NPM 包)时。 代码直接调用了 `document`,而该变量在 Node 端不存在,因此导致构建失败。 你应当使用 `isClient` 判断逻辑来使得该代码仅在客户端执行。 ```ts import { isClient } from '@vueuse/core' if (isClient) { document.xxx() // import('xxx') } ``` ## Change Generated Directory Style Valaxy builds `xxx.md` as `/xxx.html` by default. If you prefer directory-style output (`/xxx/index.html`), structure the page as a directory index — put the content in `pages/xxx/index.md` instead of `pages/xxx.md`. A route ending in `/` is written as `route-path/index.html`. > The old `vite-ssg` `dirStyle` option was removed together with the legacy engine > in v1.0 (see [#706](https://github.com/YunYouJun/valaxy/issues/706)). ## After deploying to Github Pages, some pages cannot be accessed or the JS path cannot be found Github Pages uses Jekyll by default to build static sites, and Jekyll does not build files or folders that start with `_` by default. The output of the Valaxy build may contain files that start with `_`, so these files will be ignored by Jekyll’s build after submission, causing problems. In fact, the output of the Valaxy build can be used directly as a static site without the need for redundant Jekyll build operations. If there is an empty file named .nojekyll in the root path of the content deployed by Github Pages, the Jekyll build operation will be skipped. So you can create a new file named `.nojekyll` in the `public` folder of the project: ```bash |-- public | |-- .nojekyll ``` ## Best Practices - **Categories**: guide These recommendations keep a Valaxy blog portable and make development-only problems less likely to reach production. They are guidelines rather than requirements. ## Project and Dependencies - Use a supported Node.js version and keep it consistent between local development and CI. See [Getting Started](/guide/getting-started) for the current requirement. - Use one package manager throughout the project. We recommend `pnpm`; commit `pnpm-lock.yaml` so CI and local development resolve the same dependency graph. - Add every package imported by your config or components to the blog's own `package.json`. Do not rely on a dependency that happens to be installed transitively by Valaxy, a theme, or another addon. - Upgrade Valaxy and its official theme or addons together when possible. Run a production build after upgrading. ## Posts and Assets Use stable, URL-friendly English names for folders and files: ```txt blog/pages/posts/your-post.md ``` For local post assets, colocate them with the post and use relative paths. This makes the post easy to move and lets Vite process the assets for both the page and generated feeds. ```txt pages/posts/your-post ├── a.png ├── b.png └── index.md ``` ```md [pages/posts/your-post/index.md] ![Image A](./a.png) ![Image B](./b.png) ``` Root-absolute links in Markdown are adjusted automatically when the site is deployed under a subpath. For URLs created inside Vue components, use `withBase()`. See [Deploying under a base path](/guide/deploy#deploying-under-a-base-path). ## Dynamic and Third-party Content Encapsulate third-party scripts and highly dynamic content as Vue components in `components/`, then use the component from Markdown. This keeps side effects out of the post and gives you one place to handle loading, errors, and cleanup. ```bash pnpm add @vueuse/core ``` ```vue [components/BszComponent.vue] ``` ```md [pages/posts/test-custom-component.md] # Hello World ``` Valaxy generates pages with SSR. If a library accesses `window`, `document`, or another browser API, initialize it after mounting or load it through a client-only component. See [SSR Compatibility](/guide/ssr-compat). ## Choose the Smallest Extension Level | Need | Recommended integration | | --- | --- | | A supported Markdown syntax such as diagrams | Use the built-in feature first, such as [Mermaid](/guide/markdown#mermaid) | | A library or widget used by one blog | Create a local component in `components/` | | A reusable component shared by several blogs | Publish a component package or contribute it to `valaxy-addon-components` | | Markdown transforms, build hooks, shared configuration, or automatic component registration | [Write an addon](/addons/write) | For a simple mind map, use the built-in [Mermaid mindmap example](/examples/mermaid#mindmap). Start a Markmap integration as a local `Markmap.vue` component using `markmap-lib` and `markmap-view`, initialized on the client. A dedicated addon becomes worthwhile when it also provides a fenced `markmap` Markdown syntax, shared theme and toolbar options, asset handling, and an SSR-safe lifecycle. Until then, an addon adds installation and maintenance cost without reducing much user code. ## Verify Before Deployment Development mode cannot expose every SSR, dependency, or subpath problem. Before deploying: ```bash pnpm build pnpm serve ``` Check at least the home page, a post opened directly or refreshed, a page containing third-party content, and the production subpath if `vite.base` is configured. When reporting a problem, include the smallest reproduction, the full error, whether it occurs in development or production, and the environment output from: ```bash pnpm exec valaxy debug --plain ``` ## Deployment - **Categories**: getting-started Deploying Valaxy is very easy. We suggest that you build and deploy to any platform using third party CI. ## Manual Deployment ::: code-group ```bash [pnpm] pnpm run build ``` ```bash [bun] bunx valaxy build --ssg ``` ```bash [yarn] yarn build ``` ```bash [npm] npm run build ``` ::: Run the `build` command to build, and the `dist` directory contains the built content. SSG build requires a sufficient heap (~4 GB; the engine auto-respawns with enough memory). If you still encounter `JavaScript heap out of memory`, set: ```bash NODE_OPTIONS=--max-old-space-size=4096 pnpm build ``` ## Deploying under a base path When the site is served below the domain root, configure Vite's `base` with both leading and trailing slashes. `siteConfig.url` is the canonical site URL; it does not replace the asset base. For example, a GitHub Pages project site at `https://user.github.io/repo/` uses: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ siteConfig: { url: 'https://user.github.io/repo/', }, vite: { base: '/repo/', }, }) ``` Starting with Valaxy v1.0.0-rc.4, this behavior is aligned with VitePress and is enabled by default; there is no separate compatibility switch. The final `base` resolved by Vite is shared by the page, excerpt/router, and local-search Markdown renderers. Root-absolute links and static assets written in Markdown are adjusted automatically: ```md [Guide](/guide/) ![Logo](/logo.png) [Download PDF](/manual.pdf) ``` For dynamic URLs in Vue components or theme configuration, use `withBase()`: ```vue ``` Markdown page and file links receive `base` directly. Markdown images are normalized for Vue/Vite's asset pipeline, which applies the final `base` to root-absolute public resources in the generated output. External URLs and relative paths are left unchanged. Raw HTML `` links are also left unchanged so you can deliberately link outside the configured base. Raw HTML images can still be transformed by Vue/Vite. ## Third Party Deployment ::: tip The configuration files for the following third-party deployments are built into the Valaxy template project. You can use them as needed. If the deployment fails, we recommend that you first check for potential build errors locally using `npm run build`. ::: ### GitHub Pages ::: tip Repositories named `your-username.github.io` are served from `/` and do not need a custom `base`. Other repository names are supported as project sites; configure `base: '/repository-name/'` as described above. ::: ::: details .github/workflows/gh-pages.yml <<< @/../packages/create-valaxy/template-blog/.github/workflows/gh-pages.yml ::: When you use `pnpm create valaxy` to create a template project, it contains the file [`.github/workflows/gh-pages.yml`](https://github.com/YunYouJun/valaxy/blob/main/packages/create-valaxy/template-blog/.github/workflows/gh-pages.yml) for the CI workflow of GitHub Actions. - Select the Github repository, go to `Settings`-> `Action` -> `General` -> `Workflow permissions`, and select `read and write permissions`. - Push to your GitHub repository, and go to `Settings` -> `Pages`. Select `gh-pages` branch. > `gh-pages` has been automatically deployed by `.github/workflows/gh-pages.yml`. > Please note that the 'on.push.branches' in' gh-pages.yml' should be modified to the branch where your source code is located, and the default is 'main'. ### Netlify `netlify.toml` is built-in. ### Vercel - On Vercel Dashboard, click `Add New...`, then click `Project` to create a project. - Select the repository you want to deploy and click `Import` and then set `Framework Preset` to `Other` and modify `Build and Output Settings`. - Turn on the switch on the right of the textbox and type `dist`, click `Deploy`. - Wait for ribbons to drop on the screen, then visit your website. ::: details netlify.toml <<< @/../packages/create-valaxy/template-blog/netlify.toml ::: ### Cloudflare Pages - Login to your Cloudflare account and navigate to "Workers and Pages" page. - Click `Create a project` and `Connect to Git`, then select your GitHub or GitLab repository and click `Begin setup`. - Select your Production branch. - Set `Build output directory` to `pnpm build` . - Set `Build output directory` to `dist` . - Then click "Save and Deploy". ### Nginx > [Nginx Docs](https://nginx.org/en/docs/) Here is an example of an Nginx server block configuration `nginx.conf`. This configuration includes rules for gzip compression of common text-based resources, serving static files for a Valaxy site with appropriate caching headers, and handling `cleanUrls: true`. ::: details nginx.conf ```nginx [nginx.conf] server { gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; listen 80; server_name _; index index.html; location / { # content location # root /app; root /usr/share/nginx/html; # exact matches -> reverse clean urls -> folders -> not found try_files $uri $uri.html $uri/ =404; # non existent pages error_page 404 /404.html; # a folder without index.html raises 403 in this setup error_page 403 /404.html; # adjust caching headers # files in the assets folder have hashes filenames location ~* ^/assets/ { expires 1y; add_header Cache-Control "public, immutable"; } } } ``` ::: This configuration assumes that the built Valaxy site is located in the `/usr/share/nginx/html` directory on the server. If your site files are located elsewhere, adjust the `root` directive accordingly. ### Docker > [Docker Docs](https://docs.docker.com/) Here is an example Dockerfile for building a Valaxy site and deploying it to an Nginx server. Refer to the Nginx section for the `nginx.conf` configuration and place it in the same directory as the `Dockerfile`. ::: details Dockerfile ```Dockerfile [Dockerfile] FROM node:22.12-alpine as build-stage WORKDIR /app RUN corepack enable COPY .npmrc package.json pnpm-lock.yaml ./ RUN --mount=type=cache,id=pnpm-store,target=/root/.pnpm-store \ pnpm install --frozen-lockfile COPY . . RUN pnpm build FROM nginx:stable-alpine as production-stage COPY nginx.conf /etc/nginx/nginx.conf COPY --from=build-stage /app/dist /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` ::: ### Others You can also use [Render](https://render.com/) to host your website. ::: tip Valaxy is also a static site like VitePress. You can refer to the [VitePress Deployment Guide](https://vitepress.dev/guide/deploy) for deployment. ::: ## Features - **Categories**: getting-started First, I'll introduce you to some easy ## Hot Reloading It's most worth mentioning that Valaxy supports partial hot reloading, for configuration, post contents, animation, tags, categories, and much more! For example, if you modified `valaxy.config.ts`/`site.config.ts`, the content in `xxx.md`, or `frontmatter` (`tags`/`categories`), all changes will immediately appear on the preview page, and there is no need for manual refreshing. Also, hot reloads are local, meaning that only the place modified will change, and other elements on the page will not be refreshed. ## Customization Valaxy provides powerful customization support. You can customize every component for your theme and blog just like the Ship of Theseus. See more at [Customizing Components](/guide/custom/components). ## UnoCSS > The builtin TailwindCSS-like util class (based on [UnoCSS](https://github.com/unocss/unocss)). If you have used [TailwindCSS](https://tailwindcss.com/) before, then you will rapidly learn it's convenience. You can use it at will in your Markdown and Vue components, and it will finally get packaged by need and loaded. For example: ```md This is markdown.
This is markdown.
``` You will get the effect immediately like this:
This is markdown.
## Icones > Massive amount of icons You can use any icons that are from [Icônes](https://icones.js.org/). The naming rule is `i-${collection}-${name}`, e.g. `i-ri-home-line`. The theme by default has [RemixIcon](https://github.com/Remix-Design/RemixIcon) installed. If you need any icons from other collections, you can install yourself. For example: ```bash # `collection` is the name of the icon collection, e.g. @iconify-json/ri npm i @iconify-json/collection ``` All icon names added to `config.unocss.safelist` will be ready for hot reloading. ## UI ### Syntax Highlighting > More info about syntax highlighting can be found at [Markdown Syntax Highlighting](/guide/markdown#syntax-highlighting). Based on [Shiki](https://shiki.style). Valaxy supports syntax highlighting for languages like `vue`, and also supports copying code and highlighting a particular line in the code block. For example: ```js {2} const a = 1 const b = a ``` ### Custom Theme Color You only need to provide a theme color for the global color dynamics to work and show effect. For example, if I want my theme color to be red: > Supported by `valaxy-theme-yun` ```ts [valaxy.config.ts] export default { themeConfig: { colors: { primary: 'red', }, }, } ``` Even more, other themes can also re-use the default color dynamic functions provided by Valaxy to build their own. > Please refer to code in [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-theme-yun) for more. ## File-based Routing Routes will be auto-generated for Vue/Markdown files in this dir with the same file structure. Check out [`vue-router` file-based routing](https://router.vuejs.org/file-based-routing/) for more details. ## Building Supports SPA and SSG. ### SSG Uses the built-in Valaxy SSG engine (Vue SSR + pure string rendering, no JSDOM). ```bash # SSG npm run build:ssg # valaxy build --ssg ``` ### SPA ```bash npm run build:spa # valaxy build ``` ## SEO Valaxy by default has integrated SEO optimization by Open Graph, and you don't need to worry about that. Note that for many search engines, they like SSG builds more. ## RSS Valaxy comes with a command to generate RSS feeds. > [What is RSS?](https://en.wikipedia.org/wiki/RSS) For more configuration options, see [RSS Configuration](/guide/config/extend#rss). ```bash npm run rss # valaxy rss ``` ## i18n in One Page For more info, see [i18n](/posts/i18n). ## Math | 数学公式 Valaxy supports two math rendering engines: KaTeX (default, fast rendering) and MathJax (SVG output, no external CSS/fonts needed). ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ // KaTeX (enabled by default) features: { katex: true }, // Or switch to MathJax (install first: pnpm add markdown-it-mathjax3) // math: true, }) ``` - [Math Formulas | Examples](/examples/math) - [Load KaTeX from CDN](/guide/config/extend#cdn-externals) (Experimental) ## Auto Route Replacing When Valaxy detects that an `a` hyperlink in a post is an intra-site link (relative link), it will automatically replace it with a `RouterLink`. Enjoy the dynamic page switching! ## Getting Started - **Categories**: getting-started ## Overview Valaxy = V + Galaxy aims for the next generation static blog framework, providing better hot reloading and user loading experience, with easier and powerful customization support. You can learn more about the original intensions for this project in [Why Valaxy](/guide/why). ::: tip `Valaxy` is based on [Vite](https://vitejs.dev/) to provide hot reloading and packaging, and based on [Vue](https://vuejs.org/) to realize client functionalities such as views (themes, custom components). Therefore, Valaxy supports all extensions/plugins for Vite and Vue. ::: ## Create a Valaxy Project > Example: [yun.valaxy.site](https://yun.valaxy.site) ### Try it Online You can use [StackBlitz](https://stackblitz.com/edit/valaxy) to try Valaxy online (the default theme used is [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/)). [![StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/edit/valaxy) > This is an extremely simple project. You only need the following files to rapidly build your own blog! > > - `pages` folder: storing the pages/posts > - `valaxy.config.ts`: Valaxy's configuration file > - `package.json`: dependencies ### Locally ::: danger Compatibility Note Valaxy requires [Node.js](https://nodejs.org/en/) `>=22.12.0`. This comes from `unplugin-vue-markdown@32` (which requires Node `>=22`) combined with Vite 8 (which requires `^20.19.0 || >=22.12.0`) — so on the Node 22 line the minimum is `22.12.0`. Please upgrade Node.js to `22.12.0` or later. ::: ::: tip If you are a Windows user, I strongly recommend using a Unix-like shell (such as [Git Bash](https://git-scm.com/downloads) or [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) rather than CMD / PowerShell. ::: > Since `npm init` caches your previously downloaded version, I would recommend using `pnpm` to create templates. > Install [pnpm](https://pnpm.io/):`npm i -g pnpm` ::: code-group ```bash [pnpm] pnpm create valaxy ``` ```bash [bun] bun create valaxy ``` ```bash [npm] npm init valaxy ``` ::: ::: details You will be greeted with a few simple questions. ::: Follow the prompt in the commandline to complete the process! #### Select a Theme After selecting the Blog type, you will be prompted to choose a theme: - **Yun** (default): A light & clean blog theme - **Press**: A document-oriented theme - **Custom**: Enter a custom theme name (e.g. `starter` or the full package name `valaxy-theme-starter`) After selection, `create-valaxy` will automatically configure the `theme` field in `valaxy.config.ts` and the theme dependency in `package.json`. > The default theme used is [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/), but you can also install any other themes. > This documentation is also a Valaxy theme: [valaxy-theme-press](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-press/). It is inspired by [VitePress](https://vitepress.dev/). ## Usage > Enter the folder for the Valaxy project you just created, and execute the following commands. > For example: `cd valaxy-blog`. Install the dependencies: ::: code-group ```bash [pnpm] # install pnpm i ``` ```bash [bun] # install bun install ``` ```bash [npm] # install npm i ``` ::: Start a preview: ::: code-group ```bash [pnpm] # start pnpm dev ``` ```bash [bun] # start bun dev ``` ```bash [npm] # start npm run dev ``` ::: See `http://localhost:4859/`, have fun! - See [Config](/guide/config/) and [Custom Extensions](/guide/custom/extend) for the general configuration for Valaxy blogs. - For configuring Valaxy themes, please see the documentation for the corresponding themes. (Docs for Valaxy Theme Yun is still work in progress) ### Config Modify `valaxy.config.ts` to custom your blog. See [Config](/guide/config/) for basic configuration. Documentation is being improved! ## Deployment See [Deployment](/guide/deploy) for deployment guide. ## Upgrading ::: code-group ```bash [pnpm] cd your-blog # upgrade valaxy pnpm add valaxy@latest # upgrade theme pnpm add valaxy-theme-yun@latest ``` ```bash [bun] cd your-blog # upgrade valaxy bun add valaxy@latest # upgrade theme bun add valaxy-theme-yun@latest ``` ```bash [npm] cd your-blog # upgrade valaxy npm i valaxy@latest # upgrade theme npm i valaxy-theme-yun@latest ``` ::: ### pnpm > You can use the interactive upgrade command provided by `pnpm`. ```bash # interactive upgrade pnpm up --latest -i ``` ## Migration If you are from another blog framework, you can refer to [Migration](/migration/). ## Directory Structure In most cases, you only need to work in the `pages` folder. ### Main folders - `pages`: your all pages - `posts`: write your posts here, will be counted as posts - `styles`: override theme styles, `index.scss`/`vars.csss`/`index.css` will be loaded automatically - `components`: custom your vue components (will be loaded automatically) - `layouts`: custom layouts (use it by `layout: xxx` in md) - `locales`: custom i18n ### Others - `.vscode`: recommend some useful plugins & settings, you can preview icon/i18n/class... - `.github`: GitHub Actions to auto build & deploy to GitHub Pages - `netlify.toml`: for [netlify](https://www.netlify.com/) - `vercel.json`: for [vercel](https://vercel.com/) ## Themes If you want to develop a theme and released, you can refer to [valaxy-theme-starter](https://github.com/YunYouJun/valaxy-theme-starter). ## Community If you have questions or need help, you can go to the [Discord](https://discord.gg/nd3mPkU5j8) and [Discussions](https://github.com/YunYouJun/valaxy/discussions) to ask for help. ## i18n - **Categories**: guide ## Set Supported Languages {#set-supported-languages} ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ languages: ['zh-CN', 'en'], }) ``` ## Use i18n in Config {#use-i18n-in-config} If you want to add i18n support for `siteConfig.title`/`siteConfig.description`, you can set key-value pairs in `siteConfig`. ::: tip `$t` is a virtual function provided by Valaxy. It adds a special prefix `$locale:` to mark that the text needs to be internationalized. Later, Valaxy will automatically replace it with the corresponding language text on the page. Therefore, it remains reactive on the page. ::: For example: ```ts [site.config.ts] import { $t, defineSiteConfig } from 'valaxy' export default defineSiteConfig({ title: $t('siteConfig.title'), description: $t('siteConfig.description'), }) ``` Then create corresponding language files in the `locales` directory. ```yaml [locales/zh-CN.yml] siteConfig: title: 你好,世界 ``` ```yaml [locales/en.yml] siteConfig: title: Hello World ``` ## i18n in One Page {#i18n-in-one-page} ::: tip Valaxy **proposed** a CSS-based i18n solution for blog. You can quickly write English and Chinese blogs from the same page. > If you want to know how this works, see [i18n](/posts/i18n). ::: **The effect is as follows** (click the button to switch). Another i18n method. > More info... English --- **Written like this**: ```md Another i18n method. More info... English ``` ### Title i18n {#title-i18n} Of course, Valaxy supports i18n on titles. Works the same as above. You can write internationalized titles like this: ```md ### Hello World ``` ### Frontmatter i18n {#frontmatter-i18n} Internationalizing `title` and `description`: ```md --- title: en: Hello World zh-CN: 你好,世界 description: en: A simple i18n example zh-CN: 一个简单的 i18n 示例 --- ``` ### Category/Tag i18n {#categorytag-i18n} Valaxy automatically looks up `tag.{tagName}` / `category.{categoryName}` translations in your locale files. If a translation is found, the translated text is displayed; otherwise, the raw key is shown as-is. Simply write the tag/category **key** in your frontmatter — no special prefix needed: ```md [posts/hello-world.md] --- categories: - test tags: - notes --- ``` Then define the translations in your `locales` directory: ```yaml [locales/zh-CN.yml] category: test: 测试 tag: notes: 笔记 ``` ```yaml [locales/en.yml] category: test: Test tag: notes: Notes ``` ::: tip Tags/categories without a corresponding translation are displayed as-is. For example, a tag named `valaxy` will simply render as `valaxy` if `tag.valaxy` is not defined in your locale files. ::: ::: details Legacy `$locale:` prefix (backward compatible) Older versions required the `$locale:` prefix in frontmatter: ```md --- tags: - $locale:tag.notes categories: - $locale:category.test --- ``` This still works for backward compatibility, but the **simpler approach above is recommended**. ::: #### Validation Level {#validation-level} Valaxy validates taxonomy i18n during `valaxy dev` / `valaxy build`. You can control the behavior with three levels in `valaxy.config.ts`: - `off`: skip validation - `warn`: print warnings and continue - `error`: print all issues, then exit with an error ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ build: { taxonomyI18n: { level: 'warn', }, }, }) ``` ## index ## Layout - **Categories**: guide The framework API currently supports the following layouts by default. Layout support and final appearance are usually related to the theme. - `post`: Post layout - `tags`: Tags layout - `archives`: Archives layout - `categories`: Categories layout - `collections`: Collections layout ## Using Layouts ### Collections Layout Collections allow you to group a series of related articles (e.g. a novel, a tutorial series) into a single unit with ordered navigation. #### Directory Structure ```txt pages/ collections/ index.md # Collections overview page hamster/ # A single collection index.ts # Collection config (required) index.md # Collection entry page 1.md # Article 1 2.md # Article 2 to-be-or-not.md # Article with string key ``` #### 1. Create the Overview Page Create `pages/collections/index.md` with `layout: collections`: ```md [pages/collections/index.md] --- layout: collections icon: i-ri-gallery-view collections: - hamster - love-and-peace --- ``` #### 2. Create a Collection Create the collection folder `pages/collections/hamster/` with: - `index.ts`: Collection config file (required). - `index.md`: Collection entry page. - `1.md`, `2.md`, ...: Articles in the collection. Create the entry page `pages/collections/hamster/index.md`: ```md [pages/collections/hamster/index.md] --- layout: collection --- ``` Define the collection config in `index.ts`: ```ts [pages/collections/hamster/index.ts] import { defineCollection } from 'valaxy' export default defineCollection({ key: 'hamster', title: 'Hamster', cover: 'https://cover.sli.dev', description: 'The story of I and She', items: [ { title: 'Chapter 1 - The Cage', key: '1' }, { title: 'Chapter 2 - Daylight', key: '2' }, { title: 'Chapter 3 - Cocoon', key: '3' }, ], }) ``` #### 3. Create Articles > `layout: collection` can be omitted — all articles under `pages/collections/` use the `collection` layout by default. ```md [pages/collections/hamster/1.md] --- title: Chapter 1 - The Cage --- Your article content here. ``` Preview: [Collection | Valaxy Theme Yun](https://yun.valaxy.site/collections/hamster/1) ### CollectionConfig | Field | Type | Default | Description | |-------|------|---------|-------------| | `key` | `string` | Directory name | Unique identifier. Auto-derived from the directory name if omitted. | | `title` | `string` | — | Display title of the collection. | | `cover` | `string` | — | Cover image URL. | | `description` | `string` | — | Short description. | | `categories` | `string[]` | — | Categories for the collection card. | | `tags` | `string[]` | — | Tags for the collection card. | | `collapse` | `boolean` | `true` | Whether to show the collection as a single collapsed card in homepage/archive post lists. See [Collapse Mode](#collapse-mode). | | `items` | `{ title?, key?, link? }[]` | — | Ordered list of articles. `key` maps to the `.md` filename (e.g. `key: '1'` → `1.md`). `link` references an existing page or external URL. `key` and `link` are mutually exclusive; if both are set, `link` takes precedence. Determines the article reading order and prev/next navigation. | ### Collapse Mode ::: tip `collapse` is an experimental feature available since `v0.28.0`. ::: When `collapse` is `true` (default), the collection appears as a **single card** in the homepage and archive post lists. Since collection articles live under `/collections/`, they are not shown individually in these lists — the collapsed card provides a convenient entry point to the collection. ```ts export default defineCollection({ title: 'My Series', collapse: true, // default — show as one card items: [/* ... */], }) ``` When `collapse` is `false`, no synthetic entry is added to the post list. ```ts export default defineCollection({ title: 'My Series', collapse: false, // no card in post list items: [/* ... */], }) ``` ### Linking External Content You can reference existing blog posts or external URLs in a collection's reading order using the `link` field. This is useful when your collection includes content that lives outside the collection directory. - Internal links (starting with `/`) navigate within the site using ``. - External links (e.g. `https://...`) open in a new tab with an external-link icon. - `key` and `link` are mutually exclusive per item. If both are set, `link` takes precedence. ```ts export default defineCollection({ title: 'My Learning Path', items: [ { title: 'Chapter 1 - Basics', key: '1' }, { title: 'Related Blog Post', link: '/posts/my-related-article' }, { title: 'Chapter 2 - Advanced', key: '2' }, { title: 'External Reference', link: 'https://example.com/resource' }, ], }) ``` ## Implementing Layouts (Theme Developers) [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-theme-yun) supports the `collections` layout since `v0.25.9`. By convention, themes should create layout files in the `layouts` directory, with the filename matching the layout name. The following composables are available for collection support in themes: - `useCollections()` — Get all collection configs. - `useCollection()` — Get the current collection (resolved from the route path). - `useCollectionPosts(key)` — Get posts belonging to a specific collection, sorted by the order defined in `items`. - `usePostListWithCollections()` — Get the post list with collapsed collection entries merged in. <<< @/../packages/valaxy-theme-yun/layouts/collections.vue ## FAQ ### Child pages with multiple layout nesting Vue Router pages will automatically nest parent layouts, please refer to [Nested Routes | Unplugin Vue Router](https://uvr.esm.is/guide/file-based-routing#nested-routes). For example, change: `pages/users/create.vue` to `pages/users.create.vue`. ## Markdown Extensions - **Categories**: guide ::: info Unlike `Hexo`, `Valaxy` implements some Markdown extensions (such as Container, math formulas) at the framework level, without requiring theme developers to implement them again. This is similar to many features of `VitePress`. `Valaxy` has borrowed a lot from `VitePress` and reuses plugins from [mdit-vue](https://github.com/mdit-vue/mdit-vue). However, there are some differences. Valaxy uses [KaTeX](https://katex.org/) by default (fast rendering speed), and also supports [MathJax](https://www.mathjax.org/) (aligned with VitePress, SVG output without external CSS/fonts). > **Note**: Do not enable `features.katex` and `math` at the same time. They use different rendering engines, and enabling both may cause duplicate rendering or style conflicts. When `math` (MathJax) is enabled, `features.katex` will be automatically ignored. ```ts [valaxy.config.ts] export default defineValaxyConfig({ // KaTeX (enabled by default) features: { katex: true }, // Or switch to MathJax (install first: pnpm add markdown-it-mathjax3) // math: true, }) ``` Of course, you can still add MarkdownIt plugins in Valaxy to implement more features. ::: ## Using Vue in Markdown You can directly import and use Vue components in Markdown files. For example, create a Vue component `CustomVueDemo.vue` in the `components` directory: <<< @/components/CustomVueDemo.vue [components/CustomVueDemo.vue] ```md [pages/posts/xxx.md] --- title: Using Vue in Markdown --- ``` ## Emoji :tada: **Input** ```md :tada: :100: ``` **Output** :tada: :100: A [list of all emojis](https://github.com/markdown-it/markdown-it-emoji/blob/master/lib/data/full.mjs) is available. ## Table of Contents **Input** ```md [[toc]] ``` **Output** [[toc]] Rendering of the TOC can be configured using the `markdown.toc` option. ## Line of Code Highlighting **Input** ````md ```js{4} export default { data () { return { msg: 'Highlighted!' } } } ``` ```` **Output** ```js{4} export default { data () { return { msg: 'Highlighted!' } } } ``` **Input** ````md ```ts {1} // line-numbers is disabled by default const line2 = 'This is line 2' const line3 = 'This is line 3' ``` ```ts:line-numbers {1} // line-numbers is enabled const line2 = 'This is line 2' const line3 = 'This is line 3' ``` ```ts:line-numbers=2 {1} // line-numbers is enabled and start from 2 const line3 = 'This is line 3' const line4 = 'This is line 4' ``` ```` **Output** ```ts {1} // line-numbers is disabled by default const line2 = 'This is line 2' const line3 = 'This is line 3' ``` ```ts:line-numbers {1} // line-numbers is enabled const line2 = 'This is line 2' const line3 = 'This is line 3' ``` ```ts:line-numbers=2 {1} // line-numbers is enabled and start from 2 const line3 = 'This is line 3' const line4 = 'This is line 4' ```` ## Colored Diffs in Code Blocks Adding the `// [!code --]` or `// [!code ++]` comments on a line will create a diff of that line, while keeping the colors of the codeblock. **Input** Note that only one space is needed after `!code`, there are two spaces here in case it is rendered. ````md ```js export default { data () { return { msg: 'Removed' // [!!code --] msg: 'Added' // [!!code ++] } } } ``` ```` **Output** ```js export default { data() { return { msg: 'Removed', // [!code --] msg: 'Added', // [!code ++] } } } ``` ## Errors and Warnings in Code Blocks Adding the `// [!code warning]` or `// [!code error]` comments on a line will color it accordingly. **Input** Note that only one space is needed after `!code`, there are two spaces here in case it is rendered. ````md ```js export default { data () { return { msg: 'Error', // [!!code error] msg: 'Warning' // [!!code warning] } } } ``` ```` **Output** ```js export default { data() { return { msg: 'Error', // [!code error] msg: 'Warning' // [!code warning] } } } ``` ## Import Code Snippets You can import code snippets from existing files via following syntax: ```md <<< @/filepath ``` It also supports [line highlighting](#line-of-code-highlighting): ```md <<< @/filepath{highlightLines} ``` **Input** ```md <<< @/snippets/snippet.js{2} ``` **Code file** <<< @/snippets/snippet.js **Output** <<< @/snippets/snippet.js ::: tip The value of `@` corresponds to the source root. By default it's the blog root, unless `srcDir` is configured. Alternatively, you can also import from relative paths: ```md <<< ../snippets/snippet.js ``` ::: You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/codebasics#_folding) to only include the corresponding part of the code file. You can provide a custom region name after a `#` following the filepath: **Input** ```md <<< @/snippets/snippet-with-region.js#snippet{1} ``` **Code file** <<< @/snippets/snippet-with-region.js **Output** <<< @/snippets/snippet-with-region.js#snippet{1} You can also specify the language inside the braces (`{}`) like this: ```md <<< @/snippets/snippet.cs{c#} <<< @/snippets/snippet.cs{1,2,4-6 c#} <<< @/snippets/snippet.cs{1,2,4-6 c#:line-numbers} ``` This is helpful if source language cannot be inferred from your file extension. ## Container By configuring `markdownIt`, you can set the text and icon (and its color) for custom block. ::: tip tip ::: ::: warning warning ::: ::: danger danger ::: ::: info info ::: ```md ::: details Click to expand Details Content ::: ```md ::: details Click to expand Details Content ::: ``` You can also customize new container names. ```md ::: custom I am a custom block. ::: ``` ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ markdown: { blocks: { custom: { icon: 'i-ri:info-i', text: 'CUSTOM', }, } } }) ``` ## Add Code Block Title And Icons ::: tip More code block icon examples can be found [here](/examples/code-block-icons). ::: It is implemented based on [vitepress-plugin-group-icons](https://github.com/yuyinws/vitepress-plugin-group-icons), with some [built-in icons](https://vp.yuy1n.io/features.html#built-in-icons). You can customize more icons as follows. ```ts [valaxy.config.ts] {5-14} import { defineValaxyConfig } from 'valaxy' import { localIconLoader } from 'vitepress-plugin-group-icons' export default defineValaxyConfig({ groupIcons: { customIcon: { // valaxy: 'https://valaxy.site/favicon.svg', valaxy: localIconLoader(import.meta.url, './public/favicon.svg'), nodejs: 'vscode-icons:file-type-node', playwright: 'vscode-icons:file-type-playwright', typedoc: 'vscode-icons:file-type-typedoc', eslint: 'vscode-icons:file-type-eslint', dockerfile: 'vscode-icons:file-type-docker', }, } }) ``` Now, use the following syntax: ````md ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({}) ``` ```dockerfile [sample.dockerfile] FROM ubuntu ENV PATH /opt/conda/bin:$PATH ``` ```` We will get a code block with the `valaxy.config.ts` title and Valaxy icon: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({}) ``` And we will also get a code block with the `sample.dockerfile` title and Docker icon: ```dockerfile [sample.dockerfile] FROM ubuntu ENV PATH /opt/conda/bin:$PATH ``` ## Math Formulas ::: tip More information about math formula examples can be found [here](/examples/math). ::: **Input** ```md When $a \ne 0$, there are two solutions to $(ax^2 + bx + c = 0)$ and they are $$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$ **Maxwell's equations:** | equation | description | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | $\nabla \cdot \vec{\mathbf{B}} = 0$ | divergence of $\vec{\mathbf{B}}$ is zero | | $\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}}$ | curl of $\vec{\mathbf{E}}$ is proportional to the rate of change of $\vec{\mathbf{B}}$ | | $\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho$ | _wha?_ | ``` **Output** When $a \ne 0$, there are two solutions to $(ax^2 + bx + c = 0)$ and they are $$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$ **Maxwell's equations:** | equation | description | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | $\nabla \cdot \vec{\mathbf{B}} = 0$ | divergence of $\vec{\mathbf{B}}$ is zero | | $\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}}$ | curl of $\vec{\mathbf{E}}$ is proportional to the rate of change of $\vec{\mathbf{B}}$ | | $\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho$ | _wha?_ | ### Custom KaTeX Options > [KaTeX options](https://katex.org/docs/options.html) ```ts [valaxy.config.ts] export default defineValaxyConfig({ markdown: { /** * KaTeX options * @see https://katex.org/docs/options.html */ katex: { strict: false } } }) ``` ## Markdown File Inclusion ::: tip You can also prefix the markdown path with `@`, it will act as the source root. By default, it's the Valaxy project root. ::: **Input** ```md [your-file.md] ## Docs ``` **Part file** ::: code-group ```md [parts/basics.md] Some getting started stuff. ### Configuration Can be created using `.foorc.json`. ``` ```md [TEST.md] I'm a TEST. ``` ::: **Equivalent code** ```md ## Docs I'm a TEST. Some getting started stuff. ### Configuration Can be created using `.foorc.json`. ``` It also supports selecting a line range: **Input** ```md ## Docs ``` **Part file** ::: code-group ```md [parts/basics.md] Some getting started stuff. ### Configuration Can be created using `.foorc.json`. ``` ```md [TEST.md] I'm a TEST. ``` ::: **Equivalent code** ```md ## Docs I'm a TEST. ### Configuration Can be created using `.foorc.json`. ``` The format of the selected line range can be: `{3,}`, `{,10}`, `{1,10}` ::: warning Note that this does not throw errors if your file is not present. Hence, when using this feature make sure that the contents are being rendered as expected. ::: ## UnoCSS We integrated [UnoCSS](https://unocss.dev), so you can use it in your markdown file. Freedom to control your layout! > More configurations see [UnoCSS Options](/guide/config/unocss-options).
![image](https://www.yunyoujun.cn/images/avatar.jpg)
![image](https://www.yunyoujun.cn/images/avatar.jpg)
![image](https://www.yunyoujun.cn/images/avatar.jpg)
![image](https://cdn.yunyoujun.cn/img/bg/stars-timing-1.jpg) ![image](https://cdn.yunyoujun.cn/img/bg/astronaut.webp)
```html [pages/posts/your-post.md]
![image](https://www.yunyoujun.cn/images/avatar.jpg)
![image](https://www.yunyoujun.cn/images/avatar.jpg)
![image](https://www.yunyoujun.cn/images/avatar.jpg)
![image](https://cdn.yunyoujun.cn/img/bg/stars-timing-1.jpg) ![image](https://cdn.yunyoujun.cn/img/bg/astronaut.webp)
``` ## Mermaid Based on [mermaid](https://mermaid.js.org/), you can use it in your markdown file directly. ```mermaid graph TD; A-->B; A-->C; B-->D; C-->D; ``` ````txt ```mermaid graph TD; A-->B; A-->C; B-->D; C-->D; ``` ```` More examples see: [Mermaid](/examples/mermaid) ### PlantUML PlantUML is not built-in because it requires an external server. You can configure it yourself via `markdown.transforms`: ```ts [valaxy.config.ts] import { Buffer } from 'node:buffer' import { defineValaxyConfig } from 'valaxy' const PLANTUML_SERVER = 'https://www.plantuml.com/plantuml' export default defineValaxyConfig({ markdown: { transforms: { before(code) { return code.replace( /^```plantuml\n([\s\S]+?)\n```/gm, (_, uml: string) => { const encoded = Buffer.from(uml.trim()).toString('hex') return `PlantUML diagram` }, ) }, }, }, }) ``` Then use it in your markdown: ````txt ```plantuml Alice -> Bob: Hello Bob --> Alice: Hi! ``` ```` ::: tip This uses the [official PlantUML server](https://www.plantuml.com/plantuml) by default. You can replace `PLANTUML_SERVER` with your own server address. For most use cases, [Mermaid](#mermaid) is recommended as it works out of the box without any external dependencies. ::: ## Footnote You can use `[^1]` or `[^footnote]` to add footnotes, for example: ```md This is a footnote[^1-en]. This is a paragraph of footnote[^2-en]. [^1-en]: This is a footnote. [^2-en]: This is a paragraph of footnote. Footnote paragraphs with correct indentation will be automatically attached. Use `^[content]` to create convenient inline footnotes^[like this!]. ``` This is a footnote[^1-en]. This is a paragraph of footnote[^2-en]. [^1-en]: This is a footnote. [^2-en]: This is a paragraph of footnote. Footnote paragraphs with correct indentation will be automatically attached. Use `^[content]` to create convenient inline footnotes^[like this!]. ### Footnote Preview With [`Floating Vue`](https://floating-vue.starpad.dev/), the added footnote links will display the footnote content when hovering over them. You can try it with the footnote links on this page! If you want to customize the style of the footnote, you can refer to `config` in the [Floating Vue documentation](https://floating-vue.starpad.dev/guide/config) and change the `floatingVue` option in `site.config.ts` accordingly. You can also modify the `ValaxyFootnoteTooltip` component to achieve this. ## Custom ### Custom Markdown Container Class You can add `markdownClass` in the frontmatter of the markdown file to customize the Class of the Markdown container. ```md --- markdownClass: 'markdown-body custom-markdown-class' --- ``` ## Page - **Categories**: guide ## FrontMatter You can custom page by front-matter. ::: tip More configuration options can be found in: - Page configuration: [PageFrontmatter](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/types/frontmatter/page.ts) ::: details PageFrontmatter Types <<< @/../packages/valaxy/types/frontmatter/page.ts#snippet{29-194 ts:line-numbers} ::: ### titleTemplate ```md --- title: Cool titleTemplate: '%s - Valaxy' --- ``` You will get html title `Cool - Valaxy`. ### Encrypt Page ::: warning Encryption relies on the browser's native [Web Crypto API | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API), **It is only available in HTTPS**. ::: ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ encrypt: { // 开启加密,默认关闭 enable: true // algorithm // iv // salt } }) ``` Add `password: YourPassword` to the frontmatter of the corresponding page to enable encryption. When `encrypt.enable` is `true`, and the password `password` exists in the page, encryption is enabled by default. The encrypted content should be dynamically rendered after decryption. At this time, it cannot (and should not) participate in the build process to generate static artifacts (otherwise it will be seen directly). ```md --- password: valaxy --- ``` ### Other - `sidebar: false`: Hide Left Sidebar - `aside: false`: Hide Right Aside - `toc: false`: Hide TOC - `codeHeightLimit: 300`: Code block height limit(300px) ## Post - **Categories**: guide > [Post VS Page](https://wordpress.com/zh-cn/support/post-vs-page/) ## FrontMatter ::: tip More configuration options can be found in: - Post configuration: [PostFrontmatter](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/types/frontmatter/post.ts) (Post configuration extends page configuration) - Page configuration: [PageFrontmatter](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/types/frontmatter/page.ts) (See [Page | Valaxy](/guide/page)) ::: details PostFrontmatter Types <<< @/../packages/valaxy/types/frontmatter/post.ts#snippet{ts:line-numbers} ::: `post` is a descendant of `page`, so the front matter in **pages** are supported by **posts**. For example: ```md --- title: Title hide: true --- ``` - `title`: Title of the article. - `hide`: Adding `hide` in the header allows you to hide the article temporarily. (The article will still be rendered) - `true` / `all`: When set to `true` or `all`, the article will be rendered, and you can view it by visiting the link directly. It will not be displayed in article cards or archives. - `index`: When set to `index`, it will be hidden only in the front page. It will still be displayed in archives. (You can use this for some notes unnecessary for the front page, but good for the archive for reference sometimes) ## Excerpt You can insert `` to generate an excerpt. You can set the excerpt rendering type by setting `excerpt_type`. - `excerpt`: Custom excerpt (higher priority than ``) - `excerpt_type`: The rendering type for the excerpt in the preview list (Used with ``) - `md`: Display as original markdown - `html`: Display as HTML - `text`: Display as text (removing HTML tags) ::: code-group ```md{3,10} [excerpt_type: text] --- title: 'excerpt_type: text' excerpt_type: text --- ## Header ![yun-bg](https://cdn.yunyoujun.cn/img/bg/stars-timing-0-blur-30px.jpg) Main Content ``` ```md{3,10} [excerpt_type: md] --- title: 'excerpt_type: md' excerpt_type: md --- ## Header ![yun-bg](https://cdn.yunyoujun.cn/img/bg/stars-timing-0-blur-30px.jpg) Main Content ``` ```md{3,10} [excerpt_type: html] --- title: 'excerpt_type: html' excerpt_type: html --- ## Header ![yun-bg](https://cdn.yunyoujun.cn/img/bg/stars-timing-0-blur-30px.jpg) Main Content ``` ```md{3} [custom excerpt] --- title: 'custom excerpt' excerpt: This is a custom excerpt. --- ## Header ![yun-bg](https://cdn.yunyoujun.cn/img/bg/stars-timing-0-blur-30px.jpg) Main Content ``` ::: You will get excerpt: ::: code-group ```md [excerpt_type: text] HEADER yun-bg ``` ```md [excerpt_type: md] ## Header ![yun-bg](https://cdn.yunyoujun.cn/img/bg/stars-timing-0-blur-30px.jpg) ``` ```md [excerpt_type: html] ``` ```md [custom excerpt] This is a custom excerpt. ``` ::: ## Insert ### Components - To insert existing public components in the article, please refer to [Components](/guide/built-ins). - To insert custom components in the article, please refer to [Custom Components](/guide/custom/components). ### Scripts You can use [`useScriptTag`](https://vueuse.org/core/useScriptTag/) directly, encapsulate it as a component, or add it directly to the article. ```vue ``` ## Force Standard Since Valaxy supports parsing Vue component rendering, when you enter ``, it will parse the `CustomComponent.vue` component in the `components` directory and render it. When you don't want it to be rendered, be sure to wrap it in backticks, like: ```md `` ``` ## SSR Compatibility - **Categories**: guide ## SSR Compatibility Valaxy builds your site using SSG (Static Site Generation), which renders pages to HTML at build time via Vue's server-side rendering (SSR). This means components run in a Node.js environment during the build, where browser APIs like `window`, `document`, and `navigator` are not available. ::: warning Upgrading from the old `vite-ssg` engine Valaxy used to ship a JSDOM-based `vite-ssg` engine, **removed in v1.0** (see [#706](https://github.com/YunYouJun/valaxy/issues/706)). JSDOM silently provided `window`, `document`, and `navigator` during SSR, so code that touched these globals at render time appeared to "work". The Valaxy SSG engine renders pure strings with **no** DOM, so the same code now throws or hydrates incorrectly. If you are upgrading and a theme/addon relied on a DOM during SSR, guard every browser-only access with the patterns below. ::: ### Why Hydration Mismatches Happen After SSG generates static HTML, Vue "hydrates" it in the browser — attaching event listeners and making it interactive. If the HTML rendered on the server differs from what the client renders, you get a **hydration mismatch** warning. Common causes: | Cause | Example | |-------|---------| | Browser-only API in template | `{{ window.innerWidth }}` | | Time/locale-dependent values | `{{ new Date().toLocaleString() }}` | | Browser extensions modifying HTML | Ad blockers injecting elements | | Non-standard HTML nesting | `

` inside `

`, `

` inside `` | ### `` Wrap browser-only content with the built-in `` component. Its content is only rendered on the client side. ```vue ``` Use the `#fallback` slot to show placeholder content during SSR/SSG: ```vue ``` ### `defineClientComponent` For third-party libraries that access browser APIs at import time (not just at render time), use `defineClientComponent`. It delays the `import()` until the component mounts in the browser. ```vue ``` You can pass props and a callback: ```vue ``` ### `onMounted` + `ref` Pattern For simple cases where you need browser APIs in logic (not in third-party imports), use Vue's `onMounted`: ```vue ``` ### `import.meta.env.SSR` Use the `import.meta.env.SSR` flag (provided by Vite) to conditionally execute code: ```ts if (!import.meta.env.SSR) { // This code only runs in the browser document.addEventListener('scroll', handleScroll) } ``` > This is useful in composables or setup functions where you need to guard browser-only side effects. ### CSS-Based Responsive Rendering Avoid using `v-if` with reactive viewport values for responsive layouts — this causes hydration mismatches because the server cannot know the viewport size. Use CSS instead: ```vue ``` ### Tips for Theme & Addon Developers - Always test with `pnpm demo:build` (SSG build) — `pnpm demo` (dev mode) won't catch SSR issues. - Wrap all browser-only third-party components with `` or `defineClientComponent`. - Never access `window`, `document`, or `navigator` at the top level of a ` ``` > You can configure dark mode options through `themeConfig.valaxyDarkOptions`. ::: details Default Theme Config.valaxyDarkOptions <<< @/../packages/valaxy/types/default-theme.ts {6-41 ts:line-numbers} ::: ### Node #### Hooks - [Hooks](/guide/custom/hooks.md) ## Start Writing ### App.vue > Your entry file For example, I want to add a global Loading page for the theme. You can import the global state `useAppStore` from valaxy and use `showLoading` to implement this. > You can also use your own global state management. See [Global State Management](#global-state-management). ```vue [valaxy-theme-yun/App.vue] ``` ::: tip - You can completely override the root component through the `ValaxyApp.vue` component to achieve deeper customization needs. (Completely customized by you, no longer default handling such as mounting `router-view`, etc.) ::: ### ValaxyMain You need to customize a `ValaxyMain` component to define the article rendering part of the theme. > You can get `frontmatter` and `pageData` from the `props` of `ValaxyMain`. ```vue [valaxy-theme-yun/components/ValaxyMain.vue] ``` > See [ValaxyMain.vue | valaxy-theme-yun](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/components/ValaxyMain.vue) for an example. ## Styles ### Import Default Styles Valaxy provides some default styles that you need to import in your theme. For example, create `valaxy-theme-yun/setup/main.ts`: ```ts [setup/main.ts] import { defineAppSetup, scrollTo } from 'valaxy' import { nextTick } from 'vue' // Import valaxy common styles import 'valaxy/client/styles/common/index.scss' // You can also import on demand // common import 'valaxy/client/styles/common/code.scss' import 'valaxy/client/styles/common/hamburger.scss' import 'valaxy/client/styles/common/transition.scss' // Markdown Style import 'valaxy/client/styles/common/markdown.scss' export default defineAppSetup((ctx) => { const { router, isClient } = ctx if (!isClient) return router.afterEach((to, from) => { if (to.path !== from.path) return nextTick(() => { scrollTo(document.body, to.hash, { smooth: true, }) }) }) }) ``` ### Markdown Styles Markdown styles are part of how a theme presents article content and need to be customized by the theme. You can refer to how [valaxy-theme-press](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-press/) customizes its Markdown theme. See [styles/markdown.scss](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-press/styles/markdown.scss). > If you want to use common default styles first (and customize them later), you can directly use [star-markdown-css](https://github.com/YunYouJun/star-markdown-css). > See [valaxy-theme-yun/styles](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/styles/index.scss) for usage. ### NProgress Progress Bar Built-in basic [nprogress](https://github.com/rstacruz/nprogress) styles are included. You can customize them by overriding the default nprogress styles: ```scss [your-theme/styles/index.scss] #nprogress { pointer-events: none; .bar { background: var(--va-c-primary); opacity: 0.75; position: fixed; z-index: 1024; top: 0; left: 0; width: 100%; height: 2px; } } ``` ## Features ### API > You can also use Valaxy's built-in APIs to quickly implement related features. #### Get User's Valaxy Config You can get the user's Valaxy configuration through the built-in `useValaxyConfig`. ::: tip This configuration corresponds to the user's settings in `valaxy.config.ts`, but it is only used on the client side, so it does not include Node-side configurations (such as `vite`, etc.). ::: ```ts [composables/config.ts] import { useSiteConfig, useValaxyConfig } from 'valaxy' import { useThemeConfig } from 'valaxy-theme-custom' const config = useValaxyConfig() // site.config.ts or config.value.siteConfig const siteConfig = useSiteConfig() // theme.config.ts or config.value.themeConfig const themeConfig = useThemeConfig() ``` #### Provide Typed useThemeConfig You can provide a theme-specific `useThemeConfig` function so that you and your users can get type-constrained configuration. ```ts [composables/config.ts] // custom your theme type import type { YunTheme } from '../types' import { useValaxyConfig } from 'valaxy' /** * getThemeConfig */ export function useThemeConfig() { const config = useValaxyConfig() return computed(() => config!.value.themeConfig) } ``` ```vue [components/Example.vue] ``` #### Get Post List There are two ways to get the post list. - `usePostList`: Get the post list (not recommended) ```ts import { usePostList } from 'valaxy' const postList = usePostList() ``` - `useSiteStore`: Get global site information (recommended) ```ts const site = useSiteStore() // site.postList ``` The difference between the two is that `usePostList` is a basic function that fetches all posts and re-filters them on every call, while `useSiteStore` calls `usePostList` once and caches the post list in global state for subsequent use. (Additionally, `useSiteStore` also implements hot-updating the list when saving posts, e.g., updating the title.) > [valaxy/packages/valaxy-theme-yun/components/YunPostList.vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/components/YunPostList.vue) is an example of using `useSiteStore` to display the post list. > For pagination, see [valaxy-theme-yun/pages/page/[page].vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/pages/page/%5Bpage%5D.vue) and [valaxy-theme-yun/components/YunPostList.vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/components/YunPostList.vue). #### Get Post Categories and Tags After getting the post list, each post in `site.postList` has `categories` and `tags` properties. You can also use `useCategories` and `useTags` to get all categories and tags, which include the mapping to their corresponding posts. ```ts import { useCategories, useTags } from 'valaxy' const categories = useCategories() const tags = useTags() ``` - [valaxy/packages/valaxy-theme-yun/layouts/categories.vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/layouts/categories.vue) is an example of using `useCategories` to display post categories. - [valaxy/packages/valaxy-theme-yun/layouts/tags.vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/layouts/tags.vue) is an example of using `useTags` to display post tags. ([`useYunTags`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/composables/tags.ts) is the theme's wrapper around `useTags`.) > In `useTags`, `tags` is an object where the key is the tag name and the value is the corresponding post list. > `useCategories` accepts a `category` parameter (`useCategories('aaa')`) to get the post list for a specific category. #### Get Front-matter You can get the current page's Front-matter through `useFrontmatter`. For example: ```vue ``` #### Global State Management You can use [Pinia](https://pinia.vuejs.org/) (built into Valaxy) to create your own global state and use it later. ```ts [stores/app.ts] import { acceptHMRUpdate, defineStore } from 'pinia' // custom your theme name export const useYunAppStore = defineStore('yun-app', () => { // global cache for yun return {} }) if (import.meta.hot) import.meta.hot.accept(acceptHMRUpdate(useYunAppStore, import.meta.hot)) ``` ```ts // where you want to use // components/YunExample.vue import { useYunAppStore } from '../stores/app' const yun = useYunAppStore() ``` #### Previous/Next Post Navigation for switching between the previous and next post is typically placed at the bottom of an article. You can implement it yourself using `siteStore.postList`, or use Valaxy's built-in `usePrevNext`. > See: [valaxy-theme-yun/components/YunPrevNext.vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/components/YunPostNav.vue) ```ts import { usePrevNext } from 'valaxy' const [prev, next] = usePrevNext() // prev/next type is PostFrontMatter // prev.title prev.path ``` ### Table of Contents If you want to quickly implement a table of contents, Valaxy provides a built-in hook function `useOutline`. You can use it to quickly get the `headers` (outline information) and corresponding `handleClick` event for article pages. For example: ```vue ``` > For more details, see [PressOutline | valaxy-theme-press](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-press/components/PressOutline.vue). ## Referencing Static Assets When your theme needs to include some static assets (e.g., images), you can use relative imports. (This also applies in `scss` style files.) For example, when `assets` and `components` are in the same directory: ```bash ├── components │ └── ValaxyLogo.vue └── assets └── images └── valaxy-logo.png ``` ```vue [components/ValaxyLogo.vue] ``` ## Third Party Plugin ### Implement Comments As a blog, users typically have commenting needs. Due to the variety of comment systems, theme developers like Hexo often need to repeatedly implement multiple comment systems on the theme side. This is obviously tedious. Valaxy decided to centrally provide various packaged comment components and helper functions through plugins. For example, theme developers can use `valaxy-addon-waline` to quickly integrate the [Waline](https://waline.js.org/) comment system. Users can use the same configuration to roam between different themes. > For integration, see [valaxy-addon-waline](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-addon-waline/README.md). ## Performance Optimization ### Add Dep Pre-bundling `optimizeDeps` - [Why | Dep Pre-bundling](https://vitejs.dev/guide/dep-pre-bundling.html#the-why) To improve the loading performance of subsequent pages, Vite bundles ESM dependencies with many internal modules into a single module. If your theme depends on some large ESM packages, you can pre-build these dependencies by adding the `optimizeDeps` option. > `dayjs` has been pre-built by default, you don't need to add it again. > [Why use dayjs instead of date-fns?](https://api.valaxy.site/notes/app-bundle-size.html#date-fns-vs-dayjs?) ```ts [valaxy.config.ts] import { defineTheme } from 'valaxy' export default defineTheme({ vite: { optimizeDeps: { include: ['lodash-es'], }, } }) ``` ### Using Addon Config in Themes {#using-addon-config-in-themes} When your theme integrates with optional addons (e.g., Algolia search, Waline comments), you can use `useAddonConfig` from `valaxy` to read addon options **without** adding a hard dependency on the addon package. ```vue [components/ThemeSearch.vue] ``` This avoids the previous pattern of using dynamic `import('valaxy-addon-xxx')` with `.then()` / `.catch()`, which was error-prone and not reactive. ### Remind Users with Special Needs to Install Third-party Plugins If your theme adapts to multiple `addon`s, but not all users need to install them. Such as comment plugins: - `valaxy-addon-waline` - `valaxy-addon-twikoo` When a user hasn't actively installed the corresponding `addon` (i.e., the `addon` doesn't exist), it will default to redirecting to an empty function. Therefore, if a plugin is not required, please remind users who want to use this feature to install the corresponding plugin in the theme documentation. ## Theme Yun - **Categories**: theme ::: tip Type definitions: [valaxy-theme-yun/types/index.d.ts](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/types/index.d.ts) ::: `valaxy-theme-yun` is the default blog theme for Valaxy. It focuses on personal blogs, post archives, friend links, animated home banners, and theme-level customization. ## Quick Start {#quick-start} ```bash pnpm add valaxy-theme-yun ``` ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ theme: 'yun', themeConfig: { type: 'nimbo', }, }) ``` You can also extract the theme config into a separate `theme.config.ts` file: ```ts [theme.config.ts] import { defineThemeConfig } from 'valaxy-theme-yun' export default defineThemeConfig({ type: 'nimbo', }) ``` ## Documentation Map {#documentation-map} - [Config Reference](/themes/yun/config): theme type, colors, navigation, pages, sidebar, and footer. - [Layout And Visuals](/themes/yun/layout): banner, background image, and layout-related options. - [Widgets And Pages](/themes/yun/widgets): notice, say, fireworks, post card types, menu, and friend links. - [Customization](/themes/yun/customization): edit links, outline title, and style overrides. ## valaxy-addon-abbrlink - **Categories**: addon Official package documentation included from the valaxy-addon-abbrlink README. ## valaxy-addon-algolia - **Categories**: addon Official package documentation included from the valaxy-addon-algolia README. ## valaxy-addon-bangumi - **Categories**: addon Official package documentation included from the valaxy-addon-bangumi README. ## valaxy-addon-components - **Categories**: addon Official package documentation included from the valaxy-addon-components README. ## valaxy-addon-feishu - **Categories**: addon Official package documentation included from the valaxy-addon-feishu README. ## valaxy-addon-lightgallery - **Categories**: addon Official package documentation included from the valaxy-addon-lightgallery README. ## valaxy-addon-meting - **Categories**: addon Official package documentation included from the valaxy-addon-meting README. ## valaxy-addon-moments - **Categories**: addon Official package documentation included from the valaxy-addon-moments README. ## valaxy-addon-twikoo - **Categories**: addon Official package documentation included from the valaxy-addon-twikoo README. ## valaxy-addon-waline - **Categories**: addon Official package documentation included from the valaxy-addon-waline README. ## Components - **Categories**: guide Valaxy has several simple components built in. You can use them directly when writing articles or themes. ::: tip
Based on Vue components
::: ## Basic Components ::: info Built for theme developers (common users usually do not need to use them directly) ::: ### Layout and Rendering - [`ValaxyMain.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyMain.vue): Basic page layout - [`ValaxyMd.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyMd.vue): Rendered Markdown content ### Others - [`AppLink.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/AppLink.vue): The link automatically determines whether it is an intra-site link. Use `` for intra-site links and `
`for external links. - [`ValaxyCopyright.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyCopyright.vue): The copyright information in the article. - [`ValaxyDecrypt.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyDecrypt.vue): Text decryption component - [`ValaxyGalleryDecrypt.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyGalleryDecrypt.vue): Picture decryption component - [`ValaxyLogo.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyLogo.vue): Valaxy Logo with gradient color - [`ValaxySvgLogo.vue`](): Valaxy SVG Logo - [`ValaxyPagination.vue`](): Paging component - [`ValaxyOverlay.vue`](): Grey mask component - [`ValaxyHamburger.vue`](): Hamburger button ```md ``` ## Helper Components ### 内置组件 > For users, can be used directly. You can also extend public components by [valaxy-addon-components](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-components). #### Internationalization Component `` ```yaml [locales/zh-CN.yml] menu: posts: 博客文章 ``` ```yaml [locales/en.yml] menu: posts: Posts ``` ```md ``` ### 扩展公共组件 ```bash [pnpm] pnpm add valaxy-addon-components ``` 如: - `CodePen`: CodePen code snippets - `VCLiveTime`: The establishment time of the site ```md [pages/posts/your-post.md] My Blog Content ``` My Blog Content ## Debug Component ### `` Valaxy has a built-in `` debug panel component that is **only available in development mode** (completely removed in production builds, zero overhead). This component displays a collapsible floating panel in the bottom-left corner of the page, providing the following debug information: - **Breakpoints**: Currently active responsive breakpoints (xs / sm / md / lg / xl / 2xl) - **Route**: Current route information (path, name, layout, query, params) - **Frontmatter**: Current page frontmatter data (JSON format) - **Config**: Site configuration summary and theme configuration #### Usage Use it directly in your theme or layout (no import needed, it's globally registered): ```vue ``` ::: tip `` is loaded asynchronously via `defineAsyncComponent` and guarded by `import.meta.env.DEV`, so it **has zero impact on production bundle size**. ::: ## Custom For more usage, please refer to [Custom Components](/guide/custom/components). ## Commands - **Categories**: guide Valaxy has a commandline tool. You can use `valaxy` or `vala` to execute the following commands. ```bash valaxy [args] Commands: valaxy [root] Start a local server for Valaxy [default] valaxy build [root] build your blog to static content valaxy rss [root] generate rss feed valaxy new Draft a new post valaxy debug Display debug information for your Valaxy project Positionals: root root folder of your source files [string] [default: "."] Options: -p, --port port [number] -o, --open open in browser [boolean] [default: false] --remote listen public host and enable remote control [boolean] [default: true] --log log level [string] [choices: "error", "warn", "info", "silent"] [default: "info"] -h, --help Show help [boolean] -v, --version Show version number [boolean] ``` ## Usage ### Local You can configure shortcut scripts in `package.json`. (**Suggested**) ```json { "scripts": { "build": "npm run build:ssg", "build:spa": "valaxy build", "build:ssg": "valaxy build --ssg", "dev": "valaxy dev", "new": "valaxy new", "rss": "valaxy rss" } } ``` For example, you can use `npm run dev` to run the project, use `npm run build` to build SSG site followed by building RSS source, and use `pnpm new post-title` to create a new post called `post-title` under the `posts` folder. ### Global You can also install Valaxy globally to use `valaxy` command globally. (**Optional**) ```bash pnpm add -g valaxy ``` ## Useful Commands - `valaxy .`: Start Valaxy. The default directory is current directory. (`.` is optional) - `valaxy rss`: Generate RSS - `valaxy build`: Use Vite to build SPA app by default - `valaxy build --ssg`: Build static pages (Memory-friendly, recommended), uses the built-in Valaxy SSG engine - `valaxy debug --plain`: Print environment and project information that can be pasted into an issue ## SSG Engine Valaxy uses a built-in SSG (Static Site Generation) engine (Vue SSR + pure string rendering, no JSDOM) to generate static pages with `valaxy build --ssg`. ::: tip The legacy JSDOM-based `vite-ssg` engine was **removed in v1.0** (it was broken under pnpm; see [#706](https://github.com/YunYouJun/valaxy/issues/706)). There is now a single engine — no `--ssg-engine` flag needed. ::: ### How It Works The Valaxy SSG engine runs in three phases: 1. **Client Build** — Vite builds client assets (with `ssrManifest` enabled) 2. **Server Build** — Builds the SSR entry (`entry-ssr.ts`), producing a render function executable in Node.js 3. **Render** — Loads the SSR entry, iterates over routes, calls Vue's `renderToString` for HTML, injects `<head>` tags / preload links / initial state via pure string replacement, and writes to disk Since it does not rely on JSDOM, per-page rendering has minimal memory overhead, enabling high concurrency (default 20) and fast, stable builds. Flash-of-unstyled-content is handled by the [FOUC guard](./config/extend) rather than Critical CSS inlining. ### Posts - `valaxy new <title>`: Create a post (.md) titled `title` under the directory `pages/posts`. For example, `valaxy new your-first-post` will create a file `your-first-post.md` under `pages/posts`, and update the date. > Do you think you have other more useful or better commands? That's great! Please report that by creating > an issue at [GitHub Issues](https://github.com/YunYouJun/valaxy/issues)! - [自定义文章模板](/guide/custom/templates) ### Addon Commands Enabled addons may provide commands below a package-derived namespace. For example, after configuring `valaxy-addon-moments` with `addonMoments()`: ```bash valaxy moments new [title] valaxy moments --help ``` Addon commands are resolved from the current project only when invoked, so the global `valaxy --help` output lists core commands only. An addon cannot override a core command or another enabled addon's command. Addon authors register subcommands with the experimental `extendCli` hook returned by `defineValaxyAddon`. Valaxy derives the root namespace from the package name and passes a CLI already scoped below it: ```ts export const addonMoments = defineValaxyAddon(() => ({ name: 'valaxy-addon-moments', extendCli(cli, { userRoot }) { cli.command('new [title]', 'Draft a new moment', () => {}, ({ title }) => { // Create the moment below userRoot. }) }, })) ``` CLI hooks require the recommended factory/object addon configuration, such as `addonMoments()`. String addon entries do not carry Node hooks. ## FAQ ### More logs when developing and less when building? - The default log level is `info` when developing (`valaxy`) and building (`valaxy build`). - Options: ['error', 'warn', 'info', 'silent'] You can use arguments to set the log level. For example, `valaxy build --log=info`. ### Miss `hexo deploy` from Hexo? When you create a Valaxy project, a `.github/workflows/gh-pages.yml` file is included. When you push to GitHub, it will automatically build and deploy to GitHub Pages. If you only want to deploy the `gh-pages` branch and really want to use `deploy`. You can also install `pnpm add -D gh-pages` and configure shortcut scripts in `package.json`. ```json { "scripts": { "deploy": "valaxy build && gh-pages -d dist" }, "devDependencies": { "gh-pages": "latest" } } ``` ## Valaxy <WorkInProgress /> <ValaxySponsors /> ## Extend Config - **Categories**: config ::: tip Extend Config is an advanced configuration provided by Valaxy, allowing you to customize more low-level and build-related settings. ::: Below are all the extend configuration options and related types. > [packages/valaxy/node/types/index.ts](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/node/types/index.ts) ::: details package/valaxy/node/types/index.ts ValaxyExtendConfig <<< @/../packages/valaxy/node/types/index.ts#snippet{ts:line-numbers} <<< @/../packages/valaxy/node/types/config.ts#snippet{ts:line-numbers} ::: So you can use it like this: ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' import { addonComponents } from 'valaxy-addon-components' import { VitePWA } from 'vite-plugin-pwa' const safelist = [ 'i-ri-home-line', ] export default defineValaxyConfig<ThemeConfig>({ // site config see site.config.ts or write in siteConfig siteConfig: {}, theme: 'yun', themeConfig: { banner: { enable: true, title: '云游君的小站', }, }, vite: { // https://vite-pwa-org.netlify.app/ plugins: [VitePWA()], }, unocss: { safelist, }, addons: [ addonComponents() ], }) ``` ### Build The `build` field configures the behavior of `valaxy build`. #### ssgForPagination When enabled, Valaxy generates static HTML for pagination pages (e.g., `/page/1`, `/page/2`). Default is `false`. #### foucGuard FOUC (Flash of Unstyled Content) guard. Inlines `body { opacity: 0 !important }` in `<head>` and uses JS to monitor all stylesheets until they finish loading, then removes the hidden style tag to reveal the page with a smooth fade-in. - `enabled` (default `true`): enable/disable the guard - `maxDuration` (default `5000`): max wait time (ms) before force-showing the page. Set to `0` to disable the timeout fallback ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ build: { ssgForPagination: false, foucGuard: { enabled: true, maxDuration: 5000, }, }, }) ``` ### @vitejs/plugin-vue Valaxy integrates [`@vitejs/plugin-vue`](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue) by default. You can configure it via the `vue` option. ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ vue: { template: { compilerOptions: { isCustomElement: tag => tag.startsWith('my-') } } } }) ``` ### Vite You can refer to the [Vite documentation](https://vite.dev/config/shared-options.html) to customize Vite-related configurations. ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ vite: { plugins: [] } }) ``` ### SSG Options Customize the built-in Valaxy SSG engine via `vite.ssgOptions`. Valaxy auto-generates the sitemap after the build; your callbacks run after it. Supported options: - `concurrency` — number of pages rendered in parallel (default `20`) - `includedRoutes(paths, routes)` — return the list of routes to render - `includeAllRoutes` — also render dynamic routes - `onBeforePageRender(route, html)` — transform the HTML template before a page renders - `onPageRendered(route, html)` — transform a page's HTML after it renders - `onFinished()` — runs after all pages are written (Valaxy's sitemap generation runs first) **SSG build minimum memory: ~4 GB.** Vite 8 (Rolldown) uses more memory during chunk generation; the engine auto-respawns with a sufficient heap. If you still hit `JavaScript heap out of memory`, raise the limit manually: ```bash NODE_OPTIONS=--max-old-space-size=4096 pnpm build --ssg ``` See [Dev FAQ - JavaScript heap out of memory](/dev/faq#javascript-heap-out-of-memory) for details. ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ vite: { ssgOptions: { // Number of pages rendered in parallel // concurrency: 20, // Customize which routes to generate // includedRoutes(paths, routes) { // return paths.filter(p => !p.includes(':')) // }, // Callback after the build finishes (Valaxy's sitemap runs first) async onFinished() { console.log('SSG build finished!') }, }, }, }) ``` ### Markdown You can customize Markdown-related configurations, such as code themes, block content, adding `markdown-it` plugins, transformers, etc. See the effect at: [Markdown](/guide/markdown). ::: details valaxy/node/plugins/markdown/types.ts <<< @/../packages/valaxy/node/plugins/markdown/types.ts ::: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ markdown: { // default material-theme-palenight // theme: 'material-theme-palenight', theme: { // light: 'material-theme-lighter', light: 'github-light', // dark: 'material-theme-darker', dark: 'github-dark', }, blocks: { tip: { icon: 'i-carbon-thumbs-up', text: 'ヒント', langs: { 'zh-CN': '提示', }, }, warning: { icon: 'i-carbon-warning-alt', text: '注意', }, danger: { icon: 'i-carbon-warning', text: '警告', }, info: { text: 'información', }, }, codeTransformers: [ // We use `[!!code` in demo to prevent transformation, here we revert it back. { postprocess(code) { return code.replace(/\[!!code/g, '[!code') }, }, ], config(md) { // md.use(xxx) } }, }) ``` ### DevTools Set `devtools: false` to disable DevTools. ### Addons See [Using Addons](/addons/use). ### UnoCSS See [UnoCSS](/guide/config/unocss-options). ### Modules #### RSS Valaxy has a built-in RSS module, which can be configured in `valaxy.config.ts` through the `modules.rss` configuration item. - `enable`: Whether to enable the RSS module. Default is `true`, enabled. - `fullText`: Whether to output the full text of the article. Default is `false`, only the summary is output. - `extractImagePathsFromHTML`: Whether to extract image paths from built HTML files (to resolve Vite hashed filenames). Default is `true`, enabled. ```ts [valaxy.config.ts] export default defineValaxyConfig({ modules: { rss: { enable: true, fullText: false, // 当设置为 true 时,会从构建后的 HTML 中提取图片的实际路径(包含 hash) // When set to true, it will extract actual image paths (with hash) from built HTML extractImagePathsFromHTML: true, }, }, }) ``` **About `extractImagePathsFromHTML`** When you reference images with relative paths in Markdown (e.g., `![pic](test.webp)`), Vite will bundle the image and generate a hashed filename (e.g., `/assets/test.zBFFFKJX.webp`). - When enabled (default): Image URLs in RSS feed will use the actual built paths, like `https://example.com/assets/test.zBFFFKJX.webp` - When disabled: Image URLs in RSS feed will be constructed based on the post directory, like `https://example.com/posts/article-name/test.webp` In most cases, you should keep this option as `true` to ensure RSS readers can load images correctly. #### LLMS Valaxy has a built-in LLMS module, following the [llms.txt standard](https://llmstxt.org/), to generate AI-readable Markdown content during build. When enabled, the build output will include: - `/llms.txt` — Page index grouped by directory, with links to individual `.md` files - `/llms-full.txt` — All page content concatenated (optional) - `/*.md` — Raw Markdown files for each page, accessible via URL Themes can use the `useCopyMarkdown()` composable to add a "Copy Markdown" button on post pages (built-in support in Yun theme). - `enable`: Whether to enable the LLMS module. Default is `false`, disabled. - `files`: Whether to generate individual `.md` files for each page. Default is `true`. - `fullText`: Whether to generate `llms-full.txt` (with all page content inlined). Default is `true`. - `prompt`: Custom prompt text, added to the `llms.txt` description section. Default is `''`. - `include`: Glob patterns for markdown files to include (relative to `pages/` directory). Default is `['posts/**/*.md']` to only include posts. Set to `['**/*.md']` to include all markdown files under `pages/`. You can also specify multiple directories, e.g. `['posts/**/*.md', 'guide/**/*.md']`. Pages in `llms.txt` are automatically grouped by their top-level directory (e.g. `## Posts`, `## Guide`, etc.). ```ts [site.config.ts] export default defineSiteConfig({ llms: { enable: true, files: true, fullText: true, prompt: '', // Default: only posts // include: ['posts/**/*.md'], // Include all markdown files under pages/ // include: ['**/*.md'], // Include specific directories // include: ['posts/**/*.md', 'guide/**/*.md'], }, }) ``` ### CDN Externals > Experimental With the `cdn.modules` option, you can specify certain npm packages to be loaded from CDN at runtime instead of being bundled. This can significantly reduce bundle size and leverage CDN for faster resource loading. This option only takes effect during `valaxy build`, not in dev mode. ::: tip Each module in `cdn.modules` requires the following fields: - `name`: npm package name (e.g., `'katex'`) - `global`: global variable name the library exposes on `window` (e.g., `'katex'`) - `url`: full CDN URL to the UMD/IIFE script - `css` (optional): full CDN URL to the stylesheet - `exports` (optional): named exports to re-export from the global variable (e.g., `['ref', 'computed']`) ::: #### Example: Load KaTeX from CDN KaTeX is bundled into the build output by default. If you want to load it from CDN to reduce bundle size, you can configure it as follows: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ cdn: { modules: [ { name: 'katex', global: 'katex', url: 'https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js', css: 'https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css', }, ], }, }) ``` You can also use other CDN providers by replacing the URL. For example, using unpkg: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ cdn: { modules: [ { name: 'katex', global: 'katex', url: 'https://unpkg.com/katex@0.16.21/dist/katex.min.js', css: 'https://unpkg.com/katex@0.16.21/dist/katex.min.css', }, ], }, }) ``` ## Config - **Categories**: config ## Configurations To simplify config, Valaxy divided the configuration into 3. `valaxy.config.ts` is the main entry of configuration. - `siteConfig`: Site **info** config. This affects info displayed on the site, and is independent of themes. - `themeConfig`: Theme config. This part is effective only when the specific theme is in use. - `runtimeConfig`: Runtime config (generated by Valaxy). You don't need to modify the config. - Other general Valaxy config (e.g., config that's needed for Node) For example: ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' import { addonComponents } from 'valaxy-addon-components' import { VitePWA } from 'vite-plugin-pwa' const safelist = [ 'i-ri-home-line', ] export default defineValaxyConfig<ThemeConfig>({ // site config see site.config.ts or write in siteConfig siteConfig: {}, theme: 'yun', themeConfig: { // Theme layout type: 'nimbo' (default, modern) or 'strato' (classic sidebar) // See: https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/docs type: 'nimbo', banner: { enable: true, title: '云游君的小站', }, }, vite: { // https://vite-pwa-org.netlify.app/ plugins: [VitePWA()], }, unocss: { safelist, }, addons: [ addonComponents() ], }) ``` ## Site Config > For more details, see [types/config.ts](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/types/config.ts). ::: details packages/valaxy/types/config.ts SiteConfig <<< @/../packages/valaxy/types/config.ts#snippet{ts:line-numbers} ::: Site **info** config. This affects info displayed on the site, and is independent of themes. You can also write it in `site.config.ts`. For example: ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ lang: 'zh-CN', title: 'Valaxy Theme Yun', url: 'https://valaxy.site/', author: { name: 'Yunyoujun', avatar: 'https://www.yunyoujun.cn/images/avatar.jpg', }, /** * Site favicon */ favicon: 'https://www.yunyoujun.cn/favicon.svg', /** * Subtitle */ subtitle: 'All at sea.', description: 'Valaxy Theme Yun Preview.', social: [ { name: 'RSS', link: '/atom.xml', icon: 'i-ri-rss-line', color: 'orange', } ], sponsor: { enable: true, methods: [ { name: 'Alipay', url: 'https://cdn.yunyoujun.cn/img/donate/alipay-qrcode.jpg', color: '#00A3EE', icon: 'i-ri-alipay-line', }, ], }, }) ``` ### Author Info More fields can be found in the type definitions above or via editor IntelliSense. ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ author: { name: 'Your Name', /** * Your avatar */ avatar: 'https://xxx', intro: 'A brief introduction' } }) ``` ### Timezone If you use CI/CD for building and deployment, the remote machine may be in a different timezone. You can set the timezone. This will format the time using the specified timezone by default and set the `process.env.TZ` variable. If you're hosting on other platforms, you may need to add environment variables on the corresponding platform. ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ timezone: 'Asia/Shanghai' }) ``` ### Post Sorting Set `siteConfig.orderBy` to control the sorting method of the article list. - `date`: Sort by the date of the article (default) - `updated`: Sort by the last update time of the article When `lastUpdated` is enabled, the last update time of the file will be automatically injected for articles that do not have `updated` set. ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ orderBy: 'updated', }) ``` ### Default Frontmatter Set the default Frontmatter for all posts. For example: > Set `time_warning: false` so that all articles won't show reading time warnings. ```ts {8-10} [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ /** * Default Frontmatter */ frontmatter: { time_warning: false, } }) ``` ### Social Icons ```ts export interface SocialLink { /** * The title of your link */ name: string link: string /** * Icon name * https://icones.js.org/ */ icon: string color: string } ``` Example: ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ social: [ { name: 'RSS', link: '/atom.xml', icon: 'i-ri-rss-line', color: 'orange', }, { name: 'QQ 群 1050458482', link: 'https://qm.qq.com/cgi-bin/qm/qr?k=kZJzggTTCf4SpvEQ8lXWoi5ZjhAx0ILZ&jump_from=webapi', icon: 'i-ri-qq-line', color: '#12B7F5', }, { name: 'GitHub', link: 'https://github.com/YunYouJun', icon: 'i-ri-github-line', color: '#6e5494', }, ] }) ``` ### Sponsor > At the end of each post, show sponsor information. ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ sponsor: { enable: true, methods: [ { name: 'Alipay', url: 'https://cdn.yunyoujun.cn/img/donate/alipay-qrcode.jpg', color: '#00A3EE', icon: 'i-ri-alipay-line', }, { name: 'WeChat Pay', url: 'https://cdn.yunyoujun.cn/img/donate/wechatpay-qrcode.jpg', color: '#2DC100', icon: 'i-ri-wechat-pay-line', }, ], }, }) ``` You can use the `sponsor` property to globally toggle if it's shown. ```ts interface SponsorOption { enable: boolean title: string methods: { name: string url: string color: string icon: string }[] } ``` Or you can set for each post using front matter: ```md --- title: xxx sponsor: false --- ``` ### Reading Statistics Enable reading statistics to display word count and reading time at the beginning of each post. > Requires theme support, i.e., displaying `wordCount` and `readingTime` fields from `frontmatter`. - `wordCount`: Word count - `readingTime`: Reading time (minutes) - You can set reading speed for different languages. Default: `cn` 300 words/min, `en` 200 words/min. ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ statistics: { enable: true, readTime: { /** * Reading speed */ speed: { cn: 300, en: 200, }, }, } }) ``` ### Code Height Limit You can set the height limit for each article. For example, if you set `codeHeightLimit: 300`, the height of all code blocks in the article will not exceed 300px and will be automatically collapsed. ```ts {5} import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ // ... codeHeightLimit: 300, }) ``` You can also set it separately in the Front Matter of the article: ```md {2} --- codeHeightLimit: 300 --- ``` Example can refer to [Code Height Limit](/examples/code-height-limit). ### Content Encryption Firstly, enable encryption in `site.config.ts`. ```ts {5-7} import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ // ... encrypt: { enable: true, } }) ``` - encrypt the entire article Set `password` in the Front Matter of the article: ```md {2} --- password: your_password password_hint: Custom Password Hint --- ``` - encrypt partial content ::: tip If you set `password` in Front Matter, partial encryption will be ignored. ::: Wrap content to be encrypted in `<!-- valaxy-encrypt-start:your_password --><!-- valaxy-encrypt-end -->`. Examples can be found in [Partial Content Encryption](/examples/partial-content-encryption)。 ### Client Redirects ```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 } ``` For example: ```ts [site.config.ts] export default defineSiteConfig({ redirects: { useVueRouter: true, rules: [ { from: ['/foo', '/bar'], to: '/about', }, { from: '/v1/about', to: '/about', }, ] }, }) ``` `/foo`, `/bar`, `/v1/about` these routes will be redirected to `/about`。 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 --- ``` `/redirect/old1`, `/redirect/old2`, `/v1/redirect` these routes will be redirected to `/posts/redirect`。 ::: tip When building SSG, if useVueRouter is false, an html file will be generated for each original route ::: ### Image Preview (Medium Zoom) Valaxy has built-in [medium-zoom](https://github.com/francoischalifour/medium-zoom) to preview the pictures, which is disabled by default. > [Medium Zoom Demo](https://medium-zoom.francoischalifour.com/) - mediumZoom - `enable`: Set to true to enable it - `selector`: Custom CSS selector - `options`: Refer to [options | medium-zoom](https://github.com/francoischalifour/medium-zoom#options) ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ mediumZoom: { enable: true } }) ``` In addition, you can also enable it in a certain article independently. ```md --- title: Test Medium Zoom medium_zoom: true --- ``` ### Lazyload Vanilla Lazyload Valaxy has built-in [vanilla-lazyload](https://github.com/verlok/vanilla-lazyload). `vanillaLazyload` is disabled by default. Because Valaxy itself will add `loading="lazy"` to all images, which is a browser feature, but if you want to get more extensive compatibility, you can manually enable it. ```ts export default defineSiteConfig({ vanillaLazyload: { // Disabled by default enable: true, } }) ``` ### More Configurations > For more details, see [types/config.ts](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/types/config.ts). ::: details packages/valaxy/types/config.ts SiteConfig <<< @/../packages/valaxy/types/config.ts#snippet{ts:line-numbers} ::: ## Theme Config Please refer to [Using Themes](/themes/use) and the theme you are using to configure it. > [Theme Yun Config](/themes/yun) ## Extended Config For more advanced configurations, see [Extended Config](/guide/config/extend). ## UnoCSS Options - **Categories**: config We have integrated [UnoCSS](https://unocss.dev) by default with the following presets. - [`presetWind4`](https://unocss.dev/presets/wind4): Commonly used styles generated on demand, styled like TailwindCSS v4. - [`presetAttributify`](https://unocss.dev/presets/attributify): Use attribute selectors instead of class names. - [`presetIcons`](https://unocss.dev/presets/icons): Integrated with the [icones](https://icones.netlify.app/) icon library for on-demand usage. - [`presetTypography`](https://unocss.dev/presets/typography): Typography-related style presets. Therefore, you can quickly achieve various effects directly in Markdown. See [UnoCSS | Markdown](/guide/markdown#unocss). ## unocss 您可以在主题 theme 目录的 `uno.config.ts` 或 `unocss.config.ts` 文件中编写 UnoCSS 配置。 ```ts [uno.config.ts] import { defineConfig } from 'unocss' export default defineConfig({ shortcuts: [ [ 'custom-uno-btn', 'px-4 py-1 rounded inline-block bg-teal-700 text-white cursor-pointer !outline-none hover:bg-teal-800 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50' ], ], safelist: ['bg-red-500'], }) ``` You can write UnoCSS configurations in the `uno.config.ts` or `unocss.config.ts` files within the theme directory. Alternatively, you can configure it in the `unocss` property of the `valaxy.config.ts` file. Below is an example of the `unocss` configuration in `valaxy.config.ts`: ```ts [valaxy.config.ts] import { presetIcons } from 'unocss' export default defineValaxyConfig<ThemeConfig>({ unocss: { shortcuts: [ { 'bg-base': 'bg-white dark:bg-black', 'color-base': 'text-black dark:text-white', 'border-base': 'border-[#8884]', }, ], rules: [ ['theme-text', { color: '#4b4b4b' }], ], }, }) ``` Directly configuring `presets` in the `unocss` option will override the default `presets` of the theme and Valaxy. To extend these presets, use [unocssPresets](#unocsspresets). ```ts [valaxy.config.ts] import { presetIcons } from 'unocss' export default defineValaxyConfig<ThemeConfig>({ unocss: { presets: [ presetIcons({ extraProperties: { 'display': 'inline-block', 'height': '1.2em', 'width': '1.2em', 'vertical-align': 'text-bottom', }, }), ], }, }) ``` ::: tip Configuring `presets` in the `uno.config.ts` or `unocss.config.ts` files will also override the default presets of Valaxy or the theme. To extend the presets, use [unocssPresets](#unocsspresets). ::: ## unocssPresets To extend the [UnoCSS presets](https://unocss.dev/guide/presets) in Valaxy, here is a basic example: ```ts [valaxy.config.ts] import { presetIcons } from 'unocss' export default defineValaxyConfig<ThemeConfig>({ unocssPresets: { icons: { extraProperties: { 'display': 'inline-block', 'height': '1.2em', 'width': '1.2em', 'vertical-align': 'text-bottom', }, }, }, }) ``` ::: danger <span lang="zh-CN"> 以下方式是错误的写法,注意 `unocssPresets` 和 `unocss` 配置项之间的区别 </span> <span lang="en"> The following method is incorrect. Note the difference between the `unocssPresets` and `unocss` configuration options: </span> ```ts [valaxy.config.ts] import { presetIcons } from 'unocss' export default defineValaxyConfig<ThemeConfig>({ unocssPresets: { // ❌ This won't work icons: presetIcons({ // [!code error] extraProperties: { 'display': 'inline-block', 'height': '1.2em', 'width': '1.2em', 'vertical-align': 'text-bottom', }, }), }, }) ``` ::: ## FAQ ### About UnoCSS Hot Reloading Failure > Currently, due to the inability to access UnoCSS's ctx, we have not yet found a good method to implement hot reload. [#48](https://github.com/YunYouJun/valaxy/issues/48) ## Custom Components - **Categories**: custom ## Automatic Component Registration Create `components` folder, write any Vue components. They will be registered automatically and you can even use them in your Markdown files. If there are components with the same name as the theme and Valaxy, the order of overriding is `user directory` -> `theme directory` -> `Valaxy client directory`. This also means that you can cover one component of the theme to achieve customizing the local theme! ### Disable Default Registration You can place components in folders **other than** `components` or in the `components/.exclude` folder. You can also customize the exclusion rules. ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ /** * @see https://github.com/unplugin/unplugin-vue-components#configuration * `/[\\/]node_modules[\\/]/, ` Don't exclude components under node_modules/valaxy/client/components */ components: { exclude: [/[\\/]\.git[\\/]/, /[\\/]\.exclude[\\/]/], }, }) ``` ### Custom Override Theme Component Based on this, you can easily customize the theme anywhere! For example, custom footer: > Refer to [demo/yun/components/YunFooter.vue | GitHub](https://github.com/YunYouJun/valaxy/blob/main/demo/yun/components/YunFooter.vue) In the `components` directory of the blog folder, create a new `YunFooter.vue` to overwrite the footer file of your theme. You can replace the footer directly: ```vue <template> <div>Footer content</div> </template> ``` You can also inherit and extend the previous footer: ```vue <script lang="ts" setup> import YunFooter from 'valaxy-theme-yun/components/YunFooter.vue' </script> <template> <YunFooter> Customize footer content </YunFooter> </template> ``` ### More Examples ### Insert Busuanzi Statistics > [Busuanzi Statistics](http://ibruce.info/2015/04/04/busuanzi/) Take valaxy-theme-yun as an example: > By default, operate in your blog folder. Create a new `YunFooter.vue` under the `components/` folder to customize the footer and display *Busuanzi Statistics*. You can customize its style freely according to your needs. ```vue [components/YunFooter.vue] <script lang="ts" setup> import { useScriptTag } from '@vueuse/core' import YunFooter from 'valaxy-theme-yun/components/YunFooter.vue' useScriptTag('//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js') </script> <template> <YunFooter> <!-- customize footer content --> <div>Total visits of this site <span id="busuanzi_value_site_pv" /></div> <div>Number of visitors <span id="busuanzi_value_site_uv" /></div> </YunFooter> </template> ``` Create a new `YunPostMeta.vue` under the `components/` folder to customize the information of each article and display the statistics of each article. ```vue [components/YunPostMeta.vue] <script lang="ts" setup> import type { Post } from 'valaxy' import { useLayout } from 'valaxy' import YunPostMeta from 'valaxy-theme-yun/components/YunPostMeta.vue' defineProps<{ frontmatter: Post }>() // Display only in Post layout const isPost = useLayout('post') </script> <template> <YunPostMeta :frontmatter="frontmatter"> <span v-if="isPost" id="busuanzi_container_page_pv"> Total reading of this article <span id="busuanzi_value_page_pv" /> </span> </YunPostMeta> </template> ``` The principle is to cover components. You can also freely cover any other components of the theme. ### Other [valaxy-addon-components](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-components) is also a plugin that makes full use of this mechanism. You can also refer to its implementation method and freely publish your custom components in the form of Valaxy plugin. > Because Vue components published is native, it will be completely **on-demand** when packaging, without your extra worry. [Example of using valaxy-addon-components to insert public components](https://yun.valaxy.site/examples/addons/components) ## Custom Extensions - **Categories**: custom Valaxy provides strong extensibility by "Convension over Configuration". If you have some development experience, you should be able to control every detail in the website. > The following content applies to either users or theme developers. ::: tip By default, the operations are done in the root directory of the site or the theme. If you want some reference, you can refer to [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-theme-yun). ::: ## Automatic Layout Registration Valaxy provides custom layouts based on [vite-plugin-vue-layouts-next](https://github.com/loicduong/vite-plugin-vue-layouts-next). Create a `layouts` file, and write Vue components as layouts. You can use it in your Markdown as follows. ```md [pages/album.md] --- title: Photos layout: album --- ``` Likewise, when there are layouts with the same name, the order to use is `user directory` -> `theme directory` -> `Valaxy directory`. ## Customizing index.html Create a new `index.html` file. You can globally insert anything in between `<head></head>` or `<body></body>` tags. For example: ```html [index.html] <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/star-markdown-css/dist/planet/planet-markdown.min.css" /> </head> ``` ## Extending Client Context Create a new file `setup/main.ts`: ```ts import { defineAppSetup } from 'valaxy' export default defineAppSetup((ctx) => { console.log(ctx) const { app, head, router, routes, isClient } = ctx // Use any Vue plugins app.use(/* */) }) ``` > For a detailed example, please see [Google Analytics | Third Party Integration](/guide/third-party/#谷歌统计)。 ## Overriding App Component You can create an `App.vue` file in your site root to completely override the default app component. Theme developers can also provide `App.vue` in their theme root. The resolution priority is: **User** > **Theme** > **Core**. ::: warning Overriding the app component will replace the default SEO setup (provided by `useValaxyApp()`) and the default `<router-view>`. You will need to handle these yourself. For most use cases, using `setup/main.ts` (see [Extending Client Context](#extending-client-context) above) is the recommended approach. Only use a full `App.vue` override when you need deep customization of the app shell. ::: ```vue [App.vue] <script setup lang="ts"> import { useValaxyApp } from 'valaxy' // Call useValaxyApp() to preserve default SEO behavior useValaxyApp() </script> <template> <router-view /> </template> ``` ## I18n Create `locales` folder. - `zh-CN.yml`: Chinese translation - `en.yml`: English translation For example (make sure that the file is not empty): ```yaml [locales/en.yml] button: about: About ``` ```yaml [locales/zh-CN.yml] button: about: 关于 ``` You can use it like this: ```vue [components/CustomButton.vue] <script setup> import { useI18n } from 'vue-i18n' const { t } = useI18n() </script> <template> <button> {{ t('button.about') }} </button> </template> ``` ## Template Files Create some templates for Markdown layout. (Work in progress) Create `scaffolds` folder. ```bash valaxy new <title> -l [layout] ``` - `layout`: Default is `post` Create a new file `xxx.md`, where `xxx` is your layout name. For example, `album.md` represents `layout: album`. ```bash valaxy new my-young -l album ``` ## Others - [Custom Styles | Valaxy](/guide/custom/styles) ## Hooks - **Categories**: custom ::: tip Valaxy provides a hooks system that allows you to customize various stages of the lifecycle. ::: ## Lifecycle > Hooks are executed in the order listed below. ### Build Time | Hook | Arguments | Description | | ---- | --------- | ----------- | | `options:resolved` | | Called after Valaxy config is resolved. | | `config:init` | | Called after Vite config is initialized (based on Valaxy Options). | | `vue-router:extendRoute` | `route: EditableTreeNode` | Called when extending each route (after `.md` frontmatter/excerpt is processed). | | `vue-router:beforeWriteFiles` | `root: EditableTreeNode` | Called before route files are written. | | `md:afterRender` | `ctx: MdAfterRenderContext` | Called after a Markdown page has been loaded and its frontmatter/excerpt resolved. `ctx.renderMarkdown` reuses the configured renderer and resolved Vite base for addon-generated HTML. | | `build:before` | | Called before the build starts. Only fires during `valaxy build`. | | `build:after` | | Called after the build completes. Only fires during `valaxy build`. | | `content:before-load` | | `@experimental` Called before all Content Loaders start fetching. | | `content:loaded` | | `@experimental` Called after all Content Loaders have finished. | ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ hooks: { 'config:init': () => { console.log('config:init') }, } }) ``` ### App Client {#app-client} Valaxy does not currently have a client-side hooks system. Client-side extensions are done via `defineAppSetup`, which provides an `AppContext` (including `app`, `router`, `routes`, etc.) for customizing the Vue application. ```ts [setup/main.ts] import { defineAppSetup } from 'valaxy' export default defineAppSetup(({ app, router, routes }) => { // install Vue plugins, register global components, etc. }) ``` ## Custom Styles - **Categories**: custom ## Automatic Style Injection ::: warning - `index.ts` / `index.scss` / `index.css` 不应当同时存在,否则可能会导致重复引入。 - 仅首次新建 styles/index.scss 文件时,需要重启开发服务器,以确保 scss 被加载。 ::: :::zh-CN 新建 `styles` 文件夹,目录下的以下文件将会被自动引入: - `index.ts` - `index.scss` - `index.css` - `css-vars.scss` (推荐在 `index.ts` 中自己引入 `xxx.scss`,后续可能会被弃用) 我们推荐您: - 新建 `index.ts` 文件,并在其中自由引入其他样式文件 `xxx.scss`。 ::: :::en Create `styles` folder, and the following files under the directory will be automatically imported: - `index.ts` - `index.scss` - `index.css` We recommend you: - Create `index.ts` file, and import other style files `xxx.scss` freely. - `index.ts` / `index.scss` / `index.css` should not exist at the same time, otherwise it may cause duplicate imports. ::: ## Custom Font :::zh-CN 譬如你可以在 `styles/index.ts` 中覆盖默认的字体。 - `serif`: 衬线字体:<span font="serif">字体 abcd 123</span> - `sans`: 非衬线字体:<span font="sans">字体 abcd 123</span> - `mono`: 等宽字体:<span font="mono">字体 abcd 123</span> ::: :::en For example, you can override the default font in 'styles/index.ts'. - `serif`: serif font: <span font="serif">Font abcd 123</span> - `sans`: sans-serif font: <span font="sans">Font abcd 123</span> - `mono`: monospaced font: <span font="mono">Font abcd 123</span> ::: ```ts [styles/index.ts] import './vars.scss' ``` ```scss [styles/vars.scss] :root { --va-font-serif: 'Noto Serif SC', STZhongsong, STKaiti, KaiTi, Roboto, serif; --va-font-sans: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; --va-font-mono: Menlo, Monaco, Consolas, "Courier New", monospace; } ``` ## 示例 ### Custom Cursor 替换鼠标光标样式。 例如使用 [Material Design Cursors](https://www.deviantart.com/rosea92/art/Material-Design-Cursors-Dark-756850032)。 - `default`: 默认状态下图标。 - `pointer`: 指针(即链接状态下)图标。 - `text`: 文本选择图标。 新建 `styles/index.ts` 文件,引入 `vars.scss`: ```ts [styles/index.ts] import './vars.scss' ``` 新建 `styles/vars.scss` 文件: ```scss [styles/vars.scss] :root { --cursor-default: url('https://cdn.yunyoujun.cn/css/md-cursors/pointer.cur'); --cursor-pointer: url('https://cdn.yunyoujun.cn/css/md-cursors/link.cur'); --cursor-text: url('https://cdn.yunyoujun.cn/css/md-cursors/text.cur'); } body { cursor: var(--cursor-default), auto; } a { cursor: var(--cursor-pointer), auto; &:hover { cursor: var(--cursor-pointer), auto; } } button { cursor: var(--cursor-pointer), pointer; } input { cursor: var(--cursor-text), text; } ``` 需使用 `html.dark` 选择器包裹样式。 ```ts [styles/index.ts] import './vars.scss' ``` ```scss [styles/vars.scss] // 亮色 .yun-page-header-gradient { background: linear-gradient(to right, blue 0, rgba(0, 0, 0, 0.2) 100%); } // 覆盖 Dark Mode html.dark{ --va-c-bg-light:rgba(5, 16, 29, 0.8); .yun-page-header-gradient { background: linear-gradient(to right, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 100%); } .yun-footer-gradient { background: linear-gradient(to right, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 100%); } } ``` ## Custom Post Templates - **Categories**: custom Valaxy uses [ejs](https://ejs.co/) as its template generating helper, you can define your own templates as below: ## Create a scaffold folder in your project root ```shell $ mkdir scaffolds ``` ## Create your own template to the scaffolds folder > **Note** > The filename you are going to create is going to be the same with the layout name you need when you creating file with command: > `valaxy new --layout [layout] [filename]` ```shell $ touch scaffolds/post.md $ cat <<EOF > scaffolds/post.md --- layout: <%=layout%> title: <%=title%> date: <%=date%> --- Some additional descriptions EOF ``` ## CMS Integration - **Categories**: third ::: warning Experimental Content Loader is an experimental feature (`@experimental`). The API may change in future releases. ::: ## Introduction Valaxy supports fetching content from external CMS platforms via **Content Loaders**. Loaders run before Vite starts, writing remote content as `.md` files that integrate automatically into the routing and markdown pipeline. This means: - CMS content shares the same features as local `.md` files (routing, search, RSS, etc.) - No theme or layout modifications needed - Incremental caching ensures only changed content is rewritten ## How It Works 1. Content Loaders run **before** the Vite dev server or build starts 2. Each loader fetches content from an external CMS, returning `ContentItem[]` 3. Items are written as `.md` files to `.valaxy/content/pages/` 4. These files are automatically picked up by vue-router's file-based routing 5. Existing markdown processing, search indexing, and RSS generation work unchanged ## Defining a Content Loader Use `defineContentLoader()` to create a Content Loader: ```ts [loaders/my-cms.ts] import { defineContentLoader } from 'valaxy' export default defineContentLoader({ name: 'my-cms', async load(ctx) { // Fetch content from your CMS API const response = await fetch('https://api.my-cms.com/posts') const posts = await response.json() return posts.map(post => ({ path: `posts/${post.slug}.md`, content: [ '---', `title: ${post.title}`, `date: ${post.publishedAt}`, '---', '', post.body, ].join('\n'), })) }, // Optional: poll every 30s in dev mode devPollInterval: 30000, }) ``` ## Configuration Register Content Loaders in `valaxy.config.ts`: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import myCmsLoader from './loaders/my-cms' export default defineValaxyConfig({ loaders: [myCmsLoader], }) ``` ### Using with Addons Some Valaxy addons provide Content Loaders automatically. When using such addons, you don't need to configure `loaders` manually — the addon's `setup()` function injects the loader for you: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { addonFeishu } from 'valaxy-addon-feishu' export default defineValaxyConfig({ addons: [ addonFeishu({ appId: process.env.FEISHU_APP_ID, appSecret: process.env.FEISHU_APP_SECRET, spaceId: 'your-wiki-space-id', }), ], }) ``` ## API Reference ### ContentItem Represents a single piece of content fetched from an external source. ### ContentLoaderContext The context object passed to every loader's `load()` function. ### ContentLoader The full loader definition interface. ```ts interface ContentItem { /** Route path relative to pages/, e.g. 'posts/my-post.md'. Must end with .md */ path: string /** Full markdown content including YAML frontmatter block */ content: string /** Optional digest for incremental caching (skip write if unchanged) */ digest?: string } interface ContentLoaderContext { node: ValaxyNode /** .valaxy/content/ */ cacheDir: string mode: 'dev' | 'build' } interface ContentLoader { name: string load: (ctx: ContentLoaderContext) => Promise<ContentItem[]> | ContentItem[] /** Polling interval (ms) for dev mode. undefined = no polling */ devPollInterval?: number /** Per-item transform before writing to cache */ transform?: (item: ContentItem) => ContentItem | Promise<ContentItem> } ``` ## Dev Mode Polling Set `devPollInterval` (in milliseconds) to have a loader periodically re-fetch content during development. This is useful for near-real-time preview while editing CMS content. ```ts defineContentLoader({ name: 'my-cms', load: async (ctx) => { /* ... */ }, devPollInterval: 60000, // Re-fetch every 60 seconds }) ``` ::: tip Polling only runs in dev mode. In build mode, content is fetched once. ::: ## Incremental Caching Content Loaders use digest-based incremental caching: - Each item's MD5 digest is recorded in a manifest file - On subsequent loads, unchanged items are skipped - Stale files (present in previous manifest but not current output) are automatically removed - You can provide a custom `digest` on `ContentItem` (e.g. a CMS revision ID) ## Transform Use `transform` to modify each content item before it is written to disk: ```ts defineContentLoader({ name: 'my-cms', async load(ctx) { /* ... */ }, transform(item) { // Add a footer to every post return { ...item, content: `${item.content}\n\n---\n\nFetched from My CMS`, } }, }) ``` ## Hooks Content Loaders provide two lifecycle hooks: | Hook | Description | | --- | --- | | `content:before-load` | Fired before all content loaders start fetching | | `content:loaded` | Fired after all content loaders have finished | ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ hooks: { 'content:before-load': () => { console.log('Content loading started...') }, 'content:loaded': () => { console.log('Content loading finished!') }, }, }) ``` ## Integration Addons The following Valaxy addons integrate specific CMS platforms using Content Loaders: - [valaxy-addon-feishu](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-feishu) — Fetch content from Feishu/Lark documents (`@experimental`) ## References - [VitePress CMS Guide](https://vitepress.dev/guide/cms) — Similar feature in VitePress - [GitHub Issue #294](https://github.com/YunYouJun/valaxy/issues/294) — Content Loader design discussion ## Third Comment System - **Categories**: third There are many third-party comment systems. Below is a brief introduction to how to integrate various comment systems. > [My Views on Third-Party Comment Systems](https://www.yunyoujun.cn/posts/third-party-comment-system) ## Waline > [Waline](https://waline.js.org/) is a server-side comment system that can be hosted on platforms like Vercel. Integrate using [valaxy-addon-waline](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-addon-waline/README.md). > valaxy-addon-waline is a Valaxy plugin based on Waline. > Additionally, we recommend using [kotodama](https://github.com/YunYouJun/kotodama) for comment management, which is a comment management system based on Waline's server-side implementation. ### Installation ```bash npm i valaxy-addon-waline # pnpm add valaxy-addon-waline ``` ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { addonWaline } from 'valaxy-addon-waline' export default defineValaxyConfig({ // or write it in site.config.ts siteConfig: { // Enable comments comment: { enable: true }, }, // Set valaxy-addon-waline configuration addons: [ addonWaline({ // Waline configuration, see https://waline.js.org/reference/client/props.html serverURL: 'https://your-waline-url', }), ], }) ``` ## Utterances > [Utterances](https://utteranc.es/) is a comment system based on GitHub Issues. It can be integrated directly by mounting a JS script. Create `App.vue` in the blog root directory and add the mounting script: <<< @/../demo/yun/App.vue <<< @/../demo/yun/composables/use-utterances.ts ## Third Party Integration - **Categories**: third ## Search ### Local Search (MiniSearch) Valaxy has built-in local search based on [MiniSearch](https://lucaong.github.io/minisearch/). It is the recommended local search provider for documentation sites and works without an external service. The search index is generated by Valaxy during dev/build. ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ search: { enable: true, provider: 'local', }, }) ``` Set `search: false` in page frontmatter to exclude a page from the local search index. ### Local Search (Based on fuse.js) Valaxy also supports local search based on [fuse.js](https://fusejs.io/). Use it when you need Fuse-specific options or already rely on a generated `valaxy-fuse-list.json` file. > `valaxy fuse` generates `valaxy-fuse-list.json` in the `public` directory by default. > When executing `valaxy build`, `valaxy fuse` will be executed automatically when `search.provider` is `fuse`. ```ts [site.config.ts] {7} import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ search: { enable: true, provider: 'fuse', }, }) ``` You can customize the Fuse index and matching behavior: ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ search: { enable: true, provider: 'fuse', }, fuse: { // pattern: 'pages/**/*.md', options: { keys: ['title', 'tags', 'categories', 'excerpt', 'content'], // Lower values make matching stricter. threshold: 0.4, // Useful when searching full document content. ignoreLocation: true, }, }, }) ``` You can also add an explicit fuse generation script in your `package.json`: ```json {7,9} [package.json] { "name": "yun-demo", "valaxy": { "theme": "yun" }, "scripts": { "build": "npm run build:ssg", "build:ssg": "valaxy build --ssg", "fuse": "valaxy fuse", "rss": "valaxy rss" }, "dependencies": { "valaxy": "latest", "valaxy-theme-yun": "latest" } } ``` ### Algolia DocSearch Algolia is an online third-party search service. You need to apply for the `ID` and `Secret` by yourself. > [DocSearch](https://docsearch.algolia.com/) Only technical document applications are accepted generally. Valaxy provides a quick integration plug-in: [valaxy-addon-algolia](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-algolia) (Currently only DocSearch is supported). ## Music Player > Provided by the [valaxy-addon-meting](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-meting) addon, based on [APlayer](https://github.com/DIYgod/APlayer) and [MetingJS](https://github.com/metowolf/MetingJS). ::: warning Migrated to an addon The legacy core `aplayer: true` frontmatter switch was **removed in v1.0**. The music player now lives in the `valaxy-addon-meting` addon — add it to your config to use it. ::: Install and enable the addon: ```ts // valaxy.config.ts import { defineConfig } from 'valaxy' import { addonMeting } from 'valaxy-addon-meting' export default defineConfig({ addons: [ addonMeting({ // set `global: true` for a fixed player shown on every page global: false, }), ], }) ``` Then drop a `<meting-js>` element anywhere in an article (e.g. a song from NetEase Cloud Music): ```html <meting-js id="22736708" server="netease" type="song" theme="#C20C0C"> </meting-js> ``` > Tip: the `aplayer: true` frontmatter is still honored by the addon to toggle the global fixed player on a per-page basis. See the [addon README](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-meting) for all options. Here is a demo: <meting-js id="22736708" server="netease" type="song" theme="#C20C0C"> </meting-js> > More info see [Option | MetingJS](https://github.com/metowolf/MetingJS#option) ## Google Statistics > Refer to [Custom Extensions | Extending Client Context](/guide/custom/extend#extending-client-context) You can add Google Statistics by using Vue plug-in directly. For example: - Install the dependency: `pnpm add vue-gtag-next` - Create `setup/main.ts` ```ts // setup/main.ts import { defineAppSetup } from 'valaxy' import { install as installGtag } from './gtag' export default defineAppSetup((ctx) => { installGtag(ctx) }) ``` - Create `setup/gtag.ts` ```ts import type { UserModule } from 'valaxy' import VueGtag, { trackRouter } from 'vue-gtag-next' export const install: UserModule = ({ isClient, app, router }) => { if (isClient) { app.use(VueGtag, { property: { id: 'G-1LL0D86CY9' }, }) trackRouter(router) } } ``` More info see [vue-gtag-next](https://github.com/MatteoGabriele/vue-gtag-next). ## Schema.org And OPG for SEO - **Categories**: third ::: tip [OpenGraph or Scheme.org](https://stackoverflow.com/questions/6402528/opengraph-or-schema-org) - [The Open Graph protocol](https://ogp.me/) - [Schema.org](https://schema.org/) ::: By adopting the [Schema.org](https://schema.org/) standard, search engines can better understand your website content, thereby improving your website's ranking in search results. Implemented based on [@unhead/schema-org](https://unhead.unjs.io/docs/typescript/schema-org/guides/get-started/overview). - Identity uses Person (Personal Website or Blog) > [Person | @unhead/schema.org](https://unhead.unjs.io/docs/typescript/schema-org/guides/recipes/identity#person) ## Validators - [Google Rich Results Test](https://search.google.com/test/rich-results) - [Schema Markup Validator](https://validator.schema.org/) ## Use Vite/Vue Plugin - **Categories**: third Valaxy is compatible with Vite/Vue plugins. You can refer to the following examples for usage. ## Using Vite Plugins ### Using vite-plugin-pwa ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' import { VitePWA } from 'vite-plugin-pwa' export default defineValaxyConfig<ThemeConfig>({ vite: { plugins: [ // https://vite-pwa-org.netlify.app/ VitePWA(), ], }, }) ``` ```ts [setup/main.ts] import { defineAppSetup } from 'valaxy' export default defineAppSetup(({ router, isClient }) => { router.isReady().then(async () => { if (!isClient) return const { registerSW } = await import('virtual:pwa-register') registerSW({ immediate: true }) }) }) ``` For more configuration options, please refer to [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa). ## Using Vue Plugins ::: tip Valaxy integrates [`@vitejs/plugin-vue`](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue) by default. If you need to customize the `@vitejs/plugin-vue` configuration, you can use the `vue` config option. See [Extend Config](/guide/config/extend.md#vitejs-plugin-vue) for details. ::: For example, to use Element Plus, you can add the following configuration in `setup/main.ts`: ```ts [setup/main.ts] import ElementPlus from 'element-plus' import { defineAppSetup } from 'valaxy' import 'element-plus/lib/theme-chalk/index.css' export default defineAppSetup(({ app }) => { app.use(ElementPlus) }) ``` ## Theme Yun Config - **Categories**: theme ## Theme Type {#type} Yun theme supports two layout types via `themeConfig.type`: - `nimbo` (default): Modern layout with top navigation bar + homepage banner animation + full-screen menu on mobile. - `strato`: Classic layout with left sidebar + top navigation bar, similar to traditional blogs. ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { type: 'nimbo', // or 'strato' }, }) ``` ::: tip `strato` corresponds to the v1 layout style, `nimbo` to v2. Future layout variants will be named after different cloud types (e.g., cirro, cumulo, alto). ::: ## Colors {#colors} ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { colors: { /** * Primary color * @default '#0078E7' */ primary: '#0078E7', }, }, }) ``` ## Navigation {#nav} Top navigation bar items. ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { nav: [ { text: 'Posts', link: '/posts/', icon: 'i-ri-article-line' }, { text: 'Links', link: '/links/', icon: 'i-ri-link' }, ], }, }) ``` Each `NavItem` has the following properties: | Property | Type | Description | | --- | --- | --- | | `text` | `string` | Display text (supports i18n key like `menu.posts`) | | `link` | `string` | URL | | `icon` | `string` | Icon name, see [Icônes](https://icones.js.org/) | | `active` | `string` | Active route match pattern | ## Pages {#pages} Page links displayed below the social links on the homepage sidebar. ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { pages: [ { name: 'Links', url: '/links/', icon: 'i-ri-link', color: 'dodgerblue', }, { name: 'Projects', url: '/projects', icon: 'i-ri-gallery-view', color: 'var(--va-c-text)', }, ], }, }) ``` | Property | Type | Description | | --- | --- | --- | | `name` | `string` | Page name | | `url` | `string` | Page URL | | `icon` | `string` | Icon name, see [Icônes](https://icones.js.org/) | | `color` | `string` | Icon color (CSS value), default `var(--va-c-text)` | ## Sidebar {#sidebar} The `docs` layout renders this navigation on the left. You can provide one sidebar for every document or use path prefixes to define multiple sidebars. ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { sidebar: { '/guide/': { base: '/guide/', items: [ { text: 'Guide', items: [ { text: 'Getting Started', link: 'getting-started' }, { text: 'Configuration', link: 'config' }, ], }, { text: 'Advanced', collapsed: true, items: [ { text: 'Deployment', link: 'deployment' }, ], }, ], }, }, }, }) ``` Set `layout: docs` in a page's frontmatter to use it. Groups become collapsible when `collapsed` is present; `true` starts collapsed and `false` starts open. ## Footer {#footer} ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { footer: { since: 2022, cloud: { enable: true, // Flowing cloud on top of footer }, icon: { enable: true, name: 'i-ri-heart-fill', animated: true, color: 'red', url: '', title: '', }, powered: true, // Show "Powered by Valaxy & valaxy-theme-yun" beian: { enable: false, icp: '', // e.g. '苏ICP备xxxxxxxx号' icpLink: 'https://beian.miit.gov.cn/', police: '', // Public security registration number }, }, }, }) ``` ## Theme Yun Customization - **Categories**: theme ## Edit Link {#edit-link} Add an "Edit this page" link to posts. ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { editLink: { pattern: 'https://github.com/user/repo/edit/main/:path', text: 'Edit this page on GitHub', }, }, }) ``` ## Outline Title {#outline-title} Table of contents heading text. ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { /** * @default 'On this page' */ outlineTitle: 'On this page', }, }) ``` ## Styles {#styles} Override theme styles by creating `styles/index.ts`: ```ts [styles/index.ts] import './vars.scss' ``` ```scss [styles/vars.scss] :root { --yun-bg-img: url("https://example.com/bg.jpg"); --yun-sidebar-bg-img: url("https://example.com/sidebar.jpg"); --yun-c-cloud: pink; } ``` ## Theme Yun Layout And Visuals - **Categories**: theme ## Documentation Layout {#documentation-layout} Use the `docs` layout for guide-like pages. It removes blog-only navigation and comments, renders the configured documentation sidebar, and keeps the page outline on the right. ```md --- title: Getting Started layout: docs --- ``` Configure its navigation with [`themeConfig.sidebar`](/themes/yun/config#sidebar). ## Banner {#banner} The homepage banner with staggered text animation. ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { banner: { enable: true, title: 'My Blog', // Split title manually // title: ['Hello', 'World'], // i18n support // title: { en: ['Hello', 'World'], 'zh-CN': '你好世界' }, cloud: { enable: true, // Flowing cloud animation below banner }, // Custom CSS class for site name siteNameClass: '', // Animation duration (nimbo only) duration: 500, }, }, }) ``` To change the cloud color, override the CSS variable `--yun-c-cloud`: ```css :root { --yun-c-cloud: red; } ``` ## Background Image {#bg-image} ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { bg_image: { enable: true, url: '/images/bg.jpg', dark: '/images/bg-dark.jpg', // Dark mode opacity: 1, }, }, }) ``` You can also override via CSS variables: ```css :root { --yun-bg-img: url("/images/bg.jpg"); --yun-sidebar-bg-img: url("/images/sidebar-bg.jpg"); } ``` ## Theme Yun Widgets And Pages - **Categories**: theme ## Notice {#notice} Display an announcement banner. ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { notice: { enable: true, hideInPages: false, // Whether to hide in /pages/[page] content: 'Welcome to my blog!', }, }, }) ``` ## Say {#say} Random quote/sentence display. ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { say: { enable: true, api: '', // Custom API URL or local JSON path in public/ hitokoto: { enable: true, api: 'https://v1.hitokoto.cn', }, }, }, }) ``` ## Fireworks {#fireworks} Click fireworks effect powered by [@explosions/fireworks](https://www.npmjs.com/package/@explosions/fireworks). ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { fireworks: { enable: true, colors: ['#66A7DD', '#3E83E1', '#214EC2'], }, }, }) ``` ## Post Card Types {#types} Custom post type badges with icon and color. ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { types: { link: { color: '#1890ff', icon: 'i-ri-link', }, bilibili: { color: '#FF8EB3', icon: 'i-ri-bilibili-line', }, }, }, }) ``` Then set `type` in post frontmatter: ```md --- title: My Video type: bilibili url: https://www.bilibili.com/video/xxx --- ``` ## Menu {#menu} Custom navigation icon on the far right. ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { menu: { custom: { title: 'Menu', url: '/', icon: 'i-ri-menu-line', }, }, }, }) ``` ## Friend Links Page {#links} Create `pages/links/index.md`: ```md --- title: My Friends links: - url: https://www.yunyoujun.cn avatar: https://www.yunyoujun.cn/images/avatar.jpg name: YunYouJun blog: YunYouJun's Blog desc: Hope to be an interesting person. color: "#0078e7" # Or use a JSON URL # links: https://friends.example.com/links.json random: true --- <YunLinks :links="frontmatter.links" :random="frontmatter.random" /> ``` ## Theme Press Config - **Categories**: theme ## Theme Config Reference {#theme-config-reference} The table below lists the Press-specific `themeConfig` options. Site-wide options such as `title`, `url`, `search`, and `lastUpdated` still live under `siteConfig`. | Option | Type | Default | Description | | --- | --- | --- | --- | | `logo` | `string` | `''` | Logo shown in the top nav. Use a public path such as `/favicon.svg`. | | `colors.primary` | `string` | `'#0078E7'` | Primary color injected into Press SCSS and Valaxy theme variables. | | `nav` | `NavItem[]` | `[]` | Top navigation links and dropdown groups. | | `sidebar` | `Sidebar` | `[]` | Left sidebar. Supports category names, explicit trees, and multi-sidebars keyed by path. | | `editLink.pattern` | `string` | Valaxy docs repository edit URL | URL template for the bottom "Edit this page" link. `:path` is replaced by the page relative path. | | `editLink.text` | `string` | Locale text | Custom edit-link label. | | `footer.message` | `string` | `undefined` | Footer message. HTML is allowed. | | `footer.copyright` | `string` | `undefined` | Footer copyright text. HTML is allowed. | | `socialLinks` | `SocialLink[]` | `[]` | Icon links rendered in the nav. Icons use UnoCSS icon classes such as `i-ri-github-line`. | | `locales` | `Record<string, LocaleSpecificConfig>` | `undefined` | Locale switcher data and per-locale `themeConfig` overrides. | | `i18nRouting` | `boolean` | `false` | Preserve the current route path when switching locales. | ## Home Page {#home-page} Use `layout: home` and configure the hero and features in frontmatter. ```md [pages/index.md] --- layout: home title: Acme Docs hero: name: Acme text: Build faster with Acme tagline: Everything you need to install, configure, and extend Acme. image: src: /logo.png alt: Acme Logo actions: - theme: brand text: Get Started link: /guide/getting-started type: fly - theme: alt text: View on GitHub link: https://github.com/acme/project features: - icon: i-logos:vitejs title: Fast details: Powered by Vite and Valaxy. - icon: i-logos:vue title: Extensible details: Use Vue components directly in Markdown. --- ``` `hero.image` follows VitePress's `ThemeableImage` format. It accepts a string, a `{ src, alt }` object, or a `{ light, dark, alt }` object. Press does not inject a default Valaxy logo: when `hero.image` is omitted, the home page has no hero image. Root-absolute image paths are adjusted using the configured Vite `base`. The `fly` action uses `siteConfig.favicon` for its hover icon instead of a theme-hardcoded asset. Internal action links are adjusted automatically when `i18nRouting` is enabled. ## Footer And Edit Link {#footer-edit-link} The edit link appears at the bottom of article pages. `:path` is replaced with the current page relative path. ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ siteConfig: { lastUpdated: true, }, themeConfig: { editLink: { pattern: 'https://github.com/acme/project/edit/main/docs/:path', text: 'Edit this page', }, footer: { message: 'Released under the MIT License.', copyright: 'Copyright (c) 2026 Acme.', }, }, }) ``` Set `nav: false` in page frontmatter to hide previous/next page navigation for that page. ## Page Layouts {#page-layouts} Press provides these common layouts: | Layout | Use case | | --- | --- | | `default` | Standard documentation page | | `home` | Landing page with hero and features | | `posts` | Post list page | | `post` | Blog post detail page | | `tags` | Tag archive page | | `404` | Not found page | Example archive pages: ```md [pages/posts/index.md] --- title: Posts layout: posts --- ``` ```md [pages/tags/index.md] --- title: Tags layout: tags --- ``` ## Styling {#styling} Set the theme primary color through `themeConfig.colors.primary`: ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { colors: { primary: '#0078E7', }, }, }) ``` You can also override Press CSS variables in your own styles: ```scss [styles/index.scss] :root { --pr-nav-height-mobile: 56px; --pr-nav-text: var(--va-c-text-1); } ``` ## Component Customization {#component-customization} Like other Valaxy themes, Press components can be overridden by creating a component with the same name in your site. Common extension points include: | Component | Purpose | | --- | --- | | `PressHomeHero.vue` | Home hero | | `PressHomeFeatures.vue` | Home feature grid | | `PressNavBar.vue` | Top navigation bar | | `PressSidebar.vue` | Left sidebar | | `PressDocFooter.vue` | Edit link and previous/next footer | | `PressArticle.vue` | Documentation article wrapper | For lower-level theme authoring details, see [Write A Theme](/themes/write). ## Migrating From VitePress To Theme Press - **Categories**: theme Press intentionally feels familiar to VitePress users, but it is not a drop-in `.vitepress/config.ts` replacement. - Move site and theme config into `valaxy.config.ts`, `site.config.ts`, or `theme.config.ts`. - Put content in Valaxy's `pages/` directory. - Use Valaxy frontmatter such as `categories`, `layout`, and `search`. - Configure search through `siteConfig.search.provider`. - Use Valaxy addons when you need integrations such as Algolia, Git log contributors, comments, or music. ## Common Mappings {#common-mappings} | VitePress | Valaxy + Press | | --- | --- | | `.vitepress/config.ts` | `valaxy.config.ts`, `site.config.ts`, or `theme.config.ts` | | `title`, `description` | `siteConfig.title`, `siteConfig.description` | | `themeConfig.logo` | `themeConfig.logo` | | `themeConfig.nav` | `themeConfig.nav` | | `themeConfig.sidebar` | `themeConfig.sidebar` | | `themeConfig.socialLinks` | `themeConfig.socialLinks` | | `themeConfig.editLink` | `themeConfig.editLink` | | `themeConfig.footer` | `themeConfig.footer` | | `lastUpdated` | `siteConfig.lastUpdated` | | `locales` | `themeConfig.locales` plus `siteConfig.languages` | | `themeConfig.search.provider: 'local'` | `siteConfig.search.provider: 'local'` | | `.vitepress/theme` custom layout/components | Same-name component overrides in the Valaxy site | | VitePress plugins | Valaxy addons or Vite plugins in `valaxy.config.ts` | ## Sidebar Notes {#sidebar-notes} Press supports VitePress-style sidebar arrays, path-keyed multi-sidebars, and `{ base, items }` objects: ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { sidebar: { '/guide/': { base: '/guide/', items: [ { text: 'Guide', items: [ { text: 'Introduction', link: '' }, { text: 'Getting Started', link: 'getting-started' }, ], }, ], }, }, }, }) ``` The main Valaxy-specific addition is that a top-level string such as `'guide'` still expands from page `categories`. ## Theme Press Search And i18n - **Categories**: theme ## Search {#search} Press renders the search box when `siteConfig.search.enable` is `true`. For most documentation sites, use MiniSearch local search: ```ts [valaxy.config.ts] export default defineValaxyConfig({ siteConfig: { search: { enable: true, provider: 'local', }, }, }) ``` Press also supports Valaxy's Fuse provider: ```ts export default defineValaxyConfig({ siteConfig: { search: { enable: true, provider: 'fuse', }, }, }) ``` For Algolia DocSearch, set the provider to `algolia` and install the Algolia addon: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { addonAlgolia } from 'valaxy-addon-algolia' export default defineValaxyConfig({ siteConfig: { search: { enable: true, provider: 'algolia', }, }, addons: [ addonAlgolia({ appId: 'YOUR_APP_ID', apiKey: 'YOUR_SEARCH_API_KEY', indexName: 'YOUR_INDEX_NAME', }), ], }) ``` Set `search: false` in page frontmatter to exclude one page from local search indexing. ## Multi-Language Sites {#i18n} Use `locales` to configure the language switcher and per-locale theme overrides. Enable `i18nRouting` when switching languages should keep the current route path. ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ siteConfig: { languages: ['en', 'zh-CN'], }, themeConfig: { i18nRouting: true, locales: { root: { label: 'English', lang: 'en', }, zh: { label: '简体中文', lang: 'zh-CN', link: '/zh/', themeConfig: { nav: [ { text: '指南', link: '/zh/guide/getting-started' }, ], sidebar: { '/zh/guide/': { base: '/zh/guide/', items: [ { text: '指南', items: [ { text: '快速开始', link: 'getting-started' }, ], }, ], }, }, editLink: { pattern: 'https://github.com/acme/project/edit/main/docs/:path', text: '编辑此页', }, }, }, }, }, }) ``` Place translated pages under the matching prefix: ```txt pages/ ├── guide/getting-started.md └── zh/guide/getting-started.md ``` ## Theme Press Navigation And Sidebar - **Categories**: theme ## Navigation {#nav} `themeConfig.nav` controls the top navigation. A nav item can be a direct link or a dropdown group. ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { nav: [ { text: 'Guide', link: '/guide/getting-started' }, { text: 'Ecosystem', items: [ { text: 'Addons', link: '/addons/' }, { text: 'Themes', link: '/themes/' }, ], }, ], }, }) ``` `text` can be plain text or an i18n key from your `locales/*.yml` files. ## Sidebar {#sidebar} Press supports the same broad configuration style as VitePress: an array for a single sidebar, or an object keyed by path prefix for multiple sidebars. ### Category-Driven Sidebar {#category-sidebar} Add a category name to `themeConfig.sidebar`, then mark pages with the same `categories` value: ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { sidebar: ['guide', 'reference'], }, }) ``` ```md [pages/guide/getting-started.md] --- title: Getting Started categories: - guide --- ``` This style is convenient for blog-like docs where page order can follow the generated page list. ### Explicit Tree {#explicit-tree} Use an explicit tree when you need exact order, nested groups, external links, or collapsible sections: ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { sidebar: [ { text: 'Guide', collapsed: false, items: [ { text: 'Getting Started', link: '/guide/getting-started' }, { text: 'Configuration', link: '/guide/config' }, { text: 'Advanced', collapsed: true, items: [ { text: 'Markdown', link: '/guide/markdown' }, { text: 'Deployment', link: '/guide/deploy' }, ], }, ], }, ], }, }) ``` Nested directories should be represented by nested `items` on any `SidebarItem`. If `collapsed` is omitted, Press renders the children as an always-expanded indented list. Add `collapsed: true` or `collapsed: false` only when that group should expose a collapsible caret. `docFooterText` customizes the label shown in previous/next page navigation: ```ts const item = { text: 'Configuration', link: '/guide/config', docFooterText: 'Configure Valaxy', } ``` ### Multiple Sidebars {#multiple-sidebars} For documentation with subdirectories, use a path-keyed object. Press selects the longest matching path prefix for the current route. ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { sidebar: { '/guide/': [ { text: 'Guide', items: [ { text: 'Introduction', link: '/guide/' }, { text: 'Getting Started', link: '/guide/getting-started' }, ], }, ], '/api/': [ { text: 'API Reference', items: [ { text: 'Config', link: '/api/config' }, { text: 'Methods', link: '/api/methods' }, ], }, ], }, }, }) ``` ### Base Path {#base-path} Use `base` to avoid repeating a common prefix. This mirrors VitePress' sidebar object format. ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { sidebar: { '/guide/': { base: '/guide/', items: [ { text: 'Guide', items: [ { text: 'Introduction', link: '' }, { text: 'Getting Started', link: 'getting-started' }, { text: 'Configuration', link: 'config' }, ], }, ], }, }, }, }) ``` When a sidebar array contains top-level links without `items`, Press groups consecutive links into an anonymous group, matching VitePress' default sidebar rendering model. ## Valaxy 插件橱窗 - **Categories**: addon 浏览 Valaxy 官方与社区插件。 Valaxy 主仓库维护的包已整理到[官方插件](/zh/addons/official)文档中;本橱窗还会同时收录社区插件。 <AddonGallery /> ## valaxy-addon-girls - **Categories**: addon 与主题解耦的响应式角色画廊插件,支持网格、圆球星团与轨道布局。 <!--@include: @/../packages/valaxy-addon-girls/README.zh-CN.md{3,}--> ## 交互示例 以下内容只属于文档站。上方的安装、配置和 API 说明均直接来自插件 README。 ### 布局与备注模式 <GirlsAddonShowcase locale="zh-CN" /> ### 完整 120 条角色数据 在线示例默认把全部角色装入同一个圆球星团;切换到网格后,可以比较渐进渲染与完整渲染。 <GirlsAddonLargeShowcase locale="zh-CN" /> ## index # Addons {#addons} ## 官方插件 - **Categories**: addon 构建时直接引用 Valaxy 主仓库各插件 README 的官方插件文档。 以下插件页面会在构建时直接引入对应包的 README。安装、配置和 API 说明只维护在 README 中,本页仅负责导航;[插件橱窗](/zh/addons/gallery)还会收录社区插件。 - [valaxy-addon-abbrlink](/zh/addons/official/abbrlink) - [valaxy-addon-algolia](/zh/addons/official/algolia) - [valaxy-addon-bangumi](/zh/addons/official/bangumi) - [valaxy-addon-components](/zh/addons/official/components) - [valaxy-addon-feishu](/zh/addons/official/feishu) - [valaxy-addon-girls](/zh/addons/girls) - [valaxy-addon-lightgallery](/zh/addons/official/lightgallery) - [valaxy-addon-meting](/zh/addons/official/meting) - [valaxy-addon-moments](/zh/addons/official/moments) - [valaxy-addon-twikoo](/zh/addons/official/twikoo) - [valaxy-addon-waline](/zh/addons/official/waline) `valaxy-addon-test` 是仓库内部测试夹具与模板,因此不列入用户文档。 ## 使用插件 - **Categories**: addon ## How To Use {#how-to-use} ```bash pnpm add [valaxy-addon-package1] [valaxy-addon-package2] # npm i [valaxy-addon-package1] [valaxy-addon-package2] ``` 使用 ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { addonTest } from 'valaxy-addon-test' export default defineValaxyConfig({ addons: [ // we always recommend to use function, so that you can pass options addonTest(), 'valaxy-addon-package1', // pass addon options ['valaxy-addon-package2', { global: false }], ] }) ``` ### Addon With Options {#addon-with-options} 譬如开启 Waline 评论: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { addonWaline } from 'valaxy-addon-waline' export default defineValaxyConfig({ // 启用评论 comment: { enable: true }, // 设置 valaxy-addon-waline 配置项 addons: [ addonWaline({ serverURL: 'https://your-waline-url', }), ], }) ``` ## 为什么需要插件? - **Categories**: addon 我们需要一个插件系统允许用户仅使用/快速加载部分功能。 ## 命名规范 {#naming-conventions} 插件名称:`valaxy-addon-<name>`。 > Add-on 相比 Plug-in 通常包含对界面造成修改,以及仅在某特定平台下适用的含义。 > 譬如 Edge 插件商店(Add-on Store),Slidev 等使用 Addon 命名。 > > - [Difference Between Add-on and Plug-in](http://www.differencebetween.net/technology/difference-between-add-on-and-plug-in/) > > Valaxy 本身完全支持使用 Vite 与 Vue 生态插件。 > 除此之外,我们可能还需要支持一些针对 Valaxy 并(在 Vite/Vue 插件运行前)可控制整个流程的插件。 > > 此时,Addon 的 API 仅仅适用于 Valaxy 平台。 ## 说明 {#explanation} 插件可以做什么? 譬如制作一个 Live2D 挂件,一个全局音乐播放器,或是修改 Vite 以及内置插件的一些配置等。 它用于补充 Vite/Vue 插件无法做到或加载配置繁琐的内容。 ## 编写一个插件 - **Categories**: addon ## 开始编写 {#开始编写} ::: tip **约定大于配置** - 插件:`Addon`,须以 `valaxy-addon-` 开头。 - 插件与主题类似,但做的事情更少。 - 一个站点只能使用一个主题,但可以使用多个插件。 - Addon 无需预编译,直接发布源文件即可。 ::: - `App.vue` 如果插件作者希望插件被使用时立刻全局挂载,可以将内容放置于 `valaxy-addon-<name>/App.vue` 中,并设置 `package.json` 中 `global: true`。 - `components`: 放置于 `components` 文件夹下的组件将会被自动注册,但不会被挂载。用户可以手动加载使用。 > 文档正在施工中,您可以参照 [插件橱窗](/zh/addons/gallery) 一些已有的插件。 <!-- 用户如何配置 global --> ### 创建插件模板 {#创建插件模板} ```bash pnpm create valaxy # choose template addon ``` ### 使用生命周期钩子 {#使用生命周期钩子} 如示例所示,插件可以使用 `valaxy.hook` 来挂载生命周期钩子。 实现在构建前/后以及其他节点做一些事情。 > 请参考 [生命周期钩子](/zh/guide/custom/hooks) 了解更多。 <<< @/../packages/valaxy-addon-test/node/index.ts {11-14} [valaxy-addon-test/node/index.ts] ### 在客户端读取插件选项 {#reading-addon-options} 插件通常通过 `valaxy.config.ts` 中的 `defineAddon(options)` 接受用户选项。在客户端运行时,可以通过 `useAddonConfig<T>(addonName)` 获取这些选项。 `useAddonConfig` 是 `valaxy` 提供的通用类型安全 composable,替代了以往 `useRuntimeConfig()` + `computed()` + 手动类型断言的模板代码。 ```ts [client/options.ts] import type { MyAddonOptions } from '../types' import { useAddonConfig } from 'valaxy' export function useMyAddonConfig() { return useAddonConfig<MyAddonOptions>('valaxy-addon-my-addon') } ``` 返回值类型为 `ComputedRef<ValaxyAddon<MyAddonOptions> | undefined>` —— 当插件未安装时为 `undefined`。通过 `.value?.options` 访问选项。 ```vue [components/MyComponent.vue] <script lang="ts" setup> import { useMyAddonConfig } from '../client/options' const addon = useMyAddonConfig() // addon.value?.options?.someField </script> ``` ::: tip `useAddonConfig` 必须在 `<script setup>` 或 Vue 生命周期钩子内调用(与其他 Vue composable 约束相同)。 主题组件也可以直接使用 `useAddonConfig` 读取插件选项,无需对插件包产生硬依赖。参见[在主题中使用插件配置](/zh/themes/write#using-addon-config-in-themes)。 ::: ## index # API {#api} API Docs: <https://api.valaxy.site> <ApiDocsRedirect /> ## AI 辅助开发 - **Categories**: dev Valaxy 通过 [Claude Code](https://claude.com/code) 支持 AI 辅助开发工作流,使项目贡献变得更加简单。 ## 环境配置 {#setup} 仓库在 `.claude/commands/` 目录中包含了自定义的 Claude Code 命令,用于简化常见的开发任务。 ## 可用命令 {#available-commands} ### 修复 GitHub Issues {#fix-github-issues} 自动分析并修复 GitHub issues: ```bash /fix-github-issue 1234 ``` 此命令将会: 1. 使用 GitHub CLI 获取 issue 详情 2. 分析问题描述 3. 搜索相关代码文件 4. 实现必要的修改 5. 运行测试验证修复 6. 确保代码质量(代码检查、类型检查) 7. 创建描述性的提交 8. 推送更改并创建 Pull Request **示例:** ```bash /fix-github-issue 628 ``` 这将自动修复 issue #628,包括: - 读取 issue 描述 - 找到受影响的组件 - 实现修复 - 运行测试 - 创建带有适当描述的 PR ## CLAUDE.md {#claudemd} 仓库根目录包含 `CLAUDE.md` 文件,提供: - 基本开发命令 - 架构概览 - 关键模式和约定 - 项目特定说明 该文件帮助 Claude Code 理解代码库结构和开发工作流。 ## 最佳实践 {#best-practices} 使用 AI 辅助开发时: 1. **审查更改**:提交前务必审查 AI 做出的更改 2. **充分测试**:确保测试通过并手动验证关键更改 3. **理解代码**:不要只是接受更改 - 理解更改了什么以及为什么 4. **迭代优化**:与 AI 迭代协作以优化解决方案 5. **遵循约定**:AI 会遵循现有代码模式,但要验证一致性 ## 创建自定义命令 {#creating-custom-commands} 你可以为常见任务创建自定义命令: 1. 在 `.claude/commands/` 中创建新文件 2. 使用描述性命名(例如 `add-feature.md`) 3. 编写 Claude Code 要遵循的指令 **示例命令结构:** ```markdown 请实现新功能:$ARGUMENTS。 遵循以下步骤: 1. 分析需求 2. 设计解决方案 3. 实现代码 4. 编写测试 5. 更新文档 ``` ## 提示 {#tips} - 使用 `/help` 查看所有可用命令 - AI 可以访问完整的代码库上下文 - 命令可以通过 `$ARGUMENTS` 接受参数 - AI 会遵循 `CLAUDE.md` 和现有代码的模式 - 可以使用 GitHub CLI (`gh`) 进行 GitHub 操作 ## 局限性 {#limitations} - AI 建议应由人工审查 - 复杂的架构决策可能需要人工规划 - 安全敏感的更改需要额外审查 - 部署前务必在本地环境测试 --- **注意**:AI 辅助开发是提高生产力的工具,而非替代人工判断。务必审查和理解所做的更改。 ## 参与文档 - **Categories**: dev ## 文档编写规范 {#docs-writing} Valaxy 正在为 1.0 的发布做准备,我们很期待您参与文档的撰写与翻译。 ## 文档组织方式 {#文档组织方式} Valaxy 文档采用**路径分离**的方式组织中英文内容: - 英文文档位于 `/docs/pages/` 目录下 - 中文文档位于 `/docs/pages/zh/` 目录下 例如: ``` docs/pages/guide/getting-started.md # 英文版 docs/pages/zh/guide/getting-started.md # 中文版 ``` ### 双语容器方式(仅用于特定场景) 某些文档(如博客文章)可能使用双语容器的方式在同一文件中编写中英文: ```md ::: zh-CN 中文内容 ::: ::: en English content ::: ``` 更多请参见 [单页 i18n](https://valaxy.site/guide/i18n) 和 [i18n 容器规范](/guide/i18n#container-syntax)。 ## 如何翻译 {#如何翻译} ### 1. 创建对应的中文文档 如果你发现某个英文文档还没有中文版本,请在 `/docs/pages/zh/` 下创建对应路径的文件。 例如,要翻译 `/docs/pages/guide/ssr-compat.md`: 1. 创建 `/docs/pages/zh/guide/ssr-compat.md` 2. 复制英文文档的结构 3. 将内容翻译为中文 4. 保持代码示例不变(除非需要中文化注释) ### 2. 保持文档结构一致 - **Frontmatter**:保持相同的 `categories`、`top` 等字段,只翻译 `title` - **标题层级**:保持与英文版相同的标题结构 - **代码示例**:通常不需要翻译,保持原样 - **链接**:中文文档中的内部链接应指向中文版(如 `/zh/guide/...`) ### 3. 翻译建议 - 专有名词首次出现时可以保留英文,如:"SSR(服务端渲染)" - 保持技术术语的准确性,可参考 [Vue 中文文档](https://cn.vuejs.org/) 的翻译规范 - 代码中的注释可以适当中文化,但变量名、函数名保持英文 ## 如何提交 {#如何提交} 使用 GitHub 的 Pull Request 向 valaxy 提交即可。 建议您以一个完整的 md 文件或一个分类翻译为一次提交。 Commit message 请以 `docs:` 开头。 譬如: - 添加新的中文文档翻译:`docs: add zh translation for ssr-compat` - 更新现有翻译:`docs: update guide translation` - 修改错别字:`docs: fix typo in xxx.md` - 更新英文文档:`docs(en): update getting-started guide` ## 文档预览 {#文档预览} 在提交前,请在本地预览文档: ```bash # 安装依赖 pnpm install # 启动文档开发服务器 pnpm docs:dev # 构建文档(用于检查构建错误) pnpm docs:build ``` 访问 `http://localhost:4859` 查看文档效果,使用右上角的语言切换按钮测试中英文切换。 ## 常见问题 - **Categories**: dev <details> <summary>已解决</summary> ## `background-attachment: fixed` iOS 不支持 {#background-attachment-fixed-ios-不支持} > iOS has an issue preventing background-attachment: fixed from being used with background-size: cover. > [The Fixed Background Attachment Hack | CSS Tricks](https://css-tricks.com/the-fixed-background-attachment-hack/) 改为使用 `::before` 伪元素实现。 </details> ## JavaScript heap out of memory {#javascript-heap-out-of-memory} SSG 构建(`valaxy build --ssg`)时,内置的 Valaxy SSG 引擎在同一进程中依次执行 client 构建、server 构建、页面渲染。构建阶段的 Vite resolved config 和插件系统会驻留内存,导致渲染阶段可用堆空间有限。 **最低内存要求:`--max-old-space-size=4096`(约 4 GB)**——引擎会在需要时自动以此堆重启。 ```bash # 复现测试 pnpm test:space # demo/yun pnpm test:space:docs # docs ``` 当堆限制低于 ~4 GB 时,SSG 引擎会自动以 `--max-old-space-size=4096`(以及在可用时 加上 `--expose-gc`)重启构建进程,使渲染阶段有足够余量。页面渲染默认并发数为 20 (可通过 `vite.ssgOptions.concurrency` 配置)。 如果你仍在 CI 环境中遇到 OOM,可以通过设置 `NODE_OPTIONS` 增大堆限制: ```bash NODE_OPTIONS=--max-old-space-size=4096 pnpm build --ssg ``` ## 合并 {#合并} 使用 `defu`。 但实测 `defu` faster than `@fastify/deepmerge`。 合并单个配置: - `defu`: 0.06ms - [`@fastify/deepmerge`](https://github.com/fastify/deepmerge): 0.256ms ```bash # benchmark @fastify/deepmerge x 605,343 ops/sec ±0.87% (96 runs sampled) deepmerge x 20,312 ops/sec ±1.06% (92 runs sampled) merge-deep x 83,167 ops/sec ±1.30% (94 runs sampled) ts-deepmerge x 175,977 ops/sec ±0.57% (96 runs sampled) deepmerge-ts x 174,973 ops/sec ±0.44% (93 runs sampled) lodash.merge x 89,213 ops/sec ±0.70% (98 runs sampled) ``` ## 参与开发 - **Categories**: dev - `create-valaxy` - `create-valaxy-theme` ## AI-Assisted Development {#ai-assisted-development} Valaxy supports AI-assisted development workflows. See [AI-Assisted Development](./ai) for details. Quick example - fix a GitHub issue automatically: ```bash /fix-github-issue 628 ``` ## Dev {#dev} You must use [pnpm](https://pnpm.io/). Because we use its workspace. ```bash git clone https://github.com/YunYouJun/valaxy ``` ```bash [pnpm] cd valaxy pnpm i # esbuild watch valaxy cli & valaxy-theme-yun # and run demo # build node cli pnpm run build # pnpm dev = pnpm dev:lib + pnpm demo pnpm dev ``` ### Docs {#docs} We use valaxy to build docs. Just eat our own dog food. > If you want to use more out-of-the-box for docs, you can use [VitePress](https://vitepress.dev/). ```bash # build latest valaxy cli pnpm run build pnpm run docs:build ``` If you want to display info better in two terminal (**Recommended**), follow below. ### Node {#node} ```bash # watch valaxy & valaxy-theme-yun pnpm dev:lib ``` ### Client {#client} If you only want to develop client. - Docs: `pnpm docs:dev` - Demo(theme-yun): `pnpm demo` ## LOGO LOGO = V + Galaxy - 银河 - 闪耀 - 星球 - 夜空 ## 客户端 - **Categories**: ecosystem - [valaxy-admin](https://github.com/valaxyjs/valaxy-admin) ## 安装 {#安装} ::: warning 开发中,暂未发布 ::: ## 功能 {#功能} 我们计划以客户端面板(基于 Electron)的形式,提供从安装到部署的一体化界面流程,以便供非开发者用户使用。 ## 社区 - **Categories**: ecosystem ## 社群 {#社群} - [QQ 群 1050458482](https://qm.qq.com/cgi-bin/qm/qr?k=kZJzggTTCf4SpvEQ8lXWoi5ZjhAx0ILZ&jump_from=webapi) - [Discord](https://discord.gg/sGe4U4p4CK) ## GitHub Org {#github-org} - [Valaxyjs](https://github.com/valaxyjs) - [Valaxy](https://github.com/YunYouJun/valaxy) - [社区讨论](https://github.com/YunYouJun/valaxy/discussions) - [问题反馈](https://github.com/YunYouJun/valaxy/issues) ### 相关副产物 {#相关副产物} - [css-i18n](https://github.com/valaxyjs/css-i18n): CSS 国际化方案 ## 主题 {#主题} - 见 [主题橱窗](/zh/themes/gallery) ## 插件 {#插件} - 见 [插件橱窗](/zh/addons/gallery) ## index ## 新闻 - **Categories**: ecosystem ## v0.15.x {#v015x} - [Release Notes](https://github.com/YunYouJun/valaxy/releases/tag/v0.15.0) ### Break Changes {#break-changes} Valaxy 完全升级至 ESM 模块化,不再支持 CommonJS。 ## VSCode 扩展 - **Categories**: ecosystem - GitHub: [valaxy-vscode](https://github.com/YunYouJun/valaxy-vscode) - VSCode Marketplace: [Valaxy](https://marketplace.visualstudio.com/items?itemName=yunyoujun.valaxy) ## 安装 {#安装} 在 VSCode 插件商店中搜索 `Valaxy` 安装即可。 > [Valaxy](https://marketplace.visualstudio.com/items?itemName=yunyoujun.valaxy) ## 功能 {#功能} 它提供了文章列表预览/切换/删除等功能,以便让你尽可能地可以在 VSCode 中完成所有操作。 正在完善中…… ## 代码块图标 - **Categories**: examples 代码块图标基于 [vitepress-plugin-group-icons](https://github.com/yuyinws/vitepress-plugin-group-icons) 实现。 另请参阅 [内置图标列表](https://vp.yuy1n.io/features.html#built-in-icons)。 ## 内置图标 许多常见文件类型已内置图标支持。只需在语言标识符后使用 `[文件名]` 语法: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({}) ``` ```vue [App.vue] <script setup lang="ts"> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++"> Count: {{ count }} </button> </template> ``` ```json [package.json] { "name": "my-valaxy-blog", "version": "0.1.0" } ``` ```yaml [docker-compose.yml] version: '3' services: app: image: node:20 ``` ```bash [install.sh] #!/bin/bash pnpm install pnpm build ``` ## 自定义图标 你可以在 `valaxy.config.ts` 中配置自定义图标: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { localIconLoader } from 'vitepress-plugin-group-icons' export default defineValaxyConfig({ groupIcons: { customIcon: { // 使用本地 SVG 文件 valaxy: localIconLoader(import.meta.url, './public/favicon.svg'), // 使用 iconify 图标 nodejs: 'vscode-icons:file-type-node', playwright: 'vscode-icons:file-type-playwright', typedoc: 'vscode-icons:file-type-typedoc', eslint: 'vscode-icons:file-type-eslint', dockerfile: 'vscode-icons:file-type-docker', }, }, }) ``` ## 代码组中的图标 你也可以在代码组中使用图标: ::: code-group ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ theme: 'yun', }) ``` ```json [package.json] { "dependencies": { "valaxy": "latest", "valaxy-theme-yun": "latest" } } ``` ```toml [netlify.toml] [build] command = "pnpm build" publish = "dist" ``` ::: ## 代码块高度限制 - **Categories**: examples 在 Front Matter 中设置 `codeHeightLimit: 300`。 ```md [pages/code-height-limit.md] --- codeHeightLimit: 300 --- ``` 渲染结果 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' const safelist = [ 'i-ri-home-line', ] export default defineValaxyConfig<ThemeConfig>({ // site config see site.config.ts or write in siteConfig // siteConfig: {}, theme: 'yun', themeConfig: { banner: { enable: true, title: '云游君的小站', }, notice: { enable: true, content: '公告测试', }, }, unocss: { safelist, }, }) ``` ## index ## Mermaid - [Mermaid](https://mermaid.js.org/) - Diagramming and charting tool ## Flowchart {#flowchart} ```mermaid graph TD; A-->B; A-->C; B-->D; C-->D; ``` ````txt ```mermaid graph TD; A-->B; A-->C; B-->D; C-->D; ``` ```` ## 思维导图 {#mindmap} 轻量的思维导图可以直接使用 Mermaid,不需要额外安装 Valaxy addon。如果需要将较长的 Markdown 大纲转换为可交互、可折叠的地图,再考虑 Markmap。 ```mermaid mindmap root((Valaxy 博客)) 内容 文章 页面 资源 扩展 组件 主题 插件 交付 SSG 构建 RSS 部署 ``` ````txt ```mermaid mindmap root((Valaxy 博客)) 内容 文章 页面 资源 扩展 组件 主题 插件 交付 SSG 构建 RSS 部署 ``` ```` ## Sequence diagram {#sequence-diagram} ```mermaid sequenceDiagram participant Alice participant Bob Alice->>John: Hello John, how are you? loop Healthcheck John->>John: Fight against hypochondria end Note right of John: Rational thoughts <br/>prevail! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good! ``` ````txt ```mermaid sequenceDiagram participant Alice participant Bob Alice->>John: Hello John, how are you? loop Healthcheck John->>John: Fight against hypochondria end Note right of John: Rational thoughts <br/>prevail! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good! ``` ```` ## Gantt diagram {#gantt-diagram} ```mermaid gantt dateFormat YYYY-MM-DD title Adding GANTT diagram to mermaid excludes weekdays 2014-01-10 section A section Completed task :done, des1, 2014-01-06,2014-01-08 Active task :active, des2, 2014-01-09, 3d Future task : des3, after des2, 5d Future task2 : des4, after des3, 5d ``` ````txt ```mermaid gantt dateFormat YYYY-MM-DD title Adding GANTT diagram to mermaid excludes weekdays 2014-01-10 section A section Completed task :done, des1, 2014-01-06,2014-01-08 Active task :active, des2, 2014-01-09, 3d Future task : des3, after des2, 5d Future task2 : des4, after des3, 5d ``` ```` ## Class diagram {#class-diagram} ```mermaid classDiagram Class01 <|-- AveryLongClass : Cool Class03 *-- Class04 Class05 o-- Class06 Class07 .. Class08 Class09 --> C2 : Where am i? Class09 --* C3 Class09 --|> Class07 Class07 : equals() Class07 : Object[] elementData Class01 : size() Class01 : int chimp Class01 : int gorilla Class08 <--> C2: Cool label ``` ````txt ```mermaid classDiagram Class01 <|-- AveryLongClass : Cool Class03 *-- Class04 Class05 o-- Class06 Class07 .. Class08 Class09 --> C2 : Where am i? Class09 --* C3 Class09 --|> Class07 Class07 : equals() Class07 : Object[] elementData Class01 : size() Class01 : int chimp Class01 : int gorilla Class08 <--> C2: Cool label ``` ```` ## Git graph {#git-graph} ```mermaid gitGraph commit commit branch develop commit commit commit checkout main commit commit ``` ````txt ```mermaid gitGraph commit commit branch develop commit commit commit checkout main commit commit ``` ```` ## Quadrant Chart {#quadrant-chart} ```mermaid quadrantChart title Reach and engagement of campaigns x-axis Low Reach --> High Reach y-axis Low Engagement --> High Engagement quadrant-1 We should expand quadrant-2 Need to promote quadrant-3 Re-evaluate quadrant-4 May be improved Campaign A: [0.3, 0.6] Campaign B: [0.45, 0.23] Campaign C: [0.57, 0.69] Campaign D: [0.78, 0.34] Campaign E: [0.40, 0.34] Campaign F: [0.35, 0.78] ``` ````txt ```mermaid quadrantChart title Reach and engagement of campaigns x-axis Low Reach --> High Reach y-axis Low Engagement --> High Engagement quadrant-1 We should expand quadrant-2 Need to promote quadrant-3 Re-evaluate quadrant-4 May be improved Campaign A: [0.3, 0.6] Campaign B: [0.45, 0.23] Campaign C: [0.57, 0.69] Campaign D: [0.78, 0.34] Campaign E: [0.40, 0.34] Campaign F: [0.35, 0.78] ``` ```` ## XY Chart {#xy-chart} ```mermaid xychart-beta title "Sales Revenue" x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec] y-axis "Revenue (in $)" 4000 --> 11000 bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] ``` ````txt ```mermaid xychart-beta title "Sales Revenue" x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec] y-axis "Revenue (in $)" 4000 --> 11000 bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] ``` ```` ## 部分内容加密 - **Categories**: examples 密码为 `valaxy`。 ```md <!-- valaxy-encrypt-start:valaxy --> 我是被加密的文本。 ::: details dynamically rendered frontmatter 支持动态渲染 **Frontmatter**: {{ frontmatter }} ::: <!-- valaxy-encrypt-end --> ``` ## 渲染结果 {#rendering-result} <!-- valaxy-encrypt-start:valaxy --> 我是被加密的文本。 ::: details dynamically rendered frontmatter 支持动态渲染 **Frontmatter**: {{ frontmatter }} ::: <!-- valaxy-encrypt-end --> ## 示例站点 - **Categories**: ecosystem ::: tip 欢迎 [提交 PR](https://github.com/YunYouJun/valaxy/blob/main/docs/assets/sites.ts) 补充你的站点! ::: <ExampleSites /> ## 常见问题 - **Categories**: guide ## 构建失败 {#构建失败} ### ReferenceError: document is not defined {#referenceerror-document-is-not-defined} 这通常发生在使用自定义代码 `document.xxx` 或引入第三方库(仅在浏览器端可用的 NPM 包)时。 代码直接调用了 `document`,而该变量在 Node 端不存在,因此导致构建失败。 你应当使用 `isClient` 判断逻辑来使得该代码仅在客户端执行。 ```ts import { isClient } from '@vueuse/core' if (isClient) { document.xxx() // import('xxx') } ``` ## 改变构建形式 {#change-generated-directory-style} Valaxy 默认将 `xxx.md` 构建为 `/xxx.html`。 如果你更希望构建为 `/xxx/index.html` 的目录形式,请将页面组织为目录索引——把内容放在 `pages/xxx/index.md`(而非 `pages/xxx.md`)。以 `/` 结尾的路由会写入 `route-path/index.html`。 > 旧版 `vite-ssg` 的 `dirStyle` 选项已随传统引擎在 v1.0 中移除(见 [#706](https://github.com/YunYouJun/valaxy/issues/706))。 ## 部署到 Github Pages 后部分页面无法访问或 JS 路径找不到 {#after-deploying-to-github-pages-some-pages-cannot-be-accessed-or-the-js-path-cannot-be-found} Github Pages 默认使用 Jekyll 来构建静态站点,而 Jekyll 默认不会构建以 `_` 开头的文件或文件夹。 使用 Valaxy 构建后的产物可能会出现以 `_` 开头的文件,所以这种文件提交后会被 Jekyll 的构建忽略,从而导致问题发生。 实际上 Valaxy 构建后的产物可以直接用作静态站点,而不需要 Jekyll 构建这种多余的操作。 如果 Github Pages 所部署内容的根路径有名为 .nojekyll 的空文件,则会跳过 Jekyll 构建操作。 所以可以在项目的 `public` 文件夹内新建一个名为 `.nojekyll` 的文件: ```bash |-- public | |-- .nojekyll ``` ## 最佳实践 - **Categories**: guide 以下建议可以让 Valaxy 博客更容易迁移,并减少只在生产环境出现的问题。它们是推荐约定,并非强制要求。 ## 项目与依赖 {#project-and-dependencies} - 使用 Valaxy 支持的 Node.js 版本,并让本地与 CI 保持一致。当前版本要求请查看[快速上手](/zh/guide/getting-started)。 - 整个项目只使用一种包管理器。推荐使用 `pnpm`,并提交 `pnpm-lock.yaml`,让本地和 CI 安装相同的依赖关系。 - 配置文件或组件中直接导入的包,都应添加到博客自身的 `package.json`。不要依赖由 Valaxy、主题或其他插件间接安装的包。 - 升级时尽量同步升级 Valaxy、官方主题与插件,并在升级后执行一次生产构建。 ## 文章与资源 {#posts-and-assets} 建议使用稳定、适合作为 URL 的英文名称命名文件夹和文件: ```txt blog/pages/posts/your-post.md ``` 本地文章资源建议与文章放在同一文件夹,并使用相对路径引用。这样既便于迁移,也能让 Vite 在页面和生成的订阅内容中正确处理资源。 ```txt pages/posts/your-post ├── a.png ├── b.png └── index.md ``` ```md [pages/posts/your-post/index.md] ![图片 A](./a.png) ![图片 B](./b.png) ``` 站点部署到子路径时,Markdown 中的根绝对链接会自动适配。在 Vue 组件中动态生成 URL 时,请使用 `withBase()`。详见[部署到子路径](/zh/guide/deploy#deploy-under-base-path)。 ## 动态与第三方内容 {#dynamic-and-third-party-content} 插入第三方脚本或大量动态内容时,优先将其封装为 `components/` 中的 Vue 组件,再从 Markdown 使用组件。这样可以把副作用留在文章之外,并集中处理加载、错误和清理逻辑。 ```bash pnpm add @vueuse/core ``` ```vue [components/BszComponent.vue] <script lang="ts" setup> import { useScriptTag } from '@vueuse/core' useScriptTag('https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js') </script> <template> <div> <div>本站总访问量 <span id="busuanzi_value_site_pv" /> 次</div> <div>本站访客数 <span id="busuanzi_value_site_uv" /> 人次</div> </div> </template> ``` ```md [pages/posts/test-custom-component.md] # Hello World <BszComponent /> ``` Valaxy 使用 SSR 生成页面。如果第三方库会访问 `window`、`document` 等浏览器 API,请在挂载后初始化,或通过仅客户端组件加载。详见 [SSR 兼容性](/zh/guide/ssr-compat)。 ## 选择最小的扩展层级 {#choose-the-smallest-extension-level} | 需求 | 推荐方式 | | --- | --- | | 已支持的 Markdown 语法,例如图表 | 优先使用内置能力,例如 [Mermaid](/zh/guide/markdown#mermaid) | | 仅在一个博客使用的库或挂件 | 在 `components/` 中创建本地组件 | | 多个博客共用的通用组件 | 发布组件包,或贡献到 `valaxy-addon-components` | | 需要 Markdown 转换、构建钩子、共享配置或自动注册组件 | [编写插件](/zh/addons/write) | 简单的思维导图可以直接使用内置的 [Mermaid 思维导图示例](/zh/examples/mermaid#mindmap)。使用 Markmap 时,建议先通过 `markmap-lib` 与 `markmap-view` 编写本地 `Markmap.vue` 组件,并仅在客户端初始化。当它还需要提供 `markmap` 代码围栏、统一的主题与工具栏配置、资源处理和 SSR 安全的生命周期时,再独立为 addon 才有明显收益。在此之前,addon 会增加安装和维护成本,却不能显著减少用户代码。 ## 部署前验证 {#verify-before-deployment} 开发模式无法暴露所有 SSR、依赖和子路径问题。部署前请执行: ```bash pnpm build pnpm serve ``` 至少检查首页、直接打开或刷新后的文章页、包含第三方内容的页面;如果配置了 `vite.base`,还应在实际生产子路径下检查。 反馈问题时,请提供最小复现、完整错误信息、问题发生在开发还是生产环境,以及以下命令生成的环境信息: ```bash pnpm exec valaxy debug --plain ``` ## 部署 - **Categories**: getting-started Valaxy 的部署非常简单,我们推荐你直接通过第三方的 CI 构建并托管到任意平台。 ## 自行部署 {#manual-deployment} ::: code-group ```bash [pnpm] pnpm run build ``` ```bash [yarn] yarn build ``` ```bash [npm] npm run build ``` ::: 执行 `build` 命令构建,`dist` 文件夹为构建后的内容。 SSG 构建需要足够的堆内存(~4 GB;引擎会自动以足够内存重启)。若仍遇到 `JavaScript heap out of memory` 错误,请设置: ```bash NODE_OPTIONS=--max-old-space-size=4096 pnpm build ``` ## 部署到子路径 {#deploy-under-base-path} 当站点部署在域名的子路径下时,请配置 Vite 的 `base`,并保留开头和结尾的 `/`。`siteConfig.url` 表示站点的规范 URL,不能代替资源路径的 `base`。例如,GitHub Pages 项目站点 `https://user.github.io/repo/` 应配置为: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ siteConfig: { url: 'https://user.github.io/repo/', }, vite: { base: '/repo/', }, }) ``` 从 Valaxy v1.0.0-rc.4 开始,这部分行为默认与 VitePress 对齐,不需要额外开启兼容开关。Vite 最终解析出的 `base` 会共享给页面、摘要/路由和本地搜索使用的 Markdown 渲染器。 Markdown 中的根绝对链接和静态资源会自动适配 `base`: ```md [指南](/guide/) ![Logo](/logo.png) [下载 PDF](/manual.pdf) ``` Vue 组件或主题配置中的动态 URL 请使用 `withBase()`: ```vue <script setup lang="ts"> import { withBase } from 'valaxy' const logo = '/logo.png' </script> <template> <img :src="withBase(logo)" alt="Logo"> </template> ``` Markdown 页面与文件链接会直接补上 `base`;Markdown 图片则会进入 Vue/Vite 静态资源处理链路,由它在最终产物中为根绝对 public 资源应用 `base`。 外部 URL 和相对路径不会被修改。原生 HTML `<a>` 链接也保持原样,以便显式链接到 `base` 之外的位置;原生 HTML 图片仍可能由 Vue/Vite 转换。 ## 第三方部署 {#third-party-deployment} ::: tip 第三方部署的各配置文件已内置在 Valaxy 的初始化模版项目中,您可以按需使用。 如果部署失败,推荐您先在本地通过 `npm run build` 检查潜在的构建错误。 ::: ### GitHub Pages {#github-pages} <BrandIcon icon="i-logos:github-icon" link="https://pages.github.com/" /> ::: tip 名为 `你的用户名.github.io` 的仓库会部署在根路径 `/`,无需额外设置 `base`。其他仓库名也可以作为项目站点部署,请按照上文配置 `base: '/仓库名/'`。 ::: ::: details .github/workflows/gh-pages.yml <<< @/../packages/create-valaxy/template-blog/.github/workflows/gh-pages.yml ::: 在使用 `pnpm create valaxy` 创建模版项目时,已内置文件[`.github/workflows/gh-pages.yml`](https://github.com/YunYouJun/valaxy/blob/main/packages/create-valaxy/template-blog/.github/workflows/gh-pages.yml) 以实现 GitHub Actions 的自动部署工作流。 - 选择 Github Repo,打开 `Settings`-> `Action` -> `General` -> `Workflow permissions`,选择 `read and write permissions`。 - 上传至 GitHub Repo,打开 `Settings` -> `Pages`,选择 `gh-pages` 分支。 > `gh-pages` 已由 `.github/workflows/gh-pages.yml` 自动部署。 > 注意修改 `gh-pages.yml` 中的 `on.push.branches` 为你源代码所在的分支,默认为 `main`。 ### Netlify {#netlify} <BrandIcon icon="i-logos:netlify-icon" link="https://www.netlify.com/" /> 已内置 `netlify.toml`。 - 连接 GitHub 仓库,可自动部署。 ### Vercel {#vercel} <BrandIcon icon="i-logos:vercel-icon" link="https://vercel.com/" /> 对于已有的 Valaxy 博客,在开始部署之前,您需要先对您博客的 `vercel.json` 进行修改以便[启用 `cleanUrls` 支持](https://vercel.com/docs/projects/project-configuration#cleanurls): ```json [vercel.json] { "cleanUrls": true } ``` 对于新创建的 Valaxy 博客,您只需要直接进行接下来的步骤即可。 - 在 Vercel 的 Dashboard 上,点击 `Add New...`,随后点击 `Project` 新建一个项目。 - 在左侧选择要部署的仓库,点击 `Import`,随后将 `Framework Preset` 设置为 `Other` 并更改 `Build and Output Settings`。 - 将 `Output Directory` 设置为 `dist` 后,点击 `Deploy`。 - 等待屏幕上撒下彩带后访问即可。 ::: details netlify.toml <<< @/../packages/create-valaxy/template-blog/netlify.toml ::: ### Cloudflare Pages {#cloudflare-pages} <BrandIcon icon="i-logos:cloudflare-icon" link="https://pages.cloudflare.com/" /> - 登录你的 [Cloudflare](https://www.cloudflare-cn.com/) 账号,从侧边栏导航至 “Workers 和 Pages” 页面。 - 点击 `创建项目`、`连接到 Git`,选择你的 GitHub 或者 GitLab 仓库,并点击 `开始设置`。 - 选择你的部署分支。 - 将 `构建命令` 设置为 `pnpm build` 。 - 将 `构建输出目录` 设置为 `dist` 。 - 点击 `保存并部署`。 ### Nginx {#nginx} > [Nginx Docs](https://nginx.org/en/docs/) 下面是一个 Nginx 服务器块配置示例 `nginx.conf`。此配置包括对基于文本的常见资源的 gzip 压缩、使用适当缓存头为 Valaxy 站点静态文件提供服务的规则以及处理 `cleanUrls: true` 的方法。 ::: details nginx.conf ```nginx [nginx.conf] server { gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; listen 80; server_name _; index index.html; location / { # content location # root /app; root /usr/share/nginx/html; # exact matches -> reverse clean urls -> folders -> not found try_files $uri $uri.html $uri/ =404; # non existent pages error_page 404 /404.html; # a folder without index.html raises 403 in this setup error_page 403 /404.html; # adjust caching headers # files in the assets folder have hashes filenames location ~* ^/assets/ { expires 1y; add_header Cache-Control "public, immutable"; } } } ``` ::: 本配置默认已构建的 Valaxy 站点位于服务器上的 `/usr/share/nginx/html` 目录中。如果站点文件位于其他位置,请相应调整 `root` 指令。 ### Docker {#docker} > [Docker Docs](https://docs.docker.com/) 下面是一个 Dockerfile 示例,用于构建 Valaxy 站点并将其部署到 Nginx 服务器中。 请参考 Nginx 部分配置 `nginx.conf`,并将其放置于 `Dockerfile` 同一目录下。 ::: details Dockerfile ```Dockerfile [Dockerfile] FROM node:22.12-alpine as build-stage WORKDIR /app RUN corepack enable COPY .npmrc package.json pnpm-lock.yaml ./ RUN --mount=type=cache,id=pnpm-store,target=/root/.pnpm-store \ pnpm install --frozen-lockfile COPY . . RUN pnpm build FROM nginx:stable-alpine as production-stage COPY nginx.conf /etc/nginx/nginx.conf COPY --from=build-stage /app/dist /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` ::: ### 其他 {#others} <BrandIcon class="text-xl!" icon="i-simple-icons-render" link="https://render.com/" /> 你还可以使用 [Render](https://render.com/) 等进行托管。 ::: tip Valaxy 与 VitePress 同样是静态站点。你也可以参考 [VitePress 部署指南](https://vitepress.dev/zh/guide/deploy) 进行部署。 ::: ## 亮点 - **Categories**: getting-started 首先,我们来介绍一下 Valaxy 有哪些便捷的特性。 ## 热更新 {#hot-reloading} 最值得一提的是,Valaxy 从配置到文章内容、动画到全局的标签、分类,全部都是支持热更新的!局部的! 譬如,你修改了 `valaxy.config.ts`/`site.config.ts` 或是 `xxx.md` 文章中的内容或 `frontmatter`(`tags`/`categories`)所有的变动会立刻显示在预览界面上,无需手动刷新。同时热更新也是局部的,它只变动有修改的地方,不会重新刷新整个页面。 ## 自定义 {#customization} 强大的自定义能力,你可以如忒修斯之船一样组件粒度地继承定制主题与你的博客。 更多请参见 [自定义组件](/zh/guide/custom/components)。 ## UnoCSS {#unocss} > 内置的类 TailwindCSS 的工具类(基于 [UnoCSS](https://github.com/unocss/unocss))。 如果你使用过 [TailwindCSS](https://tailwindcss.com/),那么一定能迅速领会到它的便捷之处。 你可以在你的 Markdown 和 Vue 组件中肆无忌惮地使用它,而且最终它会被按需打包并加载。 譬如: ```md 这是一份 Markdown 内容。 <div class="bg-white text-blue shadow" p="4"> 这是一份 Markdown 内容。 </div> ``` 你可以迅速得到这样的效果: <div class="bg-white text-blue shadow" p="4"> 这是一份 Markdown 内容。 </div> ## Icones {#icones} > 海量的图标 你可以任意使用 [Icones](https://icones.js.org/) 中可搜索到的任意图标。 命名规范为 `i-${collection}-${name}`,例如 `i-ri-home-line`。 主题默认安装了 [RemixIcon](https://github.com/Remix-Design/RemixIcon)。 如果你需要其他集合下的图标,可以自行安装。如: ```bash # collection 为对应的图标集名称,如 @iconify-json/ri npm i @iconify-json/collection ``` 被添加至 `config.unocss.safelist` 的图标名称将全部是热加载的! ## UI {#ui} ### 代码高亮 {#syntax-highlighting} > 更多关于代码高亮的信息请参见 [Markdown 代码高亮](/zh/guide/markdown#%25E4%25BB%25A3%25E7%25A0%2581%25E8%25A1%258C%25E9%25AB%2598%25E4%25BA%25AE)。 基于 [Shiki](https://shiki.style) 实现。 Valaxy 支持 `vue` 等语法高亮,拷贝代码,高亮其中某一行。 譬如: ```js {2} const a = 1 const b = a ``` ### 自定义主题色 {#custom-theme-color} 你只需传入一个主题色,全局各处的色彩会动态进行计算得出最终的效果。 譬如我希望主题色是红色: > `valaxy-theme-yun` 支持 ```ts [valaxy.config.ts] export default { themeConfig: { colors: { primary: 'red', }, }, } ``` 但不仅如此,其他主题同样可复用 Valaxy 默认提供的色彩及变量函数来快速构建自身。 > 更多请参见 [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-theme-yun) 代码。 ## 基于文件的自动路由 {#file-based-routing} 路由会自动遵循相同目录结构从当前路径中的 Vue/Markdown 文件生成。更多请参考 [`vue-router` 基于文件的路由](https://router.vuejs.org/file-based-routing/)。 ## 构建 {#building} 同时支持 SPA 与 SSG 两种方案。 ### SSG {#ssg} 基于 Valaxy 内置 SSG 引擎(Vue SSR + 纯字符串渲染,无 JSDOM)实现 ```bash # SSG npm run build:ssg # valaxy build --ssg ``` ### SPA {#spa} ```bash npm run build:spa # valaxy build ``` ## SEO {#seo} Valaxy 已经默认集成了 Open Graph 的 SEO 优化,您无需为此操心。 但需要注意的是,对于许多搜索引擎来说,他们可能只青睐 SSG 的构建模式。 ## RSS {#rss} 自带命令生成 RSS 订阅源。 > [RSS 是什么?](https://baike.baidu.com/item/rss/24470) 更多配置选项请参见 [RSS 配置](/zh/guide/config/extend#rss)。 ```bash npm run rss # valaxy rss ``` ## 单页 i18n {#i18n-in-one-page} 详情请见 [i18n](/zh/posts/i18n)。 ## Math | 数学公式 {#math-数学公式} Valaxy 支持两种数学渲染引擎:KaTeX(默认,渲染快)和 MathJax(SVG 输出,无需外部 CSS/字体)。 ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ // KaTeX (enabled by default) features: { katex: true }, // Or switch to MathJax (install first: pnpm add markdown-it-mathjax3) // math: true, }) ``` - [数学公式 | 示例](/zh/examples/math) - [通过 CDN 加载 KaTeX](/zh/guide/config/extend#cdn-externals) (实验性) ## 自动路由替换 {#auto-route-replacing} 当 Valaxy 检测到文章的 a 链接为站内链接时,会自动将其替换为 `RouterLink`,享受丝滑的动态切换吧! ## 开始 - **Categories**: getting-started ## 总览 {#overview} <span text-purple-600 font="bold">Valaxy</span> <span bg="$va-c-bg-soft" font="bold" px-2 py-1 rounded text-sm>= V + <span op="30">G</span>alaxy</span> 旨在成为下一代静态博客框架,提供更好的热更新与用户加载体验、更强大更便捷的自定义开发可能性。 你可以在 [为什么选 Valaxy](/zh/guide/why) 中了解更多关于项目的设计初衷。 ::: tip `Valaxy` 基于 [Vite](https://vitejs.dev/) 提供热更新与打包等功能,基于 [Vue](https://vuejs.org/) 实现视图(如主题、自定义组件)等客户端功能。 因此 Valaxy 兼容并可自由使用 Vite 与 Vue 生态的所有插件。 ::: ## 创建 Valaxy 项目 {#create-a-valaxy-project} > 示例: [yun.valaxy.site](https://yun.valaxy.site) ### 在线试用 {#try-it-online} 你可以通过 [StackBlitz](https://stackblitz.com/edit/valaxy) 在线试用 Valaxy(默认使用主题 [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/))。 [![StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/edit/valaxy) > 这是一个极简项目,您仅需以下几个文件,就可以快速搭建好你的博客! > > - `pages` 文件夹:存放页面/文章 > - `valaxy.config.ts` Valaxy 配置文件 > - `package.json` 记录依赖 ### 在本地创建 {#locally} ::: danger 兼容 Valaxy 要求 [Node.js](https://nodejs.org/en/) 的版本为 `>=22.12.0`。这是由 `unplugin-vue-markdown@32`(要求 Node `>=22`)与 Vite 8(要求 `^20.19.0 || >=22.12.0`)共同决定的——在 Node 22 分支上最低需要 `22.12.0`。请将 Node.js 升级至 `22.12.0` 或更高版本。 ::: ::: tip 如果您是 Windows 用户,我们**强烈建议**您使用类 Unix 的 Shell(如 [Git Bash](https://git-scm.com/downloads) 或 [WSL](https://docs.microsoft.com/en-us/windows/wsl/install)),而非 CMD / PowerShell. ::: 如果您想要在本地创建,只需要执行以下命令: > 由于 `npm init` 会缓存您此前下载的版本,我更推荐您使用 `pnpm` 来创建模版。 > [安装 pnpm](https://pnpm.io/installation) ::: code-group ```bash [pnpm] pnpm create valaxy ``` ```bash [npm] npm init valaxy ``` ::: ::: details You will be greeted with a few simple questions. <CreateValaxyTooltip /> ::: 跟随命令行提示完成创建! #### 选择主题 {#select-theme} 在选择 Blog 类型后,你将看到主题选择提示: - **Yun**(默认):轻盈简洁的博客主题 - **Press**:面向文档的主题 - **Custom**:输入自定义主题名(如 `starter` 或完整包名 `valaxy-theme-starter`) 选择主题后,`create-valaxy` 会自动配置 `valaxy.config.ts` 中的 `theme` 字段和 `package.json` 中的主题依赖。 > 默认使用主题 [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/),当然您也可以安装使用任意其他主题。 > 本文档同样是一个 Valaxy 主题 [valaxy-theme-press](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-press/),它的灵感来自 [VitePress](https://vitepress.dev/)。 ## 使用 {#usage} > 进入你创建好后的文件夹目录后,执行以下命令。 > 譬如:`cd valaxy-blog`。 安装依赖: ::: code-group ```bash [pnpm] # install pnpm i ``` ```bash [npm] # install npm i ``` ::: 启动预览: ::: code-group ```bash [pnpm] # start pnpm dev ``` ```bash [npm] # start npm run dev ``` ::: 博客创建完毕,查看本地 `http://localhost:4859/`,玩的开心! - Valaxy 博客通用的配置可参见 [配置](/zh/guide/config/) 与 [自定义扩展](/zh/guide/custom/extend)。 - Valaxy 主题独有配置请参见对应主题文档。(Valaxy Theme Yun 主题文档编写中……) ### 配置 {#config} 修改 `valaxy.config.ts` 来自定义你的博客吧。 基础配置可参见 [配置](/zh/guide/config/)。 文档正在不断完善中! ## 部署 {#deployment} 部署可参见 [部署|指南](/zh/guide/deploy)。 ## 升级 {#upgrading} ::: code-group ```bash [pnpm] cd your-blog # upgrade valaxy pnpm add valaxy@latest # upgrade theme pnpm add valaxy-theme-yun@latest ``` ```bash [npm] cd your-blog # upgrade valaxy npm i valaxy@latest # upgrade theme npm i valaxy-theme-yun@latest ``` ::: ### pnpm {#pnpm} > 你可以使用 pnpm 的交互升级命令。 ```bash # interactive upgrade pnpm up --latest -i ``` ## 迁移 {#migration} 如果你来自其他博客框架,可参考 [迁移](/zh/migration/)。 ## 目录结构 {#directory-structure} 在大部分情况下,你只需要在 `pages` 文件夹下进行工作,编写文章。 ### 主要的文件夹 {#main-folders} - `pages`: 你的所有页面 - `posts`: 写在 `pages/posts` 文件夹下的内容,将被当作博客文章 - `styles`: 覆盖主题样式,文件夹下的这些 scss 文件将会被自动加载 - `index.ts` / `index.scss` / `index.css` - `components`: 自定义你的组件(将会被自动注册) - `layouts`: 自定义布局 (譬如可以通过 `layout: xxx` 来使用 `layouts/xxx.vue` 布局) - `locales`: 自定义国际化关键词 ### 其他 {#others} - `.vscode`: 推荐安装一些有用的 VSCode 插件,这样你可以直接预览一些图标、国际化、辅助的 CSS Class 等 - 你可以在 VSCode 插件商店中找到 [`Valaxy` 插件](https://marketplace.visualstudio.com/items?itemName=yunyoujun.valaxy),它提供了文章列表预览/切换/删除等功能,让你尽可能地可以在 VSCode 中完成所有操作。 - `.github`: 使用 GitHub Actions 自动构建并部署到 GitHub Pages - `netlify.toml`: [Netlify](https://www.netlify.com/) 自动配置 - `vercel.json`: [Vercel](https://vercel.com/) 重定向配置 ## 主题 {#themes} 如果您希望自己开发一个主题并发布,您可以参考 [valaxy-theme-starter](https://github.com/YunYouJun/valaxy-theme-starter)。 更多内容请参见 [如何编写一个 Valaxy 主题](/zh/themes/write)。 ## 社区 {#community} 如果你有疑问或者需要帮助,可以到 [Discord](https://discord.gg/nd3mPkU5j8) 和 [GitHub Discussions](https://github.com/YunYouJun/valaxy/discussions) 社区来寻求帮助。 ## 国际化 - **Categories**: guide ## 设置支持的语言 {#set-supported-languages} ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ languages: ['zh-CN', 'en'], }) ``` ## 在配置中使用国际化 {#use-i18n-in-config} 如果你想要为 `siteConfig.title`/`siteConfig.description` 添加国际化支持,可以在 `siteConfig` 中设定键值。 ::: tip `$t` 是 Valaxy 提供的一个虚拟函数,它会添加特定的前缀 `$locale:` 以标记此处的文本需要国际化处理。 随后,Valaxy 会在页面中自动替换为对应语言的文本。 因此,它在页面上仍然是支持响应式的。 ::: 例如: ```ts [site.config.ts] import { $t, defineSiteConfig } from 'valaxy' export default defineSiteConfig({ title: $t('siteConfig.title'), description: $t('siteConfig.description'), }) ``` 然后在 `locales` 目录下创建对应的语言文件。 ```yaml [locales/zh-CN.yml] siteConfig: title: 你好,世界 ``` ```yaml [locales/en.yml] siteConfig: title: Hello World ``` ## 单页 i18n {#i18n-in-one-page} ::: tip Valaxy **提出**了一种基于 CSS 面向博客的 i18n 解决方案。 你可以在同一个页面中快速编写中英文博客。 > 如果你想了解实现原理,可参考 [i18n](/zh/posts/i18n)。 ::: **效果如下**(点击按钮切换): <ToggleLocaleDemo class="shadow p-2 rounded-full" bg="$va-c-brand" text="white" /> 另一种 i18n 方案。 > 更多内容:... 中文 --- **书写方式**如下: ```md 另一种 i18n 方案。 更多内容:... 中文 ``` ### 标题 i18n {#title-i18n} 当然,Valaxy 同样支持标题的 i18n。原理同上。 你可以采用如下方式书写: ```md ### 你好,世界 ``` ### Frontmatter i18n {#frontmatter-i18n} 实现 `title` 和 `description` 的国际化: ```md --- title: en: Hello World zh-CN: 你好,世界 description: en: A simple i18n example zh-CN: 一个简单的 i18n 示例 --- ``` ### 分类/标签 i18n {#categorytag-i18n} Valaxy 会自动在 locale 文件中查找 `tag.{tagName}` / `category.{categoryName}` 对应的翻译。 如果找到,则显示翻译后的文本;如果没有找到,则原样显示。 你只需要在 frontmatter 中写 tag/category 的 **key**,无需添加任何特殊前缀: ```md [posts/hello-world.md] --- categories: - test tags: - notes --- ``` 然后在 `locales` 目录中定义翻译: ```yaml [locales/zh-CN.yml] category: test: 测试 tag: notes: 笔记 ``` ```yaml [locales/en.yml] category: test: Test tag: notes: Notes ``` ::: tip 没有定义翻译的 tag/category 会原样显示。 例如 `valaxy` 这个 tag 如果没有在 locale 文件中定义 `tag.valaxy`,就会直接显示 `valaxy`。 ::: ::: details 旧版 `$locale:` 前缀方式(向后兼容) 旧版本需要在 frontmatter 中使用 `$locale:` 前缀: ```md --- tags: - $locale:tag.notes categories: - $locale:category.test --- ``` 该方式仍然兼容,但**推荐使用上述更简洁的方式**。 ::: #### 校验级别 {#validation-level} Valaxy 会在 `valaxy dev` / `valaxy build` 期间检查 taxonomy i18n。 你可以在 `valaxy.config.ts` 中通过三个级别控制它的行为: - `off`:跳过检查 - `warn`:输出 warning 并继续流程 - `error`:输出全部问题后,以错误结束 ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ build: { taxonomyI18n: { level: 'warn', }, }, }) ``` ## index ## 布局 - **Categories**: guide 框架 API 目前默认支持以下布局,布局支持与最终表现通常与主题有关。 - `post`:文章布局 - `tags`:标签布局 - `archives`:归档布局 - `categories`:分类布局 - `collections`:合集布局 ## 使用布局 {#使用布局} ### 合集布局 {#合集布局} 合集允许你将一系列相关文章(如小说、系列教程)组织为一个整体,并提供有序的导航。 #### 目录结构 {#目录结构} ```txt pages/ collections/ index.md # 合集总览页 hamster/ # 一个合集 index.ts # 合集配置(必需) index.md # 合集入口页 1.md # 文章 1 2.md # 文章 2 to-be-or-not.md # 使用字符串 key 的文章 ``` #### 1. 创建总览页 {#创建总览页} 新建 `pages/collections/index.md`,并指定布局为 `collections`: ```md [pages/collections/index.md] --- layout: collections icon: i-ri-gallery-view collections: - hamster - love-and-peace --- ``` #### 2. 创建合集 {#创建合集} 新建合集文件夹 `pages/collections/hamster/`,包含以下文件: - `index.ts`:合集配置文件(必需)。 - `index.md`:合集入口页。 - `1.md`、`2.md`、...:合集中的文章。 新建入口页 `pages/collections/hamster/index.md`: ```md [pages/collections/hamster/index.md] --- layout: collection --- ``` 在 `index.ts` 中定义合集配置: ```ts [pages/collections/hamster/index.ts] import { defineCollection } from 'valaxy' export default defineCollection({ key: 'hamster', title: '仓鼠', cover: 'https://cover.sli.dev', description: 'The story of I and She', items: [ { title: '第一章 仓鼠的笼子', key: '1' }, { title: '第二章 白昼之光,岂知夜色之深。', key: '2' }, { title: '第三章 作茧自缚', key: '3' }, ], }) ``` #### 3. 创建文章 {#创建文章} > `layout: collection` 可省略,`pages/collections/` 目录下的所有文章默认使用 `collection` 布局。 ```md [pages/collections/hamster/1.md] --- title: 第一章 仓鼠的笼子 --- 你的文章内容。 ``` 效果预览:[合集 | Valaxy Theme Yun](https://yun.valaxy.site/collections/hamster/1) ### CollectionConfig {#collection-config} | 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `key` | `string` | 目录名 | 唯一标识符。未指定时自动从目录名派生。 | | `title` | `string` | — | 合集显示标题。 | | `cover` | `string` | — | 封面图 URL。 | | `description` | `string` | — | 简短描述。 | | `categories` | `string[]` | — | 合集卡片的分类。 | | `tags` | `string[]` | — | 合集卡片的标签。 | | `collapse` | `boolean` | `true` | 是否在首页/归档文章列表中以单个折叠卡片展示。详见[折叠模式](#折叠模式)。 | | `items` | `{ title?, key?, link? }[]` | — | 有序文章列表。`key` 对应 `.md` 文件名(如 `key: '1'` → `1.md`)。`link` 引用已有页面或外部 URL。`key` 与 `link` 互斥,若同时设置,`link` 优先。决定文章阅读顺序和上下篇导航。 | ### 折叠模式 {#折叠模式} ::: tip `collapse` 为实验性功能,自 `v0.28.0` 起可用。 ::: 当 `collapse` 为 `true`(默认)时,合集在首页和归档文章列表中显示为**一张卡片**。由于合集文章位于 `/collections/` 路径下,它们不会单独出现在文章列表中——折叠卡片提供了进入合集的便捷入口。 ```ts export default defineCollection({ title: '我的系列', collapse: true, // 默认 — 显示为一张卡片 items: [/* ... */], }) ``` 当 `collapse` 为 `false` 时,不会在文章列表中添加合集条目。 ```ts export default defineCollection({ title: '我的系列', collapse: false, // 不在文章列表中显示卡片 items: [/* ... */], }) ``` ### 链接外部内容 {#链接外部内容} 你可以使用 `link` 字段在合集的阅读顺序中引用已有的博客文章或外部 URL。当合集包含不在合集目录中的内容时,这个功能非常有用。 - 内部链接(以 `/` 开头)通过 `<RouterLink>` 在站内导航。 - 外部链接(如 `https://...`)在新标签页中打开,并显示外部链接图标。 - `key` 与 `link` 互斥。若同时设置,`link` 优先。 ```ts export default defineCollection({ title: '我的学习路径', items: [ { title: '第一章 - 基础', key: '1' }, { title: '相关博文', link: '/posts/my-related-article' }, { title: '第二章 - 进阶', key: '2' }, { title: '外部参考', link: 'https://example.com/resource' }, ], }) ``` ## 实现布局(主题开发者) {#实现布局} [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-theme-yun) 自 `v0.25.9` 起支持 `collections` 布局。 按约定,主题需要在 `layouts` 目录下创建对应的布局文件,文件名与布局名称相同。 在主题中,你可以使用以下合集相关 API: - `useCollections()` — 获取所有合集配置。 - `useCollection()` — 获取当前合集(根据路由路径判断)。 - `useCollectionPosts(key)` — 获取指定合集的文章列表,按 `items` 定义的顺序排列。 - `usePostListWithCollections()` — 获取合并了折叠合集条目的文章列表。 <<< @/../packages/valaxy-theme-yun/layouts/collections.vue ## FAQ {#faq} ### 子页面发生了多层布局嵌套 {#child-pages-with-multiple-layout-nesting} Vue Router 的页面会自动嵌套父级布局,请参考 [Nested Routes | Unplugin Vue Router](https://uvr.esm.is/guide/file-based-routing#nested-routes)。 例如将: `pages/users/create.vue` 修改为 `pages/users.create.vue`。 ## Markdown 扩展 - **Categories**: guide ::: info 与 `Hexo` 不同,`Valaxy` 在框架层面实现了一些 Markdown 扩展(如 Container、数学公式)等,而无需主题开发者再次实现。 这与 `VitePress` 许多功能类似,`Valaxy` 从 `VitePress` 中借鉴了许多,并复用了 [mdit-vue](https://github.com/mdit-vue/mdit-vue) 的插件。 但也存在一些不同之处,Valaxy 默认使用 [KaTeX](https://katex.org/)(渲染速度快),同时也支持 [MathJax](https://www.mathjax.org/)(对齐 VitePress,SVG 输出无需外部 CSS/字体)。 > **注意**:`features.katex` 与 `math` 请勿同时开启,两者使用不同的渲染引擎,同时启用可能导致公式重复渲染或样式冲突。启用 `math`(MathJax)时,`features.katex` 会被自动忽略。 ```ts [valaxy.config.ts] export default defineValaxyConfig({ // KaTeX(默认开启) features: { katex: true }, // 或切换到 MathJax(需先安装:pnpm add markdown-it-mathjax3) // math: true, }) ``` 当然,你仍然可以在 Valaxy 中通过添加 MarkdownIt 插件来实现更多功能。 ::: ## 在 Markdown 中使用 Vue {#using-vue-in-markdown} 可以直接在 Markdown 文件中导入和使用 Vue 组件。 例如在 `components` 目录下创建一个 Vue 组件 `CustomVueDemo.vue`: <<< @/components/CustomVueDemo.vue [components/CustomVueDemo.vue] ```md [pages/posts/xxx.md] --- title: 在 Markdown 中使用 Vue --- <!-- 在 markdown 中直接使用即可: --> <CustomVueDemo /> ``` ## Emoji 表情支持 :tada: {#emoji-tada} **输入** ```md :tada: :100: ``` **输出** :tada: :100: 这是一个我们所 [支持的 Emoji 列表](https://github.com/markdown-it/markdown-it-emoji/blob/master/lib/data/full.mjs) 。 ## 目录 {#table-of-contents} **输入** ```md [[toc]] ``` **输出** [[toc]] 可以使用 `markdown.toc` 选项配置 TOC 的渲染。 ## 代码行高亮 {#line-of-code-highlighting} ````md ```js{4} export default { data () { return { msg: 'Highlighted!' } } } ``` ```` **输出** ```js{4} export default { data () { return { msg: 'Highlighted!' } } } ``` **输入** ````md ```ts {1} // line-numbers is disabled by default const line2 = 'This is line 2' const line3 = 'This is line 3' ``` ```ts:line-numbers {1} // line-numbers is enabled const line2 = 'This is line 2' const line3 = 'This is line 3' ``` ```ts:line-numbers=2 {1} // line-numbers is enabled and start from 2 const line3 = 'This is line 3' const line4 = 'This is line 4' ``` ```` **输出** ```ts {1} // line-numbers is disabled by default const line2 = 'This is line 2' const line3 = 'This is line 3' ``` ```ts:line-numbers {1} // line-numbers is enabled const line2 = 'This is line 2' const line3 = 'This is line 3' ``` ```ts:line-numbers=2 {1} // line-numbers is enabled and start from 2 const line3 = 'This is line 3' const line4 = 'This is line 4' ```` ## 代码块的增减色块标识 {#colored-diffs-in-code-blocks} 在一行上添加 `// [!code --]` 或者 `// [!code ++]` 注释将创建该行代码的增减标识,同时保持代码块的颜色。 **输入** 请注意,在 `!code`后面只需要一个空格,这里有两个空格以防被渲染。 ````md ```js export default { data () { return { msg: 'Removed' // [!!code --] msg: 'Added' // [!!code ++] } } } ``` ```` **输出** ```js export default { data() { return { msg: 'Removed', // [!code --] msg: 'Added', // [!code ++] } } } ``` ## 代码块中的错误和警告 {#errors-and-warnings-in-code-blocks} 在一行代码后中添加 `// [!code warning]` 或者 `// [!code error]` 注释将会使改行代码呈现指定颜色块。 **输入** 请注意,在 `!code`后面只需要一个空格,这里有两个空格以防被渲染。 ````md ```js export default { data () { return { msg: 'Error', // [!!code error] msg: 'Warning' // [!!code warning] } } } ``` ```` **输出** ```js export default { data() { return { msg: 'Error', // [!code error] msg: 'Warning' // [!code warning] } } } ``` ## 导入代码片段 {#import-code-snippets} 您可以通过以下语法从现有文件中导入代码片段: ```md <<< @/filepath ``` 它还支持 [行高亮](#line-of-code-highlighting): ```md <<< @/filepath{highlightLines} ``` **输入** ```md <<< @/snippets/snippet.js{2} ``` **代码文件** <<< @/snippets/snippet.js **输出** <<< @/snippets/snippet.js ::: tip `@` 的值与源根相对应。默认情况下是博客根目录,除非配置了 `srcDir` 。另外,你也可以从相对路径导入: ```md <<< ../snippets/snippet.js ``` ::: 您也可以使用 [VS Code region](https://code.visualstudio.com/docs/editor/codebasics#_folding) 只包含代码文件的相应部分。您可以在文件路径后的 `#` 后提供自定义区域名称: **输入** ```md <<< @/snippets/snippet-with-region.js#snippet{1} ``` **代码文件** <<< @/snippets/snippet-with-region.js **输出** <<< @/snippets/snippet-with-region.js#snippet{1} 您也可以像这样在大括号(`{}`)内指定语言: ```md <<< @/snippets/snippet.cs{c#} <!-- with line highlighting: --> <<< @/snippets/snippet.cs{1,2,4-6 c#} <!-- with line numbers: --> <<< @/snippets/snippet.cs{1,2,4-6 c#:line-numbers} ``` ## 代码分组 {#code-groups} 您可以像这样对多个代码块进行分组: **输入** ````md ::: code-group ```js [config.js] /** * @type {import('valaxy').UserConfig} */ const config = { // ... } export default config ``` ```ts [config.ts] import type { UserConfig } from 'valaxy' const config: UserConfig = { // ... } export default config ``` ::: ```` **输出** ::: code-group ```js [config.js] /** * @type {import('valaxy').UserConfig} */ const config = { // ... } export default config ``` ```ts [config.ts] import type { UserConfig } from 'valaxy' const config: UserConfig = { // ... } export default config ``` ::: 你也可以在代码组中 [导入代码片段](#import-code-snippets) 。 **输入** ```md ::: code-group <!-- filename is used as title by default --> <<< @/snippets/snippet.js <!-- you can provide a custom one too --> <<< @/snippets/snippet-with-region.js#snippet{1,2 ts:line-numbers} [snippet with region] ::: ``` **输出** ::: code-group <<< @/snippets/snippet.js <<< @/snippets/snippet-with-region.js#snippet{1,2 ts:line-numbers} [snippet with region] ::: ## 容器 {#container} 通过对 `markdownIt` 进行配置,你可以自由设置自定义块区域的文字以及图标及图标的颜色。 ::: tip tip ::: ::: warning warning ::: ::: danger danger ::: ::: info info ::: ```md ::: ::: tip tip ::: ::: warning warning ::: ::: danger danger ::: ::: info info ::: ``` ::: details Click to expand Details Content ::: ```md ::: details Click to expand Details Content ::: ``` 你也可以自定义新的容器名称。 ```md ::: custom I am a custom block. ::: ``` ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ markdown: { blocks: { custom: { icon: 'i-ri:info-i', text: 'CUSTOM', }, } } }) ``` ## 添加代码块标题与图标 {#add-code-block-title-and-icons} ::: tip 有关更多代码块图标的示例可以在 [此处](/zh/examples/code-block-icons) 找到。 ::: 它基于 [vitepress-plugin-group-icons](https://github.com/yuyinws/vitepress-plugin-group-icons) 实现,内置了一些[常用图标](https://vp.yuy1n.io/features.html#built-in-icons),你可以如下自定义更多图标。 ```ts [valaxy.config.ts] {5-14} import { defineValaxyConfig } from 'valaxy' import { localIconLoader } from 'vitepress-plugin-group-icons' export default defineValaxyConfig({ groupIcons: { customIcon: { // valaxy: 'https://valaxy.site/favicon.svg', valaxy: localIconLoader(import.meta.url, './public/favicon.svg'), nodejs: 'vscode-icons:file-type-node', playwright: 'vscode-icons:file-type-playwright', typedoc: 'vscode-icons:file-type-typedoc', eslint: 'vscode-icons:file-type-eslint', dockerfile: 'vscode-icons:file-type-docker', }, } }) ``` 此时,使用以下语法: ````md ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({}) ``` ```dockerfile [sample.dockerfile] FROM ubuntu ENV PATH /opt/conda/bin:$PATH ``` ```` 我们将会得到带有 `valaxy.config.ts` 标题与 Valaxy 图标的代码块: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({}) ``` 还会得到带有 `sample.dockerfile` 标题与 Docker 图标的代码块: ```dockerfile [sample.dockerfile] FROM ubuntu ENV PATH /opt/conda/bin:$PATH ``` ## 数学公式 {#math-formulas} ::: tip 有关更多数学公式的信息可以在 [此处](/zh/examples/math) 找到。 ::: **输入** ```md When $a \ne 0$, there are two solutions to $(ax^2 + bx + c = 0)$ and they are $$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$ **Maxwell's equations:** | equation | description | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | $\nabla \cdot \vec{\mathbf{B}} = 0$ | divergence of $\vec{\mathbf{B}}$ is zero | | $\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}}$ | curl of $\vec{\mathbf{E}}$ is proportional to the rate of change of $\vec{\mathbf{B}}$ | | $\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho$ | _wha?_ | ``` **输出** 当 $a \ne 0$时,$(ax^2 + bx + c = 0)$ 有两个解,他们是 $$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$ **麦克斯韦方程:** | equation | description | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | $\nabla \cdot \vec{\mathbf{B}} = 0$ | divergence of $\vec{\mathbf{B}}$ is zero | | $\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}}$ | curl of $\vec{\mathbf{E}}$ is proportional to the rate of change of $\vec{\mathbf{B}}$ | | $\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho$ | _wha?_ | ### 自定义 KaTeX 选项 {#configuration} > [KaTeX选项](https://katex.org/docs/options.html) ```ts [valaxy.config.ts] export default defineValaxyConfig({ markdown: { /** * KaTeX options * @see https://katex.org/docs/options.html */ katex: { strict: false } } }) ``` ## 包含 MarkDown 文件<!-- --> {#markdown-file-inclusion} ::: tip You can also prefix the markdown path with `@`, it will act as the source root. By default, it's the Valaxy project root. ::: **输入** ```md [your-file.md] ## Docs <!--@include: @/TEST.md--> <!--@include: ./parts/basics.md--> ``` **部分文件** ::: code-group ```md [parts/basics.md] Some getting started stuff. ### Configuration Can be created using `.foorc.json`. ``` ```md [TEST.md] I'm a TEST. ``` ::: **等效代码** ```md ## Docs I'm a TEST. Some getting started stuff. ### Configuration Can be created using `.foorc.json`. ``` 它还支持选择行范围: **输入** ```md ## Docs <!--@include: @/TEST.md--> <!--@include: ./parts/basics.md{3,}--> ``` **部分文件** ::: code-group ```md [parts/basics.md] Some getting started stuff. ### Configuration Can be created using `.foorc.json`. ``` ```md [TEST.md] I'm a TEST. ``` ::: **等效代码** ```md ## Docs I'm a TEST. ### Configuration Can be created using `.foorc.json`. ``` 所选行范围的格式可以是: `{3,}`, `{,10}`, `{1,10}` ::: warning 请注意,如果文件不存在,该功能不会出错。因此,在使用此功能时,请确保内容已按预期渲染。 ::: ## UnoCSS 我们集成了 [UnoCSS](https://unocss.dev),因此您可以在 Markdown 文件中直接使用它。 自由控制你的布局! > 更多配置见 [UnoCSS Options | 配置](/zh/guide/config/unocss-options)。 <div class="flex flex-col"> <div class="flex grid-cols-3" gap="2"> <div> ![image](https://www.yunyoujun.cn/images/avatar.jpg) </div> <div> ![image](https://www.yunyoujun.cn/images/avatar.jpg) </div> <div> ![image](https://www.yunyoujun.cn/images/avatar.jpg) </div> </div> <div class="flex grid-cols-2 justify-center items-center" gap="2"> ![image](https://cdn.yunyoujun.cn/img/bg/stars-timing-1.jpg) ![image](https://cdn.yunyoujun.cn/img/bg/astronaut.webp) </div> </div> ```html [pages/posts/your-post.md] <div class="flex flex-col"> <div class="flex grid-cols-3"> <div> ![image](https://www.yunyoujun.cn/images/avatar.jpg) </div> <div> ![image](https://www.yunyoujun.cn/images/avatar.jpg) </div> <div> ![image](https://www.yunyoujun.cn/images/avatar.jpg) </div> </div> <div class="flex grid-cols-2 justify-center items-center"> ![image](https://cdn.yunyoujun.cn/img/bg/stars-timing-1.jpg) ![image](https://cdn.yunyoujun.cn/img/bg/astronaut.webp) </div> </div> ``` ## Mermaid Based on [mermaid](https://mermaid.js.org/), you can use it in your markdown file directly. ```mermaid graph TD; A-->B; A-->C; B-->D; C-->D; ``` ````txt ```mermaid graph TD; A-->B; A-->C; B-->D; C-->D; ``` ```` More examples see: [Mermaid](/zh/examples/mermaid) ### PlantUML PlantUML 未内置于核心,因为它需要外部服务器渲染。你可以通过 `markdown.transforms` 自行配置: ```ts [valaxy.config.ts] import { Buffer } from 'node:buffer' import { defineValaxyConfig } from 'valaxy' const PLANTUML_SERVER = 'https://www.plantuml.com/plantuml' export default defineValaxyConfig({ markdown: { transforms: { before(code) { return code.replace( /^```plantuml\n([\s\S]+?)\n```/gm, (_, uml: string) => { const encoded = Buffer.from(uml.trim()).toString('hex') return `<img src="${PLANTUML_SERVER}/svg/~h${encoded}" loading="lazy" alt="PlantUML diagram">` }, ) }, }, }, }) ``` 然后在 Markdown 中使用: ````txt ```plantuml Alice -> Bob: Hello Bob --> Alice: Hi! ``` ```` ::: tip 默认使用 [PlantUML 官方服务器](https://www.plantuml.com/plantuml),你可以将 `PLANTUML_SERVER` 替换为自己的服务器地址。 大多数场景下,推荐使用 [Mermaid](#mermaid),它开箱即用,无需任何外部依赖。 ::: ## 脚注 你可以使用 `[^1]` 或 `[^footnote]` 来添加脚注,例如: ```md 这是一个脚注[^1-zh]。 这是一段脚注[^2-zh]。 [^1-zh]: 这是一个脚注。 [^2-zh]: 这是一段脚注。 正确缩进的脚注段落会被自动附加。 使用 `^[content]` 可以创建方便的内联脚注^[比如这个!]。 ``` 这是一个脚注[^1-zh]。 这是一段脚注[^2-zh]。 [^1-zh]: 这是一个脚注。 [^2-zh]: 这是一段脚注。 正确缩进的脚注段落会被自动附加。 使用 `^[content]` 可以创建方便的内联脚注^[比如这个!]。 ### 脚注预览 借助 [`Floating Vue`](https://floating-vue.starpad.dev/), 添加的脚注链接在鼠标悬停时会显示脚注内容。你可以在本页面的脚注链接上试一试! 如果你想要自定义脚注的样式,可以参考 [Floating Vue 文档](https://floating-vue.starpad.dev/guide/config) 中的 `config` 设置 `site.config.ts` 中的 `floatingVue`,你也可以修改组件 `ValaxyFootnoteTooltip` 来达到这一点。 ## 自定义 ### 自定义 Markdown 容器 Class 你可以在 Markdown 文件的 frontmatter 中添加 `markdownClass` 来自定义 Markdown 容器的 Class。 ```md --- markdownClass: 'markdown-body custom-markdown-class' --- ``` ## 页面 - **Categories**: guide ## FrontMatter {#frontmatter} 你可以使用 front-matter 定制页面属性。 ::: tip 更多配置项可参见: - 页面(Page)配置:[PageFrontmatter](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/types/frontmatter/page.ts) ::: details PageFrontmatter Types <<< @/../packages/valaxy/types/frontmatter/page.ts#snippet{29-194 ts:line-numbers} ::: ### titleTemplate {#titletemplate} ```md --- title: Cool titleTemplate: '%s - Valaxy' --- ``` 这样可以使 HTML 标题变为 `Cool - Valaxy`。 ### 页面加密 {#encrypt-page} ::: warning 加密依赖于浏览器原生 [Web Crypto API | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API), **其仅在 HTTPS 中可用**。 ::: ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ encrypt: { // 开启加密,默认关闭 enable: true // algorithm // iv // salt } }) ``` 在对应需要加密页面的 frontmatter 中添加 `password: YourPassword` 即可。 当 `encrypt.enable` 为 `true`,且页面中密码 `password` 存在时,默认开启加密。 被加密的内容应在解密后动态渲染。此时无法(也不应)参与到构建流程中生成静态产物(否则会被直接看到)。 因此对于加密内容中的图片路径,总是应该使用绝对路径而非相对路径。 ```md --- password: valaxy --- ``` ### 其它 {#other} - `sidebar: false`: 隐藏左侧文章导航栏 - `aside: false`: 隐藏右侧文章导航栏 - `toc: false`: 隐藏目录 - `codeHeightLimit: 300`: 代码块高度限制(300px) ## 文章 - **Categories**: guide > [Post VS Page](https://wordpress.com/zh-cn/support/post-vs-page/) ## FrontMatter {#frontmatter} ::: tip 更多配置项可参见: - 文章(Post)配置:[PostFrontmatter](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/types/frontmatter/post.ts) (文章配置包含页面配置) - 页面(Page)配置:[PageFrontmatter](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/types/frontmatter/page.ts) (可参见[页面配置 | Valaxy](/zh/guide/page)) ::: details PostFrontmatter Types <<< @/../packages/valaxy/types/frontmatter/post.ts#snippet{ts:line-numbers} ::: **文章**(`post`)继承自**页面**(`page`),因此**页面**中的 Front Matter 通用被**文章**支持。 > 单篇文章支持的配置项。 譬如: ```md --- title: Title hide: true --- ``` - `title`: 文章标题 - `hide`: 你可以在文章头部添加 hide 属性,来临时隐藏某篇文章。(该文章仍然会被渲染) - `true` / `all`: 当设置为 `true` 或 `all` 时,该文章仍然会被渲染,你可以直接访问链接进行查看。但不会被显示在展示的文章卡片与归档中。 - `index`: 设置为 `index` 时,将只在首页隐藏,归档中仍然展示。(譬如放一些没有必要放在首页的笔记,并在归档中方便自己查看。) ## 摘要 {#excerpt} 你可以通过插入 `<!-- more -->` 的方式生成摘要(excerpt)。 可通过设置 `excerpt_type` 设置摘要渲染类型。 - `excerpt`: 自定义摘要(优先级高于 `<!-- more -->`) - `excerpt_type`: 预览列表**摘要**的渲染类型(与 `<!-- more -->` 配合使用) - `md`: 展示原始 Markdown - `html`: 以 HTML 形式展示 - `text`: 以纯文本形式展示(去除 HTML 标签) ::: code-group ```md{3,10} [excerpt_type: text] --- title: 'excerpt_type: text' excerpt_type: text --- ## Header ![yun-bg](https://cdn.yunyoujun.cn/img/bg/stars-timing-0-blur-30px.jpg) <!-- more --> Main Content ``` ```md{3,10} [excerpt_type: md] --- title: 'excerpt_type: md' excerpt_type: md --- ## Header ![yun-bg](https://cdn.yunyoujun.cn/img/bg/stars-timing-0-blur-30px.jpg) <!-- more --> Main Content ``` ```md{3,10} [excerpt_type: html] --- title: 'excerpt_type: html' excerpt_type: html --- ## Header ![yun-bg](https://cdn.yunyoujun.cn/img/bg/stars-timing-0-blur-30px.jpg) <!-- more --> Main Content ``` ```md{3} [custom excerpt] --- title: 'custom excerpt' excerpt: This is a custom excerpt. --- ## Header ![yun-bg](https://cdn.yunyoujun.cn/img/bg/stars-timing-0-blur-30px.jpg) Main Content ``` ::: You will get excerpt: ::: code-group ```md [excerpt_type: text] HEADER yun-bg ``` ```md [excerpt_type: md] ## Header ![yun-bg](https://cdn.yunyoujun.cn/img/bg/stars-timing-0-blur-30px.jpg) ``` ```md [excerpt_type: html] <!-- Rendered HTML --> ``` ```md [custom excerpt] This is a custom excerpt. ``` ::: ## 插入 {#insert} ### 组件 {#components} - 如想在文章中插入现有公共组件,请参照 [组件](/zh/guide/built-ins)。 - 如想在文章中插入自定义组件,请参照 [自定义组件](/zh/guide/custom/components)。 ### 脚本 {#scripts} 可直接通过 [`useScriptTag`](https://vueuse.org/core/useScriptTag/) 使用,封装为组件或直接添加在文章中。 ```vue <script lang="ts" setup> useScriptTag('https://static.codepen.io/assets/embed/ei.js') </script> ``` ## 强制规范 {#force-standard} 由于 Valaxy 支持解析 Vue 组件渲染,因此当您输入 `<CustomComponent></CustomComponent>` 时,它会解析 `components` 目录下的 `CustomComponent.vue` 组件并渲染。 当您不需要其被渲染时,请务必使用反引号包裹,如: ```md `<CustomComponent></CustomComponent>` ``` ## SSR 兼容性 - **Categories**: guide ## SSR 兼容性 Valaxy 使用 SSG(静态站点生成)构建你的站点,它在构建时通过 Vue 的服务端渲染(SSR)将页面渲染为 HTML。这意味着组件在构建期间运行在 Node.js 环境中,此时像 `window`、`document` 和 `navigator` 这样的浏览器 API 是不可用的。 ::: warning 从旧版 `vite-ssg` 引擎升级 Valaxy 曾内置基于 JSDOM 的 `vite-ssg` 引擎,已在 **v1.0 中移除**(见 [#706](https://github.com/YunYouJun/valaxy/issues/706))。JSDOM 在 SSR 期间默默提供了 `window`、`document` 和 `navigator`,因此那些在渲染时访问这些全局对象的代码看似"能正常工作"。而 Valaxy SSG 引擎以纯字符串渲染、**没有** DOM,因此同样的代码现在会抛错或导致水合错误。 如果你正在升级,且某个主题或 addon 在 SSR 期间依赖 DOM,请用下文的方式守护所有仅浏览器的访问。 ::: ### 为什么会发生水合不匹配 在 SSG 生成静态 HTML 后,Vue 会在浏览器中"水合"它——附加事件监听器并使其具有交互性。如果服务器渲染的 HTML 与客户端渲染的内容不同,你会收到**水合不匹配**警告。 常见原因: | 原因 | 示例 | |-------|---------| | 模板中使用仅浏览器 API | `{{ window.innerWidth }}` | | 时间/地区相关的值 | `{{ new Date().toLocaleString() }}` | | 浏览器扩展修改 HTML | 广告拦截器注入元素 | | 非标准 HTML 嵌套 | `<p>` 嵌套在 `<p>` 中,`<div>` 在 `<a>` 中 | ### `<ClientOnly>` 使用内置的 `<ClientOnly>` 组件包裹仅浏览器的内容。它的内容只在客户端渲染。 ```vue <template> <ClientOnly> <BrowserOnlyComponent /> </ClientOnly> </template> ``` 使用 `#fallback` 插槽在 SSR/SSG 期间显示占位内容: ```vue <template> <ClientOnly> <HeavyChart :data="chartData" /> <template #fallback> <div class="chart-placeholder"> 加载图表中... </div> </template> </ClientOnly> </template> ``` ### `defineClientComponent` 对于在导入时(而不仅仅是渲染时)访问浏览器 API 的第三方库,使用 `defineClientComponent`。它会延迟 `import()` 直到组件在浏览器中挂载。 ```vue <script setup> import { defineClientComponent } from 'valaxy' const MyBrowserLib = defineClientComponent( () => import('some-browser-only-lib') ) </script> <template> <MyBrowserLib /> </template> ``` 你可以传递 props 和回调函数: ```vue <script setup> import { defineClientComponent } from 'valaxy' const EchartsChart = defineClientComponent( () => import('vue-echarts'), [ { option: chartOption, autoresize: true }, // props { default: () => h('div', '加载中...') }, // children/slots ], (mod) => { // 模块加载后调用 console.log('vue-echarts 已加载', mod) }, ) </script> <template> <EchartsChart /> </template> ``` ### `onMounted` + `ref` 模式 对于只需要在逻辑中(而不是第三方导入中)使用浏览器 API 的简单情况,使用 Vue 的 `onMounted`: ```vue <script setup> import { onMounted, ref } from 'vue' const screenWidth = ref(0) onMounted(() => { screenWidth.value = window.innerWidth }) </script> <template> <p>屏幕宽度:{{ screenWidth }}</p> </template> ``` ### `import.meta.env.SSR` 使用 `import.meta.env.SSR` 标志(由 Vite 提供)来有条件地执行代码: ```ts if (!import.meta.env.SSR) { // 这段代码只在浏览器中运行 document.addEventListener('scroll', handleScroll) } ``` > 这在 composables 或 setup 函数中保护仅浏览器的副作用时很有用。 ### 基于 CSS 的响应式渲染 避免在响应式布局中使用 `v-if` 配合响应式视口值——这会导致水合不匹配,因为服务器无法知道视口大小。改用 CSS: ```vue <!-- 不好:会导致水合不匹配 --> <template> <MobileNav v-if="isMobile" /> <DesktopNav v-else /> </template> <!-- 好:使用 CSS 媒体查询 --> <template> <MobileNav class="mobile-only" /> <DesktopNav class="desktop-only" /> </template> <style> .mobile-only { display: block; } .desktop-only { display: none; } @media (min-width: 768px) { .mobile-only { display: none; } .desktop-only { display: block; } } </style> ``` ### 主题和插件开发者提示 - 始终使用 `pnpm demo:build`(SSG 构建)进行测试——`pnpm demo`(开发模式)不会捕获 SSR 问题。 - 使用 `<ClientOnly>` 或 `defineClientComponent` 包裹所有仅浏览器的第三方组件。 - 永远不要在 `<script setup>` 块的顶层访问 `window`、`document` 或 `navigator`——将其移到 `onMounted` 中。 - 如果库提供服务端安全的构建版本(例如 `import lib from 'lib/dist/ssr'`),优先使用它而不是用 `<ClientOnly>` 包裹。 - 在 composables 中使用 `import.meta.env.SSR` 来处理条件副作用。 ## 与 AI 协作 - **Categories**: guide ## Agent Skills {#agent-skills} ::: tip 🧪 实验性:Valaxy Skills 目前为实验性功能,正在积极开发中,欢迎反馈。 ::: [Valaxy Skills](https://github.com/YunYouJun/valaxy/tree/main/skills) 是由 Valaxy 团队维护的 AI Agent Skills。 安装 Skill 后,当你使用 AI Agent 来辅助开发 Valaxy 站点时,它可以自动利用 Valaxy 提供的丰富功能集。 这使得 Agent 能够准确使用 Valaxy 的配置、主题、插件等功能。 ### 安装 {#installation} ```bash npx skills add YunYouJun/valaxy ``` ### 使用 {#usage} #### 使用 Agent 开发 Valaxy 站点 {#using-an-agent-to-develop-valaxy-sites} 示例提示词: ```txt 创建一个 Valaxy 博客站点: - 使用 valaxy-theme-yun 主题 - 配置 Algolia 搜索 - 添加 Waline 评论插件 - 启用 llms.txt 输出 - 自定义导航与侧边栏 ``` Agent 将自动引用 Valaxy Skills 中的知识来正确配置 `site.config.ts` 和 `valaxy.config.ts`,使用合适的 API(如 `defineSiteConfig`、`defineValaxyConfig`),并遵循 Valaxy 的最佳实践。 #### 使用 Agent 开发 Valaxy 主题 {#using-an-agent-to-develop-a-valaxy-theme} 使用 [AI 主题提示词生成器](/zh/themes/write#generate-a-theme-with-ai)描述主题,并将包含版本核对要求的实现清单复制到编程助手中。生成的提示词覆盖最小主题结构、配置、样式、组件约定和验证步骤,同时不会假定当前版本不存在的 API。 ## CLAUDE.md {#claudemd} Valaxy 仓库内置了 [CLAUDE.md](https://github.com/YunYouJun/valaxy/blob/main/CLAUDE.md) 文件,用于为 [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview) 等 AI 工具提供项目上下文。 该文件包含: - 项目架构概览(Monorepo 结构、核心包结构) - 常用命令(开发、构建、测试、Lint) - 配置流程(Config Merging、Roots System、Virtual Modules) - 主题与插件开发指南 - 测试策略与部署方式 如果你使用 Claude Code 或其他支持 `CLAUDE.md` 的 AI 工具来开发 Valaxy,它将自动读取该文件以获得更准确的上下文理解。 ## llms.txt {#llmstxt} Valaxy 内置了 [llms.txt](https://llmstxt.org/) 支持,可以为你的博客生成 AI 可读的纯文本内容。 在 `site.config.ts` 中启用: ```ts import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ llms: { enable: true, }, }) ``` 启用后,Valaxy 将自动生成: - `/llms.txt` — 站点索引,包含所有文章的标题、描述和链接 - `/llms-full.txt` — 完整内容,包含所有文章的全文(可通过 `fullText: false` 关闭) - `/posts/xxx.md` — 每篇文章的原始 Markdown 文件(可通过 `files: false` 关闭) ### 配置选项 {#configuration-options} ```ts export default defineSiteConfig({ llms: { enable: true, // 是否生成 llms-full.txt(默认 true) fullText: true, // 是否为每篇文章生成独立的 .md 文件(默认 true) files: true, // 自定义提示词(添加到 llms.txt 的引用块部分) prompt: '', // 要包含的文件 glob 模式(相对于 pages/ 目录,默认 ['posts/**/*.md']) include: ['posts/**/*.md'], }, }) ``` ### CLI 命令 {#cli-command} 你也可以单独生成 llms.txt 相关文件: ```bash npx valaxy llms ``` ## 从其他博客框架迁移 - **Categories**: migration ## AI 迁移助手 {#ai-migration-assistant} 选择你的原始框架,复制提示词到 AI 助手中,快速完成迁移。 <MigrationPrompt /> ## 从 Hexo 迁移至 Valaxy {#migrate-from-hexo-to-valaxy} ### 迁移内容 {#migrate-contents} Hexo 博客目录与 Valaxy 博客目录对应关系如下,将相关内容复制至对应文件夹即可。 > 譬如**迁移文章**,即将 Hexo `source/_posts` 目录下内容复制至 Valaxy `pages/posts` 目录下。 |用途|Hexo|Valaxy| |---|---|---| |文章(Markdown 文件)|`source/_posts`|`pages/posts`| |页面(Markdown / Html)|`source`|`pages`| |静态资源(`*.js` / `*.css` / `CNAME` etc.)|`source`|`public`| ### 迁移配置 {#migrate-configurations} 参考 [Valaxy 配置文档](/zh/guide/config/) 将 Hexo `_config.yml` 配置文件中的内容,迁移至 `valaxy.config.ts` 文件中。 > 配置示例:[demo/yun/valaxy.config.ts](https://github.com/YunYouJun/valaxy/blob/main/demo/yun/valaxy.config.ts)、[yunyoujun.github.io/valaxy.config.ts](https://github.com/YunYouJun/yunyoujun.github.io/blob/valaxy/valaxy.config.ts) > `valaxy.config.ts` 提供了完备的类型提示,这意味着你在 VSCode 中可以直接鼠标悬浮查看各参数注释。 ### 示例 {#example} 更复杂的迁移示例,您还可以对比 [yunyoujun.github.io | GitHub](https://github.com/YunYouJun/yunyoujun.github.io) 仓库 [hexo](https://github.com/YunYouJun/yunyoujun.github.io/tree/hexo) 分支与 [valaxy](https://github.com/YunYouJun/yunyoujun.github.io/tree/valaxy) 的异同。 ## 从其他任意博客框架迁移 {#migrate-from-any-other-blog-framework} - 将你的文章(Markdown 文件)复制至 Valaxy `pages/posts` 目录下。 - 将你的自定义页面(非文章的 Markdown/HTML 文件)复制至 Valaxy `pages` 目录下。 - 将你的静态资源(图片等)复制至 Valaxy `public` 目录下。 - 参考 [配置](/zh/guide/config/) 配置你的配置文件 `valaxy.config.ts`/`site.config.ts`。 ## 常见问题 {#common-problems} ### 摘要截断符 {#read-more-separator} 默认为 `<!-- more -->`,`more` 前后需有空格。 ### Markdown 换行 {#newline-in-markdown} Valaxy 的 Markdown 解析基于 [`markdown-it`](https://github.com/markdown-it/markdown-it) 实现。 #### 没有换行 `markdown-it` 的策略在 Markdown 中换行后渲染的内容并没有换行: ```md 第一行 没有换行 ``` 第一行 没有换行 --- #### 换行了 如果需要正常换行,需在末尾添加两个空格: ```md 第一行(末尾有两个空格) 换行了 ``` 第一行(末尾有两个空格) 换行了 ## 版本迁移 - **Categories**: migration ## v1.0.0 {#v100} v1.0.0 是首个稳定版本,包含若干破坏性变更,主要是移除长期已弃用的选项。大多数博客无需改动;若你使用过下列功能,请对照检查。 ### Node.js:最低版本升至 `>=22.12.0` {#nodejs-version} Valaxy 现在要求 Node.js `>=22.12.0`(此前为 `^18 || >=20`)。这是由 `unplugin-vue-markdown@32`(要求 Node `>=22`)与 Vite 8(`^20.19.0 || >=22.12.0`)共同决定的——在 Node 22 分支上最低需要 `22.12.0`。**不再支持 Node 18 与 20。** 更新前请先升级本地、CI 与部署环境的 Node 版本(见 [#710](https://github.com/YunYouJun/valaxy/pull/710))。 ### SSG:移除传统 `vite-ssg` 引擎 {#ssg-remove-vite-ssg} 基于 JSDOM 的 `vite-ssg` SSG 引擎已被移除(它在 pnpm 下损坏,详见 [#706](https://github.com/YunYouJun/valaxy/issues/706))。现在只有单一的内置 Valaxy SSG 引擎。 - `--ssg-engine` 命令行参数与 `build.ssg.engine` 配置项均已移除——直接运行 `valaxy build --ssg` 即可。 - `vite.ssgOptions` 仍然支持,但形状改为 `ValaxySSGOptions`:`concurrency`、`includedRoutes`、`includeAllRoutes`、`onBeforePageRender`、`onPageRendered`、`onFinished`。`vite-ssg` 专属选项(`dirStyle`、`beastiesOptions`、`formatting`、`script`)不再存在。 - **Critical CSS 内联(beasties)已移除。** 首屏无样式闪烁改由 FOUC guard 处理(`build.foucGuard`)。 - 如需目录式输出(`/foo/index.html`),请用目录索引页(`pages/foo/index.md`)替代旧的 `dirStyle: 'nested'` 选项。 ### 音乐播放器迁移至 `valaxy-addon-meting` {#music-player-addon} 内置的 `aplayer: true` frontmatter 开关不再加载音乐播放器。请安装并启用该插件: ```ts // valaxy.config.ts import { addonMeting } from 'valaxy-addon-meting' export default defineValaxyConfig({ addons: [addonMeting()], }) ``` Markdown 中 `<meting-js>` 的用法不变。参见 [音乐播放器](/zh/guide/third-party#music-player)。 ### 配置与 frontmatter 移除 {#config-frontmatter-removals} - **顶层 `ignoreDeadLinks`** → 改用 `build.ignoreDeadLinks`。 - **`unocssPresets.uno`** → 改用 `unocssPresets.wind4`(自 wind3→wind4 迁移后它早已失效)。 - **frontmatter `color`**(标题颜色)已从核心类型移除。它属于主题范畴——`valaxy-theme-yun` 运行时仍读取它;建议改用 `pageTitleClass` / `postTitleClass`。 ### SSR 全局对象 {#ssr-globals} 如果某个主题或插件依赖旧引擎的 JSDOM(它在 SSR 期间默默提供 `window` / `document` / `navigator`),请守护这些访问——当前引擎以纯字符串渲染、没有 DOM。参见 [SSR 兼容性](/zh/guide/ssr-compat)。 ## v0.21.0 {#v0210} ### 自行引入公共样式 {#自行引入公共样式} 主题开发者需要自行引入公共样式 `valaxy/client/styles/common/index.scss`。 参见 [引入默认样式](/zh/themes/write#引入默认样式)。 ## Tags ## Valaxy 主题橱窗 - **Categories**: theme 浏览 Valaxy 官方与社区主题。 浏览 Valaxy 官方与社区主题,在线预览效果,或直接前往对应的文档与代码仓库。 欢迎将你的作品[提交到主题橱窗](https://github.com/YunYouJun/valaxy/edit/main/docs/data/themes.ts)。 <ThemeGallery /> ## index ## 主题 Press - **Categories**: theme ::: tip 类型定义:[valaxy-theme-press/types/index.d.ts](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-press/types/index.d.ts) ::: `valaxy-theme-press` 是 Valaxy 官方的文档主题。它的交互与组织方式受 VitePress 启发,但运行在 Valaxy 的路由、Markdown、插件、多语言与博客数据系统之上。 当你要构建文档站、项目手册、知识库,或一个同时需要文章、分类、标签、插件和自定义 Vue 组件的文档型站点时,可以选择 Press。 ## 快速开始 {#quick-start} 最简单的方式是在创建 Valaxy 项目时选择 **Press**: ```bash pnpm create valaxy ``` 如果你已经有一个 Valaxy 项目,可以手动安装主题并将 `theme` 设置为 `press`: ```bash pnpm add valaxy-theme-press ``` ```ts [valaxy.config.ts] import type { PressTheme } from 'valaxy-theme-press' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<PressTheme.Config>({ theme: 'press', themeConfig: { logo: '/favicon.svg', }, }) ``` 你也可以将主题配置拆到 `theme.config.ts` 中: ```ts [theme.config.ts] import { defineThemeConfig } from 'valaxy-theme-press' export default defineThemeConfig({ logo: '/favicon.svg', }) ``` ## 文档地图 {#documentation-map} - [配置参考](/zh/themes/press/config):`themeConfig` 选项、首页、页脚、布局、样式与组件覆盖。 - [导航栏与侧边栏](/zh/themes/press/sidebar-nav):顶部导航、分类侧边栏、显式树结构、多侧边栏与 `base`。 - [搜索与多语言](/zh/themes/press/search-i18n):本地搜索、Fuse、Algolia、语言切换器与多语言配置。 - [从 VitePress 迁移](/zh/themes/press/migration):VitePress 用户迁移时的配置映射与注意事项。 ## 最小文档站配置 {#minimal-docs-site} 一个实用的文档站通常会配置站点信息、搜索、导航、侧边栏、编辑链接和页脚: ```ts [valaxy.config.ts] import type { PressTheme } from 'valaxy-theme-press' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<PressTheme.Config>({ siteConfig: { title: 'Acme Docs', url: 'https://docs.example.com', description: 'Documentation for Acme', search: { enable: true, provider: 'local', }, lastUpdated: true, }, theme: 'press', themeConfig: { logo: '/favicon.svg', nav: [ { text: '指南', link: '/guide/getting-started' }, { text: 'API', link: '/api/' }, ], sidebar: { '/guide/': { base: '/guide/', items: [ { text: '指南', items: [ { text: '快速开始', link: 'getting-started' }, { text: '配置', link: 'config' }, ], }, ], }, }, editLink: { pattern: 'https://github.com/acme/project/edit/main/docs/:path', text: '编辑此页', }, footer: { message: 'Released under the MIT License.', copyright: 'Copyright (c) 2026 Acme.', }, }, }) ``` Valaxy 官方文档本身就是 Press 的最大实际示例。完整配置可参考 [docs/valaxy.config.ts](https://github.com/YunYouJun/valaxy/blob/main/docs/valaxy.config.ts)。 ## 使用主题 - **Categories**: theme ## 安装主题 {#install-theme} ```bash npm i valaxy-theme-yun # pnpm add valaxy-theme-yun ``` ## 启用主题 {#enable-theme} 配置 `theme` 字段为主题名称,如 `yun`。 ```ts [valaxy.config.ts] import { defineConfig } from 'valaxy' export default defineConfig({ theme: 'yun' }) ``` ## 主题配置 {#theme-config} 参见对应主题文档,配置 `themeConfig`。 > [主题 Yun 配置](/zh/themes/yun) ```ts [valaxy.config.ts] import { defineConfig } from 'valaxy' export default defineConfig({ theme: 'yun', themeConfig: { // ... } }) ``` ## 如何编写一个 Valaxy 主题 - **Categories**: theme ::: tip Valaxy 与 Vite/Vue 的生态完全兼容,因此你在编写主题时,可以任意使用第三方的 `Vite`/`Vue` 插件。 - [Authoring a Plugin | Vite](https://vitejs.dev/guide/api-plugin.html#authoring-a-plugin) - [Writing a Plugin | Vue](https://vuejs.org/guide/reusability/plugins.html#writing-a-plugin) ::: Valaxy 主题无需预编译,直接发布源文件即可。 撰写中... 作为 Valaxy 作者,我可以很轻松的实现自己的主题。 但也因此,我可能很难了解真正主题开发者的需求。 因此,如果你有任何开发主题的相关问题, 可前往 QQ 频道[「云乐坊」](https://pd.qq.com/s/grfe9jxoe) 或发起 [Discussions](https://github.com/YunYouJun/valaxy/discussions) 与我交流,我将会为您提供尽可能的帮助,并针对泛化的问题撰写文档。 > 对了,由于目前的主题并不多,主题作者可以在[这里](/zh/themes/gallery)发现一些来自云游君私人的奖励。 ## 主题示例 {#theme-examples} - [valaxy-theme-starter](https://github.com/valaxyjs/valaxy-theme-starter): Valaxy 主题开发模版 - [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-theme-yun): valaxy-theme-yun 一个更完善的主题示例 - [valaxy-theme-press](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-theme-press): valaxy-theme-press 当前文档主题示例 ## 使用 AI 生成主题 {#generate-a-theme-with-ai} 填写主题名称与简短的设计说明,然后将生成的提示词复制到 AI 编程助手中。提示词会把任务限制在最小主题包内,要求助手根据当前安装的 Valaxy 版本核对 API,并提供实现与验证检查点。 <ThemePrompt /> ::: tip 为了获得更准确的结果,建议先安装 [Valaxy Skills](/zh/guide/work-with-ai#agent-skills),并在已经包含 Valaxy 或[主题 starter](https://github.com/valaxyjs/valaxy-theme-starter) 的仓库内运行助手。发布主题前,请人工审查生成的代码与依赖变更。 ::: ## 创建主题模板 {#creating-a-theme-template} ::: tip 如果你只想简单点,创建一个自己使用的博客主题而不发布,你可以直接在本地引用你的主题。 可参见 [demo/custom](https://github.com/YunYouJun/valaxy/tree/main/demo/custom)。 ::: ```bash # 使用 valaxy-theme-starter 模版 pnpm create valaxy # choose Theme ``` 在动手之前,我们先来了解一下一个 Valaxy 主题的基础结构,它与正常的用户目录结构也十分相似。 以 [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-theme-yun) 为例: > 尽管它们看起来很多,但是大部分都是可选的,你可以根据主题的需求按需编写。 - `App.vue`: 主题的入口文件,用于挂载全局的主题组件 - `README.md`: 主题的说明文档(毫无疑问,这是必不可少的 :P) - `client`:主题所暴露给用户的客户端辅助函数 - `index.ts`: 主题的客户端辅助函数入口文件 - `components`: 主题的组件 - `ValaxyMain.vue`: 主题的文章渲染组件 - `YunSidebar.vue`: 主题的侧边栏组件 - `YunSponsor.vue`: 主题的赞助组件 - `YunWaline.vue`: 第三方评论 Waline 适配组件 - `composables`: 辅助的 Composition API - `config.ts`: 主题的配置文件 - `helper.ts`: 主题的辅助函数 - `index.ts`: 主题的 Composition API 入口文件 - `post.ts`: 主题的文章相关的辅助函数 - `docs`: 主题的文档(自由用你喜欢的结构组织并展示吧!) > 出于定制化与 [DogFooding](https://zh.wikipedia.org/zh-sg/%E5%90%83%E8%87%AA%E5%B7%B1%E7%9A%84%E7%8B%97%E7%B2%AE) 的考虑,Valaxy 的文档采用自身制作,并制作了一个文档主题 [valaxy-theme-press](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-theme-press),如果你只是想要一个简单轻量的文档站点,[Vitepress](https://vitepress.vuejs.org/) 是个不错的选择。([valaxy-theme-starter](https://github.com/valaxyjs/valaxy-theme-starter) 在未来也许会内置该示例模版。) - `en-US`: 英文文档 - `zh-CN`: 中文文档 - `features`: 主题特色功能,一些不依赖于 Vue Composition API 的功能(区别于 `composables`) - `fireworks.ts`: 烟花点击效果 - `layouts`: 主题的布局(扩展更多布局) - `default.vue`: 默认布局 - `home.vue`: 首页布局 - `layout.vue`: 文章列表布局 - `post.vue`: 文章布局(放置于 `pages/posts/` 文件夹下的文章默认为 `post 布局) - `tags.vue`: 标签布局 - `locales`: 主题的多语言支持 - `en.yml`: 英文语言文件 - `zh-CN.yml`: 中文语言文件 - `node_modules`: 主题的依赖(请勿提交至仓库) - `node`: 主题的 Node 端逻辑 - `package.json`: 主题的相关信息与依赖 - `pages`: 主题的默认页面(扩展更多页面) - `index.vue`: 首页 - `page`: 普通页 - `[page].vue`: 文章列表页,动态路由,如 `/page/2` - `setup`: 主题的入口文件(可注册 Vue 插件等) - `main.ts`: 主入口文件 `defineAppSetup` - `stores`: 主题的状态管理 - `app.ts`: 全局状态管理文件 - `styles`: 主题的样式 - `index.ts`: 主题的样式入口文件 - `tsconfig.json`: 主题的 TypeScript 配置 - `types`: 主题的类型声明 - `index.d.ts`: 主题的类型声明入口文件 - `unocss.config.ts`: 主题的 unocss 配置 - `utils`: 主题的工具函数 - `valaxy.config.ts`: 主题的配置文件 ## APIs {#apis} 我们提供了一个扩展函数 `extendMd`,以供你快速扩展页面信息。 在主题的 `valaxy.config.ts` 中,你可以通过 `extendMd` 来访问每个 Markdown 页面的路由、frontmatter 数据、摘要和文件路径,并在构建时对其进行修改。 ```ts [valaxy.config.ts] import { defineTheme } from 'valaxy' export default defineTheme({ extendMd(ctx) { // ctx.route - EditableTreeNode,可修改路由 meta 信息 // ctx.data - 只读的 frontmatter 原始数据 // ctx.content - 原始 Markdown 内容 // ctx.excerpt - 摘要内容(如果存在) // ctx.path - Markdown 文件的绝对路径 // 示例:为所有页面添加自定义 meta ctx.route.addToMeta({ frontmatter: { customField: 'hello from theme', }, }) }, }) ``` 你也可以直接扩展 [`vue-router/vite`](https://router.vuejs.org/file-based-routing/) 插件中的 `extendRoute`。 > <https://github.com/posva/unplugin-vue-router/issues/43#issuecomment-1433140464> (now part of vue-router) ```ts [valaxy.config.ts] import { defineTheme } from 'valaxy' export default defineTheme({ router: { extendRoute(route) { // want to get component absolute paths? // const path = route.components.get('default') console.log(route) }, }, extendMd(ctx) { console.log(ctx.path) }, }) ``` ```ts import type { EditableTreeNode } from 'vue-router/unplugin' // provided by valaxy, just as a tip export interface ValaxyConfig { vue?: Parameters<typeof Vue>[0] components?: Parameters<typeof Components>[0] unocss?: UnoCSSConfig pages?: Parameters<typeof Pages>[0] extendMd?: (ctx: { route: EditableTreeNode data: Readonly<Record<string, any>> excerpt?: string path: string }) => void } ``` ::: tip `data` 解析自 Markdown frontmatter,为原始数据(不可变),将会被合并至 `route.meta.frontmatter` 中。 ::: ### Client {#client} #### 切换亮暗模式 {#toggle-dark} 以下变量被存储在全局状态中,你可以通过 `useAppStore` 获取。 - `isDark`: 是否启用了暗黑模式 - `themeColor`: 主题色(可跟随 isDark 变化) - `toggleDark`: 切换暗黑模式 - `toggleDarkWithTransition`: 带有过渡效果的切换暗黑模式 ```vue [components/YunToggleDark.vue] <script lang="ts" setup> import { useAppStore } from 'valaxy' const appStore = useAppStore() </script> <template> <button class="yun-icon-btn" @click="app.toggleDarkWithTransition"> <div i="ri-sun-line dark:ri-moon-line" /> </button> </template> ``` > 你可以通过 `themeConfig.valaxyDarkOptions` 来配置暗黑模式的相关选项。 ::: details Default Theme Config.valaxyDarkOptions <<< @/../packages/valaxy/types/default-theme.ts {6-41 ts:line-numbers} ::: ### Node {#node} #### Hooks {#hooks} - [钩子](/zh/guide/custom/hooks.md) ## 开始编写 {#start-writing} ### App.vue {#app-vue} > 你的入口文件 譬如我想要为主题添加一个全局的 Loading 页面。 你可以从 valaxy 导入全局状态 `useAppStore`,记录 `showLoading` 来实现。 > 你也可以使用你自己的全局状态管理。参见 [全局状态管理](#global-state-management)。 ```vue [valaxy-theme-yun/App.vue] <script lang="ts" setup> import { useHead } from '@unhead/vue' import { useAppStore } from 'valaxy' import { onMounted } from 'vue' // ... const app = useAppStore() onMounted(() => { app.showLoading = false }) </script> <template> <!-- ... --> <!-- 添加 Loading 组件,components/YunLoading.vue --> <!-- https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/components/YunLoading.vue --> <Transition name="fade"> <YunLoading v-if="app.showLoading" /> </Transition> </template> ``` ::: tip - 你可以通过 `ValaxyApp.vue` 组件完全覆盖根组件,来达成你更深层次的定制化需求。(完全由你自定义,不再默认挂在 `router-view` 等默认处理。) ::: ### ValaxyMain {#valaxymain} 你需要自定义一个 `ValaxyMain` 组件来决定主题的文章渲染部分。 > 你可以从 `ValaxyMain` 的 `props` 中获取 `frontmatter` 与 `pageData`。 ```vue [valaxy-theme-yun/components/ValaxyMain.vue] <script lang="ts" setup> import type { PageData, Post } from 'valaxy' defineProps<{ frontmatter: Post data?: PageData }>() </script> <template> <main> <slot name="main-content"> <ValaxyMd :frontmatter="frontmatter"> <slot name="main-content-md" /> <slot /> </ValaxyMd> </slot> </main> </template> ``` > 示例可参考 [ValaxyMain.vue | valaxy-theme-yun](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/components/ValaxyMain.vue) ## 样式 {#styles} ### 引入默认样式 {#import-default-styles} Valaxy 提供了一些默认样式,你需要在主题中自行引入。 例如,新建 `valaxy-theme-yun/setup/main.ts`: ```ts [setup/main.ts] import { defineAppSetup, scrollTo } from 'valaxy' import { nextTick } from 'vue' // 引入 valaxy 公共样式 import 'valaxy/client/styles/common/index.scss' // 你也可以按需引入 // common import 'valaxy/client/styles/common/code.scss' import 'valaxy/client/styles/common/hamburger.scss' import 'valaxy/client/styles/common/transition.scss' // Markdown Style import 'valaxy/client/styles/common/markdown.scss' export default defineAppSetup((ctx) => { const { router, isClient } = ctx if (!isClient) return router.afterEach((to, from) => { if (to.path !== from.path) return nextTick(() => { scrollTo(document.body, to.hash, { smooth: true, }) }) }) }) ``` ### Markdown 样式 {#markdown-styles} Markdown 样式是主题呈现文章样式的部分,需要由主题自定义。 你可以参考 [valaxy-theme-press](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-press/) 自定义 Markdown 主题的方式,见 [styles/markdown.scss](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-press/styles/markdown.scss)。 > 如果你想先使用常见的默认样式(后续再进行定制),你可以直接使用 [star-markdown-css](https://github.com/YunYouJun/star-markdown-css)。 > 使用方式可参见 [valaxy-theme-yun/styles](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/styles/index.scss) ### NProgress 进度条 {#nprogress-progress-bar} 内置了基础的 [nprogress](https://github.com/rstacruz/nprogress) 样式,你可以通过覆盖 nprogress 的默认样式进行定制: ```scss [your-theme/styles/index.scss] #nprogress { pointer-events: none; .bar { background: var(--va-c-primary); opacity: 0.75; position: fixed; z-index: 1024; top: 0; left: 0; width: 100%; height: 2px; } } ``` ## 功能 {#features} ### API {#api} > 你还可以使用 Valaxy 内置的 API 以快速实现相关功能。 #### 获取用户的 Valaxy Config {#get-user-s-valaxy-config} 你可以通过内置的 `useValaxyConfig` 获取用户的 Valaxy 配置。 ::: tip 这部分配置与用户的 `valaxy.config.ts` 中的配置相对应,但它仅在客户端使用,因此并不包含 Node 端相关配置(如 `vite` 等)。 ::: ```ts [composables/config.ts] import { useSiteConfig, useValaxyConfig } from 'valaxy' import { useThemeConfig } from 'valaxy-theme-custom' const config = useValaxyConfig() // site.config.ts or config.value.siteConfig const siteConfig = useSiteConfig() // theme.config.ts or config.value.themeConfig const themeConfig = useThemeConfig() ``` #### 提供 Typed useThemeConfig {#provide-typed-usethemeconfig} 你可以提供一个主题的 `useThemeConfig` 函数,以便自己/用户获得带有类型约束的配置。 ```ts [composables/config.ts] // custom your theme type import type { YunTheme } from '../types' import { useValaxyConfig } from 'valaxy' /** * getThemeConfig */ export function useThemeConfig<ThemeConfig = YunTheme.Config>() { const config = useValaxyConfig<ThemeConfig>() return computed(() => config!.value.themeConfig) } ``` ```vue [components/Example.vue] <script lang="ts" setup> import { useThemeConfig } from 'valaxy-theme-custom' const themeConfig = useThemeConfig() </script> ``` #### 获取文章列表 {#get-post-list} 获取文章列表有两种方式。 - `usePostList`: 获取文章列表(不推荐) ```ts import { usePostList } from 'valaxy' const postList = usePostList() ``` - `useSiteStore`: 获取全局站点信息(推荐) ```ts const site = useSiteStore() // site.postList ``` 以上两者之间的区别是,`usePostList` 是一个基础函数,每次调用都会获取所有文章并重新过滤一次,而 `useSiteStore` 则会先调用 `usePostList` 并将获取的文章列表缓存在全局的状态中,以供你后续调用。 (此外,`useSiteStore` 还实现了保存文章时(如标题)热更新列表信息的功能。) > [valaxy/packages/valaxy-theme-yun/components/YunPostList.vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/components/YunPostList.vue) 是一个使用 `useSiteStore` 展示文章列表的示例。 > 分页功能可参考 [valaxy-theme-yun/pages/page/[page].vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/pages/page/%5Bpage%5D.vue) 与 [valaxy-theme-yun/components/YunPostList.vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/components/YunPostList.vue)。 #### 获取文章分类与标签 {#get-post-categories-and-tags} 在你获取文章列表后,`site.postList` 中的每篇文章都具有 `categories`(分类) 与 `tags`(标签) 属性。 你还可以通过 `useCategories` 与 `useTags` 获取所有分类、标签,其中便包含了与文章的对应关系。 ```ts import { useCategories, useTags } from 'valaxy' const categories = useCategories() const tags = useTags() ``` - [valaxy/packages/valaxy-theme-yun/layouts/categories.vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/layouts/categories.vue) 是一个使用 `useCategories` 展示文章分类的示例。 - [valaxy/packages/valaxy-theme-yun/layouts/tags.vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/layouts/tags.vue) 是一个使用 `useTags` 展示文章标签的示例。([`useYunTags`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/composables/tags.ts) 是主题对 `useTags` 的封裝。) > `useTags` 中的 `tags` 为一个对象,其键为标签名,值为对应的文章列表。 > `useCategories` 可传入参数 `category`(`useCategories('aaa')`) 以获取指定分类的文章列表。 #### 获取 Front-matter {#get-front-matter} 你可以通过 `useFrontmatter` 获取当前页面的 Front-matter。 譬如: ```vue <script lang="ts" setup> import { useFrontmatter } from 'valaxy' const fm = useFrontmatter() </script> <template> <h1>{{ fm.title }}</h1> </template> ``` #### 全局状态管理 {#global-state-management} 你可以借助 [Pinia](https://pinia.vuejs.org/) (Valaxy 内置)建立自己的全局状态,并在随后使用它, ```ts [stores/app.ts] import { acceptHMRUpdate, defineStore } from 'pinia' // custom your theme name export const useYunAppStore = defineStore('yun-app', () => { // global cache for yun return {} }) if (import.meta.hot) import.meta.hot.accept(acceptHMRUpdate(useYunAppStore, import.meta.hot)) ``` ```ts // where you want to use // components/YunExample.vue import { useYunAppStore } from '../stores/app' const yun = useYunAppStore() ``` #### 上一篇/下一篇 {#previous-next-post} 文章底部通常存在切换上一篇/下一篇的导航。 你可以利用 `siteStore.postList` 自行实现,也可以使用 Valaxy 提供的 `usePrevNext`。 > 可参见:[valaxy-theme-yun/components/YunPrevNext.vue](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/components/YunPostNav.vue) ```ts import { usePrevNext } from 'valaxy' const [prev, next] = usePrevNext() // prev/next type is PostFrontMatter // prev.title prev.path ``` ### 目录 {#table-of-contents} 如果你想要快速实现一个目录,Valaxy 提供了一个内置钩子函数 `useOutline`。 你可以用它快速获取文章页的目录信息 `headers` 与对应点击事件 `handleClick`,如: ```vue <script setup lang="ts"> import { useOutline } from 'valaxy' const { headers, handleClick } = useOutline() </script> <template> <nav aria-labelledby="doc-outline-aria-label"> <span id="doc-outline-aria-label" class="visually-hidden"> Table of Contents </span> <PressOutlineItem class="va-toc relative z-1 css-i18n-toc" :headers="headers" :on-click="handleClick" root /> </nav> </template> ``` > 更多可参见 [PressOutline | valaxy-theme-press](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-press/components/PressOutline.vue)。 ## 引用静态资源 {#referencing-static-assets} 当主题需要内置一些静态资源(如:图片等),你可以通过相对引用的方式实现。(这在 `scss` 样式文件中也适用) 譬如 `assets` 与 `components` 处于同一目录下时: ```bash ├── components │ └── ValaxyLogo.vue └── assets └── images └── valaxy-logo.png ``` ```vue [components/ValaxyLogo.vue] <script lang="ts" setup> import valaxyLogoPng from '../assets/images/valaxy-logo.png' </script> <template> <img max-w="50" m="auto" :src="valaxyLogoPng" alt="Valaxy Logo" z="1"> </template> <style scoped> .test-image { background-image: url('../assets/images/valaxy-logo.png'); } </style> ``` ## Third Party Plugin {#third-party-plugin} ### 实现评论 {#implement-comments} 作为博客,用户通常会有评论的需求。 而由于评论系统各不相同,如 Hexo 等主题开发者们通常需在主题侧重复实现多款评论系统。 这显然是繁琐的。 Valaxy 决定通过插件中心化地提供各类封装好的评论组件和辅助函数。 譬如主题开发者,可以借助 `valaxy-addon-waline` 来快速实现 [Waline](https://waline.js.org/) 评论系统的集成。 而用户则可以使用相同的配置穿梭漫游于不同的主题之间。 > 集成参见 [valaxy-addon-waline](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-addon-waline/README.md)。 ## 性能优化 {#performance-optimization} ### 添加依赖预构建 `optimizeDeps` {#add-dep-pre-bundling-optimizedeps} - [原因|依赖预构建](https://cn.vite.dev/guide/dep-pre-bundling.html#the-why) 为了提高后续页面的加载性能,Vite 将那些具有许多内部模块的 ESM 依赖项转换为单个模块。 如果你的主题依赖了一些大型的 ESM 包,你可以通过添加 `optimizeDeps` 选项来预构建这些依赖项。 > `dayjs` 已被默认预构建,您无需再次添加。 > [为什么用 dayjs 而不是 date-fns?](https://api.valaxy.site/notes/app-bundle-size.html#date-fns-vs-dayjs?) ```ts [valaxy.config.ts] import { defineTheme } from 'valaxy' export default defineTheme({ vite: { optimizeDeps: { include: ['lodash-es'], }, } }) ``` ### 在主题中使用插件配置 {#using-addon-config-in-themes} 当你的主题集成了可选插件(如 Algolia 搜索、Waline 评论)时,可以使用 `valaxy` 提供的 `useAddonConfig` 读取插件选项,而**无需**对插件包产生硬依赖。 ```vue [components/ThemeSearch.vue] <script lang="ts" setup> import type { AlgoliaSearchOptions } from '../types/algolia' import { useAddonConfig } from 'valaxy' const algolia = useAddonConfig<AlgoliaSearchOptions>('valaxy-addon-algolia') // 插件未安装时 algolia.value 为 undefined </script> ``` 这避免了以往使用动态 `import('valaxy-addon-xxx')` + `.then()` / `.catch()` 的方式,后者容易出错且不具备响应式。 ### 提醒特殊需求的用户安装第三方插件 {#remind-users-with-special-needs-to-install-third-party-plugins} 如果您的主题适配了多个 `addon`,但用户并非都需要安装。 如评论插件: - `valaxy-addon-waline` - `valaxy-addon-twikoo` 当用户没有主动安装对应 `addon` 时(即 `addon` 不存在的情况),则会默认重定向至一个空函数。 因此,如果某个插件不是必须的,请在主题文档中提醒想要使用该功能的用户安装对应插件。 ## 主题 Yun - **Categories**: theme ::: tip 类型定义:[valaxy-theme-yun/types/index.d.ts](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/types/index.d.ts) ::: `valaxy-theme-yun` 是 Valaxy 的默认博客主题,适合个人博客、文章归档、友情链接、首页标语动画与主题自定义。 ## 快速开始 {#quick-start} ```bash pnpm add valaxy-theme-yun ``` ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ theme: 'yun', themeConfig: { type: 'nimbo', }, }) ``` 你也可以将主题配置提取到单独的 `theme.config.ts` 文件中: ```ts [theme.config.ts] import { defineThemeConfig } from 'valaxy-theme-yun' export default defineThemeConfig({ type: 'nimbo', }) ``` ## 文档地图 {#documentation-map} - [配置参考](/zh/themes/yun/config):主题类型、配色、导航栏、页面入口、侧边栏与页脚。 - [布局与视觉](/zh/themes/yun/layout):首页标语、背景图与布局相关配置。 - [功能组件与页面](/zh/themes/yun/widgets):公告、说说、烟花、文章卡片类型、菜单与友链页。 - [自定义](/zh/themes/yun/customization):编辑链接、大纲标题与样式覆盖。 ## valaxy-addon-abbrlink - **Categories**: addon 构建时直接引用 valaxy-addon-abbrlink README 的官方插件文档。 <!--@include: @/../packages/valaxy-addon-abbrlink/README.zh-CN.md{3,}--> ## valaxy-addon-algolia - **Categories**: addon 构建时直接引用 valaxy-addon-algolia README 的官方插件文档。 <!--@include: @/../packages/valaxy-addon-algolia/README.zh-CN.md{3,}--> ## valaxy-addon-bangumi - **Categories**: addon 构建时直接引用 valaxy-addon-bangumi README 的官方插件文档。 <!--@include: @/../packages/valaxy-addon-bangumi/README.zh-CN.md{3,}--> ## valaxy-addon-components - **Categories**: addon 构建时直接引用 valaxy-addon-components 中文 README 的官方插件文档。 <!--@include: @/../packages/valaxy-addon-components/README.zh-CN.md{3,}--> ## valaxy-addon-feishu - **Categories**: addon 构建时直接引用 valaxy-addon-feishu README 的官方插件文档。 <!--@include: @/../packages/valaxy-addon-feishu/README.zh-CN.md{3,}--> ## valaxy-addon-lightgallery - **Categories**: addon 构建时直接引用 valaxy-addon-lightgallery 中文 README 的官方插件文档。 <!--@include: @/../packages/valaxy-addon-lightgallery/README.zh-CN.md{3,}--> ## valaxy-addon-meting - **Categories**: addon 构建时直接引用 valaxy-addon-meting README 的官方插件文档。 <!--@include: @/../packages/valaxy-addon-meting/README.zh-CN.md{3,}--> ## valaxy-addon-moments - **Categories**: addon 构建时直接引用 valaxy-addon-moments 中文 README 的官方插件文档。 <!--@include: @/../packages/valaxy-addon-moments/README.zh-CN.md{5,}--> ## valaxy-addon-twikoo - **Categories**: addon 构建时直接引用 valaxy-addon-twikoo README 的官方插件文档。 <!--@include: @/../packages/valaxy-addon-twikoo/README.zh-CN.md{3,}--> ## valaxy-addon-waline - **Categories**: addon 构建时直接引用 valaxy-addon-waline README 的官方插件文档。 <!--@include: @/../packages/valaxy-addon-waline/README.zh-CN.md{3,}--> ## 组件 - **Categories**: guide Valaxy 内置了几个简单的组件。 你可以在写文章或者创作主题时直接使用。 ::: tip <div flex items="center" pb-1><div inline-flex i-logos:vue /> <span ml-1 inline-flex>基于 Vue 组件</span></div> ::: ## 基础组件 {#basic-components} ::: info 面向主题开发者(普通用户通常不需要直接使用) ::: ### 布局与渲染 {#layout-and-rendering} - [`ValaxyMain.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyMain.vue): 页面基础布局 - [`ValaxyMd.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyMd.vue): Markdown 渲染内容 ### 其他 {#others} - [`AppLink.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/AppLink.vue): 根据链接自动判断是否为站内链接,站内链接使用 `<router-link/>`,站外链接使用 `<a target="_blank"></a>`。 - [`ValaxyCopyright.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyCopyright.vue): 文章中的版权信息 - [`ValaxyDecrypt.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyDecrypt.vue): 文本解密组件 - [`ValaxyGalleryDecrypt.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyGalleryDecrypt.vue): 图片解密组件 - [`ValaxyLogo.vue`](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyLogo.vue): 带渐变色彩的 Valaxy Logo - [`ValaxySvgLogo.vue`](<https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxySvgLogo.vue>): Valaxy SVG Logo - [`ValaxyPagination.vue`](<https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyPagination.vue>): 分页组件 - [`ValaxyOverlay.vue`](<https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyOverlay.vue>): 灰色遮罩组件 - [`ValaxyHamburger.vue`](<https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/client/components/ValaxyHamburger.vue>): 汉堡按钮 ```md <ValaxyLogo /> ``` <ValaxyLogo /> ## 辅助组件 {#helper-components} ### 内置组件 {#内置组件} > 面向用户,可直接使用 你也可以通过 [valaxy-addon-components](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-components) 扩展公共组件。 #### 国际化组件 `<VT />` {#internationalization-component} ```yaml [locales/zh-CN.yml] menu: posts: 博客文章 ``` ```yaml [locales/en.yml] menu: posts: Posts ``` ```md <!-- auto follow locale --> <VT content="menu.posts" /> ``` <VT content="menu.posts" /> ### 扩展公共组件 {#扩展公共组件} ```bash [pnpm] pnpm add valaxy-addon-components ``` 如: - `CodePen`: CodePen 代码片段 - `VCLiveTime`: 站点建立时间 ```md [pages/posts/your-post.md] My Blog Content <CodePen class="h-300px" name="Margin Collapse" id="WqXGpo" user="YunYouJun" tab="html,result" /> ``` My Blog Content <CodePen class="h-300px" name="Margin Collapse" id="WqXGpo" user="YunYouJun" tab="html,result" /> ## 调试组件 {#debug-component} ### `<ValaxyDebug />` {#valaxy-debug} Valaxy 内置了 `<ValaxyDebug />` 调试面板组件,**仅在开发模式下可用**(生产构建时会被完全移除,零开销)。 该组件会在页面左下角显示一个可折叠的浮动面板,包含以下调试信息: - **Breakpoints**:当前视口命中的响应式断点(xs / sm / md / lg / xl / 2xl) - **Route**:当前路由信息(path、name、layout、query、params) - **Frontmatter**:当前页面的 frontmatter 数据(JSON 格式) - **Config**:站点配置摘要和主题配置 #### 使用方式 {#debug-usage} 在你的主题或布局中直接使用即可(无需引入,已全局注册): ```vue <template> <div> <!-- 你的页面内容 --> <ValaxyDebug /> </div> </template> ``` ::: tip `<ValaxyDebug />` 使用 `defineAsyncComponent` 异步加载,并通过 `import.meta.env.DEV` 守卫注册,因此**不会影响生产环境的打包体积**。 ::: ## 自定义 {#自定义} 更多用法请参见 [自定义组件](/zh/guide/custom/components)。 ## 命令行 - **Categories**: guide Valaxy 内置了辅助命令行,你可使用 `valaxy` 或缩写 `vala` 来执行以下命令。 ```bash valaxy [args] Commands: valaxy [root] Start a local server for Valaxy [default] valaxy build [root] build your blog to static content valaxy rss [root] generate rss feed valaxy new <title> Draft a new post valaxy debug Display debug information for your Valaxy project Positionals: root root folder of your source files [string] [default: "."] Options: -p, --port port [number] -o, --open open in browser [boolean] [default: false] --remote listen public host and enable remote control [boolean] [default: true] --log log level [string] [choices: "error", "warn", "info", "silent"] [default: "info"] -h, --help Show help [boolean] -v, --version Show version number [boolean] ``` ## 使用 {#usage} ### 局部使用 {#local} 你可以在项目的 `package.json` 中配置快捷脚本。(**推荐**) ```json { "scripts": { "build": "npm run build:ssg", "build:spa": "valaxy build", "build:ssg": "valaxy build --ssg", "dev": "valaxy dev", "new": "valaxy new", "rss": "valaxy rss" } } ``` 譬如通过 `npm run dev` 启动项目,通过 `npm run build` 可以在构建生成 ssg 站点后,再构建 RSS 源。 通过 `pnpm new post-title` 在 `posts` 文件夹下新建一个名为 `post-title` 的文章。 ### 全局安装 {#global} 你也可以全局安装 valaxy 以在全局使用 `valaxy` 命令。(**非必须**) ```bash pnpm add -g valaxy ``` ## 常用命令 {#useful-commands} - `valaxy .`: 启动 Valaxy,默认目录为当前目录(`.` 可不写) - `valaxy rss`: 自动生成 RSS - `valaxy build`: 默认采用 Vite 构建 SPA 应用 - `valaxy build --ssg`: 构建静态页面站点(内存友好,推荐),使用 Valaxy 内置 SSG 引擎 - `valaxy debug --plain`: 输出可粘贴到 Issue 的环境与项目信息 ## SSG 引擎 {#ssg-engines} Valaxy 使用内置的 SSG(Static Site Generation)引擎(Vue SSR + 纯字符串渲染,无 JSDOM),通过 `valaxy build --ssg` 生成静态页面。 ::: tip 基于 JSDOM 的传统 `vite-ssg` 引擎已在 **v1.0 中移除**(它在 pnpm 下损坏,详见 [#706](https://github.com/YunYouJun/valaxy/issues/706))。现在只有单一引擎,无需 `--ssg-engine` 参数。 ::: ### 工作原理 {#how-it-works} Valaxy SSG 引擎分为三个阶段: 1. **Client Build** — 使用 Vite 构建客户端产物(启用 `ssrManifest`) 2. **Server Build** — 构建 SSR 入口(`entry-ssr.ts`),生成可在 Node.js 中执行的渲染函数 3. **Render** — 加载 SSR 入口,遍历路由,调用 Vue 的 `renderToString` 生成 HTML,通过纯字符串替换注入 `<head>` 标签、preload 链接和初始状态,写入磁盘 由于不依赖 JSDOM,每页渲染的内存开销极低,因此可以使用更高的并发数(默认 20),整体构建速度更快且更稳定。首屏无样式闪烁由 [FOUC guard](./config/extend) 处理,而非 Critical CSS 内联。 ### 文章 {#posts} - `valaxy new <title>`: 在 `pages/posts` 目录下新建标题为 `title` 的帖子(.md) - `-f` 以文件夹的形式创建。 譬如: - `valaxy new your-first-post`,将会在 `pages/posts` 下自动新建 `your-first-post.md` 文件,并附带日期。 - `valaxy new -f your-first-post`,将会在 `pages/posts` 下自动新建 `your-first-post/index.md` 文件。 > 你觉得还可以有其他更常用、更好用的命令?没问题,尽管来 [Issues](https://github.com/YunYouJun/valaxy/issues) 反馈吧! - [自定义文章模板](/zh/guide/custom/templates) ### 插件命令 {#addon-commands} 已启用的插件可以在根据包名生成的命名空间下提供命令。例如,通过 `addonMoments()` 配置 `valaxy-addon-moments` 后: ```bash valaxy moments new [title] valaxy moments --help ``` 插件命令仅在调用时从当前项目解析,因此全局 `valaxy --help` 只展示核心命令。插件不能覆盖核心命令,也不能与另一个已启用插件的命令重名。 插件作者可通过 `defineValaxyAddon` 返回值中实验性的 `extendCli` 钩子注册子命令。Valaxy 会根据包名生成根命名空间,并传入已经限制在该命名空间下的 CLI: ```ts export const addonMoments = defineValaxyAddon(() => ({ name: 'valaxy-addon-moments', extendCli(cli, { userRoot }) { cli.command('new [title]', 'Draft a new moment', () => {}, ({ title }) => { // 在 userRoot 下创建动态。 }) }, })) ``` CLI 钩子要求使用推荐的工厂/对象形式配置插件,例如 `addonMoments()`;字符串插件配置不携带 Node 钩子。 ## FAQ {#faq} ### 控制台开发时日志太少,构建时日志太多? {#more-logs-when-developing-and-less-when-building} - 开发与(`valaxy`)构建(`valaxy build`)时默认日志等级为 `info` - 可选项:['error', 'warn', 'info', 'silent'] 您可以通过设置日志等级控制。 譬如 `valaxy build --log=warn`。 ### 怀念 Hexo 的 `hexo deploy`? {#miss-hexo-deploy-from-hexo} 在创建 Valaxy 项目时,已内置了 `.github/workflows/gh-pages.yml`,在推送至 GitHub 时,会自动构建并部署到 GitHub Pages。 如果你仅想部署 `gh-pages` 分支,并且真的很想使用 `deploy`。 你也可以安装 `pnpm add -D gh-pages`,并在项目的 `package.json` 中配置快捷脚本。 ```json { "scripts": { "deploy": "valaxy build && gh-pages -d dist" }, "devDependencies": { "gh-pages": "latest" } } ``` ## 扩展配置 - **Categories**: config ::: tip 扩展配置是 Valaxy 提供的高阶配置,允许你自定义更多与底层/构建相关的配置。 ::: 以下是所有的扩展配置项与相关类型。 > [packages/valaxy/node/types/index.ts](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/node/types/index.ts) ::: details package/valaxy/node/types/index.ts ValaxyExtendConfig <<< @/../packages/valaxy/node/types/index.ts#snippet{ts:line-numbers} <<< @/../packages/valaxy/node/types/config.ts#snippet{ts:line-numbers} ::: 所以,你可以像这样使用: ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' import { addonComponents } from 'valaxy-addon-components' import { VitePWA } from 'vite-plugin-pwa' const safelist = [ 'i-ri-home-line', ] export default defineValaxyConfig<ThemeConfig>({ // site config see site.config.ts or write in siteConfig siteConfig: {}, theme: 'yun', themeConfig: { banner: { enable: true, title: '云游君的小站', }, }, vite: { // https://vite-pwa-org.netlify.app/ plugins: [VitePWA()], }, unocss: { safelist, }, addons: [ addonComponents() ], }) ``` ### Build {#build} `build` 字段用于配置 `valaxy build` 的构建行为。 #### ssgForPagination {#ssgforpagination} 启用后,Valaxy 会为分页页面生成独立的静态 HTML(如 `/page/1`、`/page/2` 等)。默认 `false`。 #### foucGuard {#foucguard} FOUC(Flash of Unstyled Content)防护配置。通过在 `<head>` 中内联 `body { opacity: 0 !important }` 隐藏页面,并通过 JS 监测所有样式表加载完成后,移除该隐藏样式标签以显示页面,防止首屏样式闪烁和样式分批渲染的问题。 - `enabled`(默认 `true`):是否启用 FOUC 防护 - `maxDuration`(默认 `5000`):最大等待时间(毫秒),作为 CSS 加载失败时的安全兜底。设为 `0` 可禁用超时兜底 ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ build: { ssgForPagination: false, foucGuard: { enabled: true, maxDuration: 5000, }, }, }) ``` ### @vitejs/plugin-vue {#vitejsplugin-vue} Valaxy 默认集成了 [`@vitejs/plugin-vue`](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue) 插件,你可以通过 `vue` 配置项进行配置。 ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ vue: { template: { compilerOptions: { isCustomElement: tag => tag.startsWith('my-') } } } }) ``` ### Vite {#vite} 你可以参见 [Vite 文档](https://vite.dev/config/shared-options.html) 自定义 Vite 相关配置。 ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ vite: { plugins: [] } }) ``` ### SSG Options {#ssg-options} 通过 `vite.ssgOptions` 自定义内置的 Valaxy SSG 引擎。Valaxy 会在构建后自动生成 sitemap,你的回调会在其后运行。 支持的选项: - `concurrency` — 并发渲染的页面数(默认 `20`) - `includedRoutes(paths, routes)` — 返回需要渲染的路由列表 - `includeAllRoutes` — 同时渲染动态路由 - `onBeforePageRender(route, html)` — 页面渲染前转换 HTML 模板 - `onPageRendered(route, html)` — 页面渲染后转换其 HTML - `onFinished()` — 所有页面写入后运行(Valaxy 的 sitemap 生成先执行) **SSG 构建最低内存:~4 GB。** Vite 8(Rolldown)在 chunk 生成阶段占用更多内存,引擎会自动以足够的堆重启。若仍遇到 `JavaScript heap out of memory`,请手动增大限制: ```bash NODE_OPTIONS=--max-old-space-size=4096 pnpm build --ssg ``` 详见 [开发 FAQ - JavaScript heap out of memory](/zh/dev/faq#javascript-heap-out-of-memory)。 ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ vite: { ssgOptions: { // 并发渲染的页面数 // concurrency: 20, // 自定义要生成的路由 // includedRoutes(paths, routes) { // return paths.filter(p => !p.includes(':')) // }, // 构建完成后的回调(Valaxy 的 sitemap 生成始终会先执行) async onFinished() { console.log('SSG build finished!') }, }, }, }) ``` ### Markdown {#markdown} 可自定义 Markdown 相关配置,如代码主题、区块内容、添加 `markdown-it` 插件、transformer 等。 效果参见: [Markdown](/zh/guide/markdown)。 ::: details valaxy/node/plugins/markdown/types.ts <<< @/../packages/valaxy/node/plugins/markdown/types.ts ::: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ markdown: { // default material-theme-palenight // theme: 'material-theme-palenight', theme: { // light: 'material-theme-lighter', light: 'github-light', // dark: 'material-theme-darker', dark: 'github-dark', }, blocks: { tip: { icon: 'i-carbon-thumbs-up', text: 'ヒント', langs: { 'zh-CN': '提示', }, }, warning: { icon: 'i-carbon-warning-alt', text: '注意', }, danger: { icon: 'i-carbon-warning', text: '警告', }, info: { text: 'información', }, }, codeTransformers: [ // We use `[!!code` in demo to prevent transformation, here we revert it back. { postprocess(code) { return code.replace(/\[!!code/g, '[!code') }, }, ], config(md) { // md.use(xxx) } }, }) ``` ### DevTools {#devtools} 设置 `devtools: false` 以关闭 DevTools。 ### 插件 Addons {#addons} 参见 [使用插件](/zh/addons/use)。 ### UnoCSS {#unocss} 参见 [UnoCSS](/zh/guide/config/unocss-options)。 ### Modules {#modules} #### RSS {#rss} Valaxy 内置了 RSS 模块,你可以在 `valaxy.config.ts` 中通过 `modules.rss` 配置项进行配置。 - `enable`: 是否启用 RSS 模块。默认 `true`,启用。 - `fullText`: 是否输出文章全文。默认 `false`,只输出摘要。 - `extractImagePathsFromHTML`: 是否从构建后的 HTML 中提取图片路径(用于解析 Vite 打包后的 hash 文件名)。默认 `true`,启用。 ```ts [valaxy.config.ts] export default defineValaxyConfig({ modules: { rss: { enable: true, fullText: false, // 当设置为 true 时,会从构建后的 HTML 中提取图片的实际路径(包含 hash) // When set to true, it will extract actual image paths (with hash) from built HTML extractImagePathsFromHTML: true, }, }, }) ``` **关于 `extractImagePathsFromHTML`** 当你在 Markdown 中使用相对路径引用图片时(如 `![pic](test.webp)`),Vite 会将图片打包并生成带 hash 的文件名(如 `/assets/test.zBFFFKJX.webp`)。 - 启用此选项(默认):RSS feed 中的图片 URL 将使用构建后的实际路径,如 `https://example.com/assets/test.zBFFFKJX.webp` - 禁用此选项:RSS feed 中的图片 URL 将基于文章目录构建,如 `https://example.com/posts/article-name/test.webp` 大多数情况下,你应该保持此选项为 `true`,以确保 RSS 阅读器能正确加载图片。 #### LLMS {#llms} Valaxy 内置了 LLMS 模块,遵循 [llms.txt 标准](https://llmstxt.org/),在构建时生成 AI 可读的 Markdown 内容。 启用后,构建产物中将包含: - `/llms.txt` — 站点页面索引,按目录分组,包含指向各 `.md` 文件的链接 - `/llms-full.txt` — 所有页面内容的合集(可选) - `/*.md` — 每个页面的原始 Markdown 文件,可通过 URL 直接访问 同时,主题可以利用 `useCopyMarkdown()` composable 为文章页添加「复制 Markdown」按钮(Yun 主题已内置支持)。 - `enable`: 是否启用 LLMS 模块。默认 `false`,关闭。 - `files`: 是否为每个页面生成独立的 `.md` 文件。默认 `true`。 - `fullText`: 是否生成 `llms-full.txt`(包含所有页面完整内容)。默认 `true`。 - `prompt`: 自定义提示词,添加到 `llms.txt` 的描述部分。默认 `''`。 - `include`: 要包含的 Markdown 文件 glob 模式(相对于 `pages/` 目录)。默认 `['posts/**/*.md']` 仅包含 posts 目录。设为 `['**/*.md']` 可包含所有 `pages/` 下的 Markdown 文件,也可指定多个目录如 `['posts/**/*.md', 'guide/**/*.md']`。 `llms.txt` 中的页面会按顶级目录自动分组(如 `## Posts`、`## Guide` 等)。 ```ts [site.config.ts] export default defineSiteConfig({ llms: { enable: true, files: true, fullText: true, prompt: '', // Default: only posts // include: ['posts/**/*.md'], // Include all markdown files under pages/ // include: ['**/*.md'], // Include specific directories // include: ['posts/**/*.md', 'guide/**/*.md'], }, }) ``` ### CDN 外部化 {#cdn-externals} > 实验性功能 通过 `cdn.modules` 配置项,你可以指定某些 npm 包在构建时从 CDN 加载,而非打包到最终产物中。 这可以显著减小构建产物体积,并利用 CDN 加速资源加载。 该配置仅在 `valaxy build` 时生效,开发模式下不受影响。 ::: tip `cdn.modules` 中的每个模块需要提供以下字段: - `name`: npm 包名(如 `'katex'`) - `global`: 该库在 `window` 上暴露的全局变量名(如 `'katex'`) - `url`: CDN 脚本的完整 URL - `css`(可选): CDN 样式表的完整 URL - `exports`(可选): 需要重新导出的命名导出列表(如 `['ref', 'computed']`) ::: #### 示例:通过 CDN 加载 KaTeX {#example-load-katex-from-cdn} KaTeX 默认会被打包进构建产物。如果你希望通过 CDN 加载 KaTeX 以减小打包体积,可以如下配置: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ cdn: { modules: [ { name: 'katex', global: 'katex', url: 'https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js', css: 'https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css', }, ], }, }) ``` 你也可以使用其他 CDN 源,只需替换 URL 即可。例如使用 unpkg: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ cdn: { modules: [ { name: 'katex', global: 'katex', url: 'https://unpkg.com/katex@0.16.21/dist/katex.min.js', css: 'https://unpkg.com/katex@0.16.21/dist/katex.min.css', }, ], }, }) ``` ## 基础配置 - **Categories**: config ## 配置说明 {#configurations} 为了便于配置,Valaxy 将配置分为了三种。 `valaxy.config.ts` 是配置的主入口,它包含了以下配置。 - `siteConfig`: 站点**信息**配置,这部分内容面向站点展示,且在不同主题中也是通用的格式 - `themeConfig`: 主题配置,这部分内容仅在特定主题生效 - `runtimeConfig`: 运行时的配置(由 Valaxy 自动生成),用户无需配置 - 其他 Valaxy 通用配置内容(如需要在 Node 端处理的配置 `unocss`/`addons`) 譬如: ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' import { addonComponents } from 'valaxy-addon-components' import { VitePWA } from 'vite-plugin-pwa' const safelist = [ 'i-ri-home-line', ] export default defineValaxyConfig<ThemeConfig>({ // site config see site.config.ts or write in siteConfig siteConfig: {}, theme: 'yun', themeConfig: { // 主题布局类型:'nimbo'(默认,现代布局)或 'strato'(经典侧边栏) // 详见:https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-theme-yun/docs type: 'nimbo', banner: { enable: true, title: '云游君的小站', }, }, vite: { // https://vite-pwa-org.netlify.app/ plugins: [VitePWA()], }, unocss: { safelist, }, addons: [ addonComponents() ], }) ``` ## 站点配置 {#site-config} > 更多详细配置可参见 [types/config.ts](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/types/config.ts)。 ::: details packages/valaxy/types/config.ts SiteConfig <<< @/../packages/valaxy/types/config.ts#snippet{ts:line-numbers} ::: 站点**信息**配置,这部分内容面向站点展示且在任何主题也是通用的格式。 你也可以将其写在 `site.config.ts` 中。 譬如: ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ lang: 'zh-CN', title: 'Valaxy Theme Yun', url: 'https://valaxy.site/', author: { name: '云游君', avatar: 'https://www.yunyoujun.cn/images/avatar.jpg', }, /** * 站点图标 */ favicon: 'https://www.yunyoujun.cn/favicon.svg', /** * 副标题 */ subtitle: 'All at sea.', description: 'Valaxy Theme Yun Preview.', social: [ { name: 'RSS', link: '/atom.xml', icon: 'i-ri-rss-line', color: 'orange', } ], sponsor: { enable: true, methods: [ { name: '支付宝', url: 'https://cdn.yunyoujun.cn/img/donate/alipay-qrcode.jpg', color: '#00A3EE', icon: 'i-ri-alipay-line', }, ], }, }) ``` ### 作者信息 {#作者信息} 更多字段可参考上文类型或直接在编辑器提示中查看。 ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ author: { name: '你的名字', /** * Your avatar * 头像链接 */ avatar: 'https://xxx', intro: '个人简介' } }) ``` ### 时区 {#时区} 如果你使用 CI/CD 构建部署,远程机器可能处于其他时区,你可以设置时区。 此时将会默认使用该时区格式化时间,并设置 `process.env.TZ` 变量。 如果你托管于其他平台,你可能需要在对应平台添加环境变量。 ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ timezone: 'Asia/Shanghai' }) ``` ### 文章排序 {#post-sorting} 设置 `siteConfig.orderBy` 可控制文章列表的排序方式。 - `date`: 按照文章的日期排序(默认) - `updated`: 按照文章的最后更新时间排序 当 `lastUpdated` 开启时,将会为未设置 `updated` 的文章自动注入文件的最后更新时间。 ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ orderBy: 'updated', }) ``` ### Default Frontmatter {#default-frontmatter} 为所有文章设置默认的 Frontmatter。 譬如: > 设置 `time_warning: false`,则所有文章都不会显示阅读时间警告。 ```ts {7-9} [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ /** * 默认 Frontmatter */ frontmatter: { time_warning: false, } }) ``` ### 社交图标 {#social-icons} ```ts export interface SocialLink { /** * 社交链接名称 */ name: string link: string /** * 图标名称 * https://icones.js.org/ */ icon: string color: string } ``` 示例: ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ social: [ { name: 'RSS', link: '/atom.xml', icon: 'i-ri-rss-line', color: 'orange', }, { name: 'QQ 群 1050458482', link: 'https://qm.qq.com/cgi-bin/qm/qr?k=kZJzggTTCf4SpvEQ8lXWoi5ZjhAx0ILZ&jump_from=webapi', icon: 'i-ri-qq-line', color: '#12B7F5', }, { name: 'GitHub', link: 'https://github.com/YunYouJun', icon: 'i-ri-github-line', color: '#6e5494', }, ] }) ``` ### 赞助 {#sponsor} > 在每篇文章末尾,展示赞助(打赏)信息。 ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ sponsor: { enable: true, methods: [ { name: '支付宝', url: 'https://cdn.yunyoujun.cn/img/donate/alipay-qrcode.jpg', color: '#00A3EE', icon: 'i-ri-alipay-line', }, { name: '微信支付', url: 'https://cdn.yunyoujun.cn/img/donate/wechatpay-qrcode.jpg', color: '#2DC100', icon: 'i-ri-wechat-pay-line', }, ], }, }) ``` 你可以通过 `sponsor` 属性控制全局是否开启。 ```ts interface SponsorOption { enable: boolean title: string methods: { name: string url: string color: string icon: string }[] } ``` 或为某篇文章的 Front Matter 单独设置: ```md --- title: xxx sponsor: false --- ``` ### 阅读统计 {#阅读统计} 开启阅读统计,将会在每篇文章开头展示阅读统计信息。 > 需要主题进行适配,即展示 `frontmatter` 中的 `wordCount` 和 `readingTime` 字段。 - `wordCount`:字数统计 - `readingTime`:阅读时长(分钟) - 可以设置不同语言的阅读速度,默认 `cn` 为 300 字/分钟,`en` 为 200 字/分钟。 ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ /** * 开启阅读统计 */ statistics: { enable: true, readTime: { /** * 阅读速度 */ speed: { cn: 300, en: 200, }, }, } }) ``` ### 代码块高度限制 {#code-height-limit} 你可以为每篇文章设置代码块高度限制。 譬如设置 `codeHeightLimit: 300`,则文章中所有代码块高度都不会超过 300px,并自动折叠。 ```ts {5} import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ // ... codeHeightLimit: 300, }) ``` 你也可以在文章的 Front Matter 中单独设置: ```md {2} --- codeHeightLimit: 300 --- ``` 示例可参见 [代码块高度限制](/zh/examples/code-height-limit)。 ### 内容加密 {#content-encryption} 首先在 `site.config.ts` 中开启加密 ```ts {5-7} import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ // ... encrypt: { enable: true, } }) ``` - 加密整篇文章 在文章的 Front Matter 中设置 `password`: ```md {2} --- password: your_password password_hint: 自定义密码提示 --- ``` - 加密部分内容 ::: tip 如果在文章的 Front Matter 中设置了 `password`,文章中的部分加密将被忽略。 ::: 将待加密的内容包裹在 `<!-- valaxy-encrypt-start:your_password --><!-- valaxy-encrypt-end -->` 中。 示例可参见 [部分内容加密](/zh/examples/partial-content-encryption)。 ### 客户端重定向 {#client-redirects} ```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 } ``` 示例: ```ts [site.config.ts] export default defineSiteConfig({ redirects: { useVueRouter: true, rules: [ { from: ['/foo', '/bar'], to: '/about', }, { from: '/v1/about', to: '/about', }, ] }, }) ``` `/foo`, `/bar`, `/v1/about` 这些路由会被重定向到 `/about`。 你也可以在 Front Matter 中配置: ```md <!-- pages/posts/redirect.md --> --- from: - /redirect/old1 - /redirect/old2 --- ``` ```md <!-- pages/posts/redirect.md --> --- from: /v1/redirect --- ``` `/redirect/old1`, `/redirect/old2`, `/v1/redirect` 这些路由会被重定向到 `/posts/redirect`。 ::: tip 在 SSG 构建时,如果 useVueRouter 为 false,则会为每一个源路由生成一个 html 文件 ::: ### 图片预览(Medium Zoom) {#image-preview-medium-zoom} Valaxy 内置了 [medium-zoom](https://github.com/francoischalifour/medium-zoom) 进行图片预览,默认关闭。 > [Medium Zoom Demo](https://medium-zoom.francoischalifour.com/) - mediumZoom - `enable`: 是否开启 - `selector`: 可自定义传入选择器 - `options`: 与 [options | medium-zoom](https://github.com/francoischalifour/medium-zoom#options) 一致 譬如开启 Medium Zoom: ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ mediumZoom: { enable: true } }) ``` 除此之外,你也可以单独控制是否在某篇文章中开启。 ```md --- title: Test Medium Zoom medium_zoom: true --- ``` ### 懒加载 Vanilla Lazyload {#lazyload-vanilla-lazyload} Valaxy 内置了 [vanilla-lazyload](https://github.com/verlok/vanilla-lazyload)。 `vanillaLazyload` 默认不开启。 因为 Valaxy 本身会为所有的图片添加 `loading="lazy"`,它是浏览器的特性,但如果你希望得到更广泛的兼容,你可以手动开启 `vanillaLazyload`。 ```ts export default defineSiteConfig({ vanillaLazyload: { // 默认不开启 enable: true, } }) ``` ### 更多配置 {#更多配置} > 更多详细配置可参见 [types/config.ts](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy/types/config.ts)。 ::: details packages/valaxy/types/config.ts SiteConfig <<< @/../packages/valaxy/types/config.ts#snippet{ts:line-numbers} ::: ## 主题配置 {#theme-config} 参照 [使用主题](/zh/themes/use) 及您所使用的主题文档进行配置。 ## 扩展配置 {#扩展配置} 更多高阶配置请参见 [扩展配置](/zh/guide/config/extend)。 ## UnoCSS 配置 - **Categories**: config 我们默认集成了 [UnoCSS](https://unocss.dev) 以下 presets 预设。 - [`presetWind4`](https://unocss.dev/presets/wind4): 一些常用按需生成的样式,TailwindCSS v4 风格。 - [`presetAttributify`](https://unocss.dev/presets/attributify): 使用属性选择器替代类名。 - [`presetIcons`](https://unocss.dev/presets/icons): 集成了 [icones](https://icones.netlify.app/) 图标库,按需使用。 - [`presetTypography`](https://unocss.dev/presets/typography): 排版相关的样式预设。 因此,你可以直接在 Markdown 中快速实现各式各样的效果。 见 [UnoCSS | Markdown](/zh/guide/markdown#unocss)。 ## unocss {#unocss} 您可以在主题 theme 目录的 `uno.config.ts` 或 `unocss.config.ts` 文件中编写 UnoCSS 配置。 ```ts [uno.config.ts] import { defineConfig } from 'unocss' export default defineConfig({ shortcuts: [ [ 'custom-uno-btn', 'px-4 py-1 rounded inline-block bg-teal-700 text-white cursor-pointer !outline-none hover:bg-teal-800 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50' ], ], safelist: ['bg-red-500'], }) ``` 也可以在 `valaxy.config.ts` 文件的 `unocss` 配置项中进行配置。以下是 `valaxy.config.ts` 中 `unocss` 配置项的示例: ```ts [valaxy.config.ts] import { presetIcons } from 'unocss' export default defineValaxyConfig<ThemeConfig>({ unocss: { shortcuts: [ { 'bg-base': 'bg-white dark:bg-black', 'color-base': 'text-black dark:text-white', 'border-base': 'border-[#8884]', }, ], rules: [ ['theme-text', { color: '#4b4b4b' }], ], }, }) ``` 在 `unocss` 配置项中直接配置 `presets` 会覆盖主题和 Valaxy 默认的 `presets`。 如果想扩展这些预设,请使用 [unocssPresets](#unocsspresets)。 ```ts [valaxy.config.ts] import { presetIcons } from 'unocss' export default defineValaxyConfig<ThemeConfig>({ unocss: { presets: [ presetIcons({ extraProperties: { 'display': 'inline-block', 'height': '1.2em', 'width': '1.2em', 'vertical-align': 'text-bottom', }, }), ], }, }) ``` ::: tip 在 `uno.config.ts` 或 `unocss.config.ts` 文件中配置 `presets` 也会覆盖 Valaxy 或主题的默认预设。 若要扩展预设,请使用 [unocssPresets](#unocsspresets) 。 ::: ## unocssPresets {#unocsspresets} 若要在 Valaxy 中扩展 [UnoCSS presets](https://unocss.dev/guide/presets) 配置项,以下是一个基本示例 ```ts [valaxy.config.ts] import { presetIcons } from 'unocss' export default defineValaxyConfig<ThemeConfig>({ unocssPresets: { icons: { extraProperties: { 'display': 'inline-block', 'height': '1.2em', 'width': '1.2em', 'vertical-align': 'text-bottom', }, }, }, }) ``` ::: danger <span lang="zh-CN"> 以下方式是错误的写法,注意 `unocssPresets` 和 `unocss` 配置项之间的区别 </span> <span lang="en"> The following method is incorrect. Note the difference between the `unocssPresets` and `unocss` configuration options: </span> ```ts [valaxy.config.ts] import { presetIcons } from 'unocss' export default defineValaxyConfig<ThemeConfig>({ unocssPresets: { // ❌ This won't work icons: presetIcons({ // [!code error] extraProperties: { 'display': 'inline-block', 'height': '1.2em', 'width': '1.2em', 'vertical-align': 'text-bottom', }, }), }, }) ``` ::: ## FAQ {#faq} ### 关于 UnoCSS 热重载失效 {#about-unocss-hot-reloading-failure} > 由于目前无法获取 UnoCSS 的 ctx,暂时还没有找到一个好的方法来实现热重载 [#48](https://github.com/YunYouJun/valaxy/issues/48) ## 自定义组件 - **Categories**: custom ## 自动组件注册 {#automatic-component-registration} 新建 `components` 文件夹,书写任意 Vue 组件。 它们会被自动注册,你甚至可以在你的 Markdown 文件中使用它。 如果存在与主题、Valaxy 的同名组件,覆盖顺序为 `用户目录` -> `主题目录` -> `Valaxy 客户端目录`。 这也意味着你可以只覆盖主题的某个组件,来达到自定义**局部**主题的效果! ### 取消默认注册 {#取消默认注册} 你可以将组件放置于**非** `components` 文件夹或 `components/.exclude` 文件夹下。 你也可以自定义排除规则。 ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ /** * @see https://github.com/unplugin/unplugin-vue-components#configuration * `/[\\/]node_modules[\\/]/, ` 不要排除 node_modules/valaxy/client/components 下的组件 */ components: { exclude: [/[\\/]\.git[\\/]/, /[\\/]\.exclude[\\/]/], }, }) ``` ### 自定义覆盖主题组件 {#custom-override-theme-component} 基于此,你可以非常容易地自定义主题的任何地方! 譬如自定义页脚: > 可参见 [demo/yun/components/YunFooter.vue | GitHub](https://github.com/YunYouJun/valaxy/blob/main/demo/yun/components/YunFooter.vue) 在博客文件夹中 `components` 目录下,新建 `YunFooter.vue` 覆盖你的主题页脚文件。 你可以直接替换掉页脚内容: ```vue <template> <div>页脚内容</div> </template> ``` 也可以继承扩展此前的页脚: ```vue <script lang="ts" setup> import YunFooter from 'valaxy-theme-yun/components/YunFooter.vue' </script> <template> <YunFooter> 自定义页脚内容 </YunFooter> </template> ``` ### 更多示例 {#more-examples} ### 插入不蒜子统计 {#insert-busuanzi-statistics} > [不蒜子统计](http://ibruce.info/2015/04/04/busuanzi/) 以 valaxy-theme-yun 为例: > 默认指在你的博客文件夹下进行操作。 在 `components/` 文件夹下新建 `YunFooter.vue` 以自定义页脚并显示不蒜子统计。 你可以根据你的需要自由定制它的样式。 ```vue [components/YunFooter.vue] <script lang="ts" setup> import { useScriptTag } from '@vueuse/core' import YunFooter from 'valaxy-theme-yun/components/YunFooter.vue' useScriptTag('//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js') </script> <template> <YunFooter> <!-- 自定义页脚内容 --> <div>本站总访问量 <span id="busuanzi_value_site_pv" /> 次</div> <div>本站访客数 <span id="busuanzi_value_site_uv" /> 人次</div> </YunFooter> </template> ``` 在 `components/` 文件夹下新建 `YunPostMeta.vue` 以自定义每篇文章的信息并显示不蒜子单篇文章统计。 ```vue [components/YunPostMeta.vue] <script lang="ts" setup> import type { Post } from 'valaxy' import { useLayout } from 'valaxy' import YunPostMeta from 'valaxy-theme-yun/components/YunPostMeta.vue' defineProps<{ frontmatter: Post }>() // 仅在 Post 布局显示 const isPost = useLayout('post') </script> <template> <YunPostMeta :frontmatter="frontmatter"> <span v-if="isPost" id="busuanzi_container_page_pv"> 本文总阅读量 <span id="busuanzi_value_page_pv" /> 次 </span> </YunPostMeta> </template> ``` 原理即覆盖组件,您还可以自由覆盖主题的任意其他组件。 ### 其他 {#other} [valaxy-addon-components](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-components) 也是一个充分利用该机制的插件,你也可以参考它的实现方式,以 Valaxy 插件的形式自由发布你的自定义组件。 > 由于发布的是原生的 Vue 组件,所以打包时它将会完全是**按需**的,无需您额外担忧。 [使用 valaxy-addon-components 插入公共组件的示例](https://yun.valaxy.site/examples/addons/components) ## 自定义扩展 - **Categories**: custom Valaxy 以约定大于配置的方式提供了强大的扩展功能,如果你有一定开发经验,可以自定义控制站点的每一处细节。 > 以下内容无论对于用户还是主题开发者来说都同样适用。 ::: tip 默认在用户站点根目录或主题根目录下操作。 如果你想要有所参考,你可以参见 [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-theme-yun)。 ::: ## 自动布局注册 {#automatic-layout-registration} 基于 [vite-plugin-vue-layouts-next](https://github.com/loicduong/vite-plugin-vue-layouts-next),Valaxy 提供了布局功能。 新建 `layouts` 文件,书写 Vue 组件作为布局。 你可以在 Markdown 中如下使用它。 ```md [pages/album.md] --- title: Photos layout: album --- ``` 同样,当存在同名布局时,覆盖顺序为 `用户目录` -> `主题目录` -> `Valaxy 客户端目录`。 ## 自定义 index.html {#customizing-indexhtml} 新建 `index.html`,你可以在 `<head></head>` 与 `<body></body>` 全局地插入任意内容。 譬如: ```html [index.html] <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/star-markdown-css/dist/planet/planet-markdown.min.css" /> </head> ``` ## 扩展 Client 上下文 {#extending-client-context} 新建 `setup/main.ts`: ```ts [setup/main.ts] import { defineAppSetup } from 'valaxy' export default defineAppSetup((ctx) => { console.log(ctx) const { app, head, router, routes, isClient } = ctx // 任意使用 Vue 生态的插件 app.use(/* */) }) ``` > 具体示例可参见 [谷歌统计|第三方集成](/zh/guide/third-party/#谷歌统计)。 ## 覆盖 App 组件 {#overriding-app-component} 你可以在站点根目录下创建 `App.vue` 文件来完全覆盖默认的应用组件。主题开发者也可以在主题根目录下提供 `App.vue`。 覆盖优先级为:**用户** > **主题** > **核心**。 ::: warning 覆盖 App 组件会替换掉默认的 SEO 设置(由 `useValaxyApp()` 提供)和默认的 `<router-view>`,你需要自行处理这些内容。 大多数情况下,推荐使用 `setup/main.ts`(参见上方 [扩展 Client 上下文](#extending-client-context))。仅在需要深度自定义应用外壳时才使用完整的 `App.vue` 覆盖。 ::: ```vue [App.vue] <script setup lang="ts"> import { useValaxyApp } from 'valaxy' // 调用 useValaxyApp() 以保留默认的 SEO 行为 useValaxyApp() </script> <template> <router-view /> </template> ``` ## 多语言支持 {#i18n} 新建 `locales` 文件夹。 - `zh-CN.yml`: 中文翻译 - `en.yml`: 英文翻译 譬如(请确保文件内容非空): ```yaml [locales/en.yml] button: about: About ``` ```yaml [locales/zh-CN.yml] button: about: 关于 ``` 你可以如下方式使用它: ```vue [components/CustomButton.vue] <script setup> import { useI18n } from 'vue-i18n' const { t } = useI18n() </script> <template> <button> {{ t('button.about') }} </button> </template> ``` ## 模版文件 {#template-files} 新建某类布局 Markdown 文件的模版。(开发中) 新建 `scaffolds` 文件夹。 ```bash valaxy new <title> -l [layout] ``` - `layout`: 默认为 `post` 新建 `xxx.md`,xxx 取决于你的布局名称。 譬如 `album.md` 代表 `layout: album`。 ```bash valaxy new my-young -l album ``` ## 其他 {#others} - [自定义样式 | Valaxy](/zh/guide/custom/styles) ## 钩子 - **Categories**: custom ::: tip Valaxy 提供了钩子系统,以便你可以对生命周期的各个阶段进行定制。 ::: ## 生命周期 {#lifecycle} > 钩子的生命周期以排列顺序执行。 ### Build Time {#build-time} | Hook | Arguments | Description | | ---- | --------- | ----------- | | `options:resolved` | | 在 Valaxy 配置解析之后执行。| | `config:init` | | 在 Vite 配置初始化(根据 Valaxy Options 进行初始化)之后执行。| | `vue-router:extendRoute` | `route: EditableTreeNode` | 在扩展每个路由时执行(`.md` 页面的 frontmatter/excerpt 处理完成之后)。| | `vue-router:beforeWriteFiles` | `root: EditableTreeNode` | 在路由文件写入之前执行。| | `md:afterRender` | `ctx: MdAfterRenderContext` | 在 Markdown 页面加载并解析 frontmatter/excerpt 之后执行;插件可通过 `ctx.renderMarkdown` 复用已配置的渲染器和最终 Vite base。| | `build:before` | | 在构建开始之前执行。仅在 `valaxy build` 时触发。| | `build:after` | | 在构建完成之后执行。仅在 `valaxy build` 时触发。| | `content:before-load` | | `@experimental` 在所有 Content Loader 开始获取之前触发。| | `content:loaded` | | `@experimental` 在所有 Content Loader 完成之后触发。| ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ hooks: { 'config:init': () => { console.log('config:init') }, } }) ``` ### App Client {#app-client} Valaxy 目前没有客户端 hooks 系统。客户端扩展通过 `defineAppSetup` 完成,它提供 `AppContext`(包括 `app`、`router`、`routes` 等)用于定制 Vue 应用。 ```ts [setup/main.ts] import { defineAppSetup } from 'valaxy' export default defineAppSetup(({ app, router, routes }) => { // 安装 Vue 插件、注册全局组件等 }) ``` ## 自定义样式 - **Categories**: custom ## 自动样式注入 {#automatic-style-injection} ::: warning - `index.ts` / `index.scss` / `index.css` 不应当同时存在,否则可能会导致重复引入。 - 仅首次新建 styles/index.scss 文件时,需要重启开发服务器,以确保 scss 被加载。 ::: :::zh-CN 新建 `styles` 文件夹,目录下的以下文件将会被自动引入: - `index.ts` - `index.scss` - `index.css` - `css-vars.scss` (推荐在 `index.ts` 中自己引入 `xxx.scss`,后续可能会被弃用) 我们推荐您: - 新建 `index.ts` 文件,并在其中自由引入其他样式文件 `xxx.scss`。 ::: :::en Create `styles` folder, and the following files under the directory will be automatically imported: - `index.ts` - `index.scss` - `index.css` We recommend you: - Create `index.ts` file, and import other style files `xxx.scss` freely. - `index.ts` / `index.scss` / `index.css` should not exist at the same time, otherwise it may cause duplicate imports. ::: ## 自定义字体 {#custom-font} :::zh-CN 譬如你可以在 `styles/index.ts` 中覆盖默认的字体。 - `serif`: 衬线字体:<span font="serif">字体 abcd 123</span> - `sans`: 非衬线字体:<span font="sans">字体 abcd 123</span> - `mono`: 等宽字体:<span font="mono">字体 abcd 123</span> ::: :::en For example, you can override the default font in 'styles/index.ts'. - `serif`: serif font: <span font="serif">Font abcd 123</span> - `sans`: sans-serif font: <span font="sans">Font abcd 123</span> - `mono`: monospaced font: <span font="mono">Font abcd 123</span> ::: ```ts [styles/index.ts] import './vars.scss' ``` ```scss [styles/vars.scss] :root { --va-font-serif: 'Noto Serif SC', STZhongsong, STKaiti, KaiTi, Roboto, serif; --va-font-sans: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; --va-font-mono: Menlo, Monaco, Consolas, "Courier New", monospace; } ``` ## 示例 {#示例} ### 自定义光标 {#custom-cursor} 替换鼠标光标样式。 例如使用 [Material Design Cursors](https://www.deviantart.com/rosea92/art/Material-Design-Cursors-Dark-756850032)。 - `default`: 默认状态下图标。 - `pointer`: 指针(即链接状态下)图标。 - `text`: 文本选择图标。 新建 `styles/index.ts` 文件,引入 `vars.scss`: ```ts [styles/index.ts] import './vars.scss' ``` 新建 `styles/vars.scss` 文件: ```scss [styles/vars.scss] :root { --cursor-default: url('https://cdn.yunyoujun.cn/css/md-cursors/pointer.cur'); --cursor-pointer: url('https://cdn.yunyoujun.cn/css/md-cursors/link.cur'); --cursor-text: url('https://cdn.yunyoujun.cn/css/md-cursors/text.cur'); } body { cursor: var(--cursor-default), auto; } a { cursor: var(--cursor-pointer), auto; &:hover { cursor: var(--cursor-pointer), auto; } } button { cursor: var(--cursor-pointer), pointer; } input { cursor: var(--cursor-text), text; } ``` ### 覆盖暗色模式 需使用 `html.dark` 选择器包裹样式。 ```ts [styles/index.ts] import './vars.scss' ``` ```scss [styles/vars.scss] // 亮色 .yun-page-header-gradient { background: linear-gradient(to right, blue 0, rgba(0, 0, 0, 0.2) 100%); } // 覆盖 Dark Mode html.dark{ --va-c-bg-light:rgba(5, 16, 29, 0.8); .yun-page-header-gradient { background: linear-gradient(to right, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 100%); } .yun-footer-gradient { background: linear-gradient(to right, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 100%); } } ``` ## 自定义文章模板 - **Categories**: custom Valaxy 使用 [ejs](https://ejs.co/) 作为模板生成助手,你可以按照以下方式定义自己的模板: ## 在项目根目录中创建 scaffold 文件夹 {#create-a-scaffold-folder-in-your-project-root} ```shell $ mkdir scaffolds ``` ## 在 scaffolds 文件夹中创建自己的模板 {#create-your-own-template-to-the-scaffolds-folder} > **提示** > 你要创建的文件名将与使用命令创建文件时所需的布局名称相同: > `valaxy new --layout [layout] [filename]` ```shell $ touch scaffolds/post.md $ cat <<EOF > scaffolds/post.md --- layout: <%=layout%> title: <%=title%> date: <%=date%> --- Some additional descriptions EOF ``` ## CMS 集成 - **Categories**: third ::: warning 实验性功能 Content Loader 是一个实验性功能(`@experimental`),API 可能会在后续版本中发生变化。 ::: ## 介绍 Valaxy 支持通过 **Content Loader** 从外部 CMS 平台获取内容。Content Loader 在 Vite 启动之前运行,将远程内容写入 `.md` 文件,并自动集成到路由和 Markdown 处理管道中。 这意味着: - 来自 CMS 的内容与本地 `.md` 文件享有相同的功能(路由、搜索、RSS 等) - 无需修改主题或布局 - 支持增量缓存,只更新变化的内容 ## 工作原理 1. Content Loader 在 Vite 服务器/构建启动 **之前** 运行 2. 每个 Loader 从外部 CMS 获取内容,返回 `ContentItem[]` 3. 内容被写入 `.valaxy/content/pages/` 目录下的 `.md` 文件 4. 这些文件被 vue-router 的文件路由系统自动发现 5. 现有的 Markdown 处理、搜索索引和 RSS 生成等功能无需修改即可工作 ## 定义 Content Loader 使用 `defineContentLoader()` 创建一个 Content Loader: ```ts [loaders/my-cms.ts] import { defineContentLoader } from 'valaxy' export default defineContentLoader({ name: 'my-cms', async load(ctx) { // 从 CMS API 获取内容 const response = await fetch('https://api.my-cms.com/posts') const posts = await response.json() return posts.map(post => ({ path: `posts/${post.slug}.md`, content: [ '---', `title: ${post.title}`, `date: ${post.publishedAt}`, '---', '', post.body, ].join('\n'), })) }, // 可选:开发模式下每 30 秒轮询一次 devPollInterval: 30000, }) ``` ## 配置 在 `valaxy.config.ts` 中注册 Content Loader: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import myCmsLoader from './loaders/my-cms' export default defineValaxyConfig({ loaders: [myCmsLoader], }) ``` ### 通过插件使用 部分 Valaxy 插件会自动提供 Content Loader。使用这类插件时,无需手动配置 `loaders` —— 插件的 `setup()` 函数会自动注入 Loader: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { addonFeishu } from 'valaxy-addon-feishu' export default defineValaxyConfig({ addons: [ addonFeishu({ appId: process.env.FEISHU_APP_ID, appSecret: process.env.FEISHU_APP_SECRET, spaceId: 'your-wiki-space-id', }), ], }) ``` ## API 参考 ### ContentItem 表示一个从外部来源获取的内容项。 ### ContentLoaderContext 传递给 `load()` 函数的上下文对象。 ### ContentLoader 完整的 Loader 定义接口。 ```ts interface ContentItem { /** 相对于 pages/ 的路由路径,例如 'posts/my-post.md'。必须以 .md 结尾 */ path: string /** 完整的 markdown 内容,包含 YAML frontmatter */ content: string /** 可选的摘要值,用于增量缓存(未变化则跳过写入) */ digest?: string } interface ContentLoaderContext { node: ValaxyNode /** .valaxy/content/ */ cacheDir: string mode: 'dev' | 'build' } interface ContentLoader { name: string load: (ctx: ContentLoaderContext) => Promise<ContentItem[]> | ContentItem[] /** 开发模式轮询间隔(毫秒),undefined 表示不轮询 */ devPollInterval?: number /** 写入缓存前的逐项转换 */ transform?: (item: ContentItem) => ContentItem | Promise<ContentItem> } ``` ## 开发模式轮询 设置 `devPollInterval`(毫秒)后,Loader 会在开发模式下定期重新获取内容。这对于在编辑 CMS 内容时实现近实时预览非常有用。 ```ts defineContentLoader({ name: 'my-cms', load: async (ctx) => { /* ... */ }, devPollInterval: 60000, // 每 60 秒重新获取 }) ``` ::: tip 轮询仅在开发模式下生效。构建模式下只会获取一次内容。 ::: ## 增量缓存 Content Loader 使用基于 digest 的增量缓存机制: - 每个内容项的 MD5 摘要被记录在 manifest 文件中 - 下次加载时,如果摘要未变化,则跳过写入 - 不再存在于 Loader 输出中的旧文件会被自动清理 - 你也可以在 `ContentItem` 中提供自定义 `digest`(例如使用 CMS 的 revision ID) ## Transform 使用 `transform` 在写入文件之前对每个内容项进行转换: ```ts defineContentLoader({ name: 'my-cms', async load(ctx) { /* ... */ }, transform(item) { // 为每篇文章添加页脚 return { ...item, content: `${item.content}\n\n---\n\nFetched from My CMS`, } }, }) ``` ## Hooks Content Loader 提供了两个生命周期钩子: | Hook | 描述 | | --- | --- | | `content:before-load` | 在所有 Content Loader 开始获取之前触发 | | `content:loaded` | 在所有 Content Loader 完成之后触发 | ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig({ hooks: { 'content:before-load': () => { console.log('Content loading started...') }, 'content:loaded': () => { console.log('Content loading finished!') }, }, }) ``` ## 集成插件 以下 Valaxy 插件使用 Content Loader 集成了具体的 CMS 平台: - [valaxy-addon-feishu](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-feishu) — 从飞书/Lark 文档获取内容(`@experimental`) ## 参考 - [VitePress CMS 指南](https://vitepress.dev/guide/cms) — VitePress 的类似功能 - [GitHub Issue #294](https://github.com/YunYouJun/valaxy/issues/294) — Content Loader 的设计讨论 ## 第三方评论系统 - **Categories**: third 存在许多第三方评论系统,下面简要介绍下各评论系统集成方式。 > [第三方评论系统之我见](https://www.yunyoujun.cn/posts/third-party-comment-system) ## Waline {#waline} > [Waline](https://waline.js.org/) 是一个依赖服务端实现的评论系统,它可以托管在 Vercel 等平台上。 使用 [valaxy-addon-waline](https://github.com/YunYouJun/valaxy/blob/main/packages/valaxy-addon-waline/README.md) 集成。 > valaxy-addon-waline 是基于 Waline 的一个 Valaxy 插件。 > 除此之外,我们推荐您可以使用 [kotodama](https://github.com/YunYouJun/kotodama) 进行评论管理,它是一个基于 Waline 服务端实现的评论管理系统。 ### 安装 {#安装} ```bash npm i valaxy-addon-waline # pnpm add valaxy-addon-waline ``` ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { addonWaline } from 'valaxy-addon-waline' export default defineValaxyConfig({ // or write it in site.config.ts siteConfig: { // 启用评论 comment: { enable: true }, }, // 设置 valaxy-addon-waline 配置项 addons: [ addonWaline({ // Waline 配置项,参考 https://waline.js.org/reference/client/props.html serverURL: 'https://your-waline-url', }), ], }) ``` ## Utterances {#utterances} > [Utterances](https://utteranc.es/) 是一个基于 GitHub Issues 实现的评论系统。 它可以直接通过挂载 JS 脚本集成。 在博客根目录下新建 `App.vue`,添加挂载脚本: <<< @/../demo/yun/App.vue <<< @/../demo/yun/composables/use-utterances.ts ## 第三方集成 - **Categories**: third ## 搜索 {#search} ### 本地搜索(MiniSearch) {#local-search-minisearch} Valaxy 内置了基于 [MiniSearch](https://lucaong.github.io/minisearch/) 的本地搜索。它不依赖外部服务,适合作为文档站的默认本地搜索方案。 搜索索引会在 Valaxy dev/build 时生成。 ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ search: { enable: true, provider: 'local', }, }) ``` 如果某个页面不希望进入本地搜索索引,可以在页面 frontmatter 中设置 `search: false`。 ### 本地搜索(基于 fuse.js) {#local-search-based-on-fusejs} Valaxy 也支持基于 [fuse.js](https://fusejs.io/) 的离线搜索。需要 Fuse 特定匹配选项,或已经依赖 `valaxy-fuse-list.json` 时,可以继续使用它。 > `valaxy fuse` 默认会在 `public` 目录下生成 `valaxy-fuse-list.json`。 > 当 `search.provider` 为 `fuse` 时,执行 `valaxy build` 会自动执行 `valaxy fuse`。 #### 使用 {#setup} ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ search: { enable: true, provider: 'fuse', }, }) ``` 如果你想要使用全文搜索,可参考 [Options | fuse.js](https://www.fusejs.io/api/options.html) 进行设置。 譬如: ```ts [site.config.ts] import { defineSiteConfig } from 'valaxy' export default defineSiteConfig({ search: { enable: true, provider: 'fuse', }, fuse: { /** * 设置搜索的文件路径 */ // pattern: 'pages/**/*.md', options: { keys: ['title', 'tags', 'categories', 'excerpt', 'content'], /** * @default 0.6 * @see https://www.fusejs.io/api/options.html#threshold * 设置匹配阈值,越低越精确 */ // threshold: 0.6, /** * @default false * @see https://www.fusejs.io/api/options.html#ignoreLocation * 忽略位置 * 这对于搜索文档全文内容有用,若无需全文搜索,则无需设置此项 */ ignoreLocation: true, }, }, }) ``` - 你也可以在 `package.json` 中显式添加 fuse 生成脚本 ```json {7,9} [package.json] { "name": "yun-demo", "valaxy": { "theme": "yun" }, "scripts": { "build": "npm run build:ssg", "build:ssg": "valaxy build --ssg", "fuse": "valaxy fuse", "rss": "valaxy rss" }, "dependencies": { "valaxy": "latest", "valaxy-theme-yun": "latest" } } ``` ### Algolia 搜索 {#algolia-docsearch} Algolia 是一个在线第三方搜索服务,您需要自行申请相关 ID 和 Secret。 > [DocSearch](https://docsearch.algolia.com/) 申请通常只接受技术文档。 Valaxy 提供了一个快速集成插件 [valaxy-addon-algolia](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-algolia)(目前仅支持 DocSearch)。 ## 音乐播放器 {#music-player} > 由 [valaxy-addon-meting](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-meting) 插件提供,基于 [APlayer](https://github.com/DIYgod/APlayer) 与 [MetingJS](https://github.com/metowolf/MetingJS) 实现。 ::: warning 已迁移为插件 旧版核心的 `aplayer: true` frontmatter 开关已在 **v1.0 中移除**。音乐播放器现由 `valaxy-addon-meting` 插件提供——将其加入配置即可使用。 ::: 安装并启用插件: ```ts // valaxy.config.ts import { defineConfig } from 'valaxy' import { addonMeting } from 'valaxy-addon-meting' export default defineConfig({ addons: [ addonMeting({ // 设为 `global: true` 可在每个页面显示固定播放器 global: false, }), ], }) ``` 随后在文章中任意位置放入 `<meting-js>` 元素(例如网易云某首歌曲,`id` 为歌曲 ID): ```html <meting-js id="22736708" server="netease" type="song" theme="#C20C0C"> </meting-js> ``` > 提示:`aplayer: true` frontmatter 仍被插件支持,用于按页切换全局固定播放器的显隐。完整选项见 [插件 README](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-addon-meting)。 效果如下: <meting-js id="22736708" server="netease" type="song" theme="#C20C0C"> </meting-js> > More info see [Option | MetingJS](https://github.com/metowolf/MetingJS#option) ## 谷歌统计 {#google-statistics} > 可参见 [扩展 Client 上下文|自定义扩展](/zh/guide/custom/extend#%25E6%2589%25A9%25E5%25B1%2595-client-%25E4%25B8%258A%25E4%25B8%258B%25E6%2596%2587) 你可以通过直接使用 Vue 插件的方式引入谷歌统计。 譬如: - 安装依赖:`pnpm add vue-gtag-next` - 新建 `setup/main.ts`: ```ts [setup/main.ts] import { defineAppSetup } from 'valaxy' import { install as installGtag } from './gtag' export default defineAppSetup((ctx) => { installGtag(ctx) }) ``` - 新建 `setup/gtag.ts`: ```ts [setup/gtag.ts] import type { UserModule } from 'valaxy' import VueGtag, { trackRouter } from 'vue-gtag-next' export const install: UserModule = ({ isClient, app, router }) => { if (isClient) { app.use(VueGtag, { property: { id: 'G-1LL0D86CY9' }, }) trackRouter(router) } } ``` More info see [vue-gtag-next](https://github.com/MatteoGabriele/vue-gtag-next). ## Schema.org 和 OPG 用于 SEO - **Categories**: third ::: tip [OpenGraph or Scheme.org](https://stackoverflow.com/questions/6402528/opengraph-or-schema-org) - [The Open Graph protocol](https://ogp.me/) - [Schema.org](https://schema.org/) ::: 采用 [Schema.org](https://schema.org/) 标准,可以让搜索引擎更好地理解网站内容,从而提高网站在搜索结果中的排名。 基于 [@unhead/schema-org](https://unhead.unjs.io/docs/typescript/schema-org/guides/get-started/overview) 实现。 - Identity 采用了 Person (Personal Website or Blog) > [Person | @unhead/schema.org](https://unhead.unjs.io/docs/typescript/schema-org/guides/recipes/identity#person) ## Validators {#validators} - [Google 富媒体搜索结果测试](https://search.google.com/test/rich-results) - [架构标记验证器](https://validator.schema.org/) ## 使用 Vite/Vue 插件 - **Categories**: third Valaxy 兼容 Vite/Vue 插件,你可以参考以下示例进行使用。 ## 使用 Vite 插件 {#使用-vite-插件} ### 使用 vite-plugin-pwa {#使用-vite-plugin-pwa} ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' import { VitePWA } from 'vite-plugin-pwa' export default defineValaxyConfig<ThemeConfig>({ vite: { plugins: [ // https://vite-pwa-org.netlify.app/ VitePWA(), ], }, }) ``` ```ts [setup/main.ts] import { defineAppSetup } from 'valaxy' export default defineAppSetup(({ router, isClient }) => { router.isReady().then(async () => { if (!isClient) return const { registerSW } = await import('virtual:pwa-register') registerSW({ immediate: true }) }) }) ``` 更多配置请参考 [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa)。 ## 使用 Vue 插件 {#使用-vue-插件} ::: tip Valaxy 默认集成了 [`@vitejs/plugin-vue`](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue) 插件,如果你自定义插件 `@vitejs/plugin-vue` 的配置,你可以通过 `vue` 配置项进行配置。 可参见 [扩展配置](/zh/guide/config/extend.md#vitejs-plugin-vue)。 ::: 譬如使用 Element Plus,你可以在 `setup/main.ts` 中添加以下配置: ```ts [setup/main.ts] import ElementPlus from 'element-plus' import { defineAppSetup } from 'valaxy' import 'element-plus/lib/theme-chalk/index.css' export default defineAppSetup(({ app }) => { app.use(ElementPlus) }) ``` ## 主题 Yun 配置 - **Categories**: theme ## 主题类型 {#type} Yun 主题支持两种布局类型,通过 `themeConfig.type` 切换: - `nimbo`(默认):现代布局。顶部导航栏 + 首页 Banner 动画 + 全屏菜单(移动端)。 - `strato`:经典布局。左侧边栏 + 顶部导航栏,类似传统博客风格。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { type: 'nimbo', // 或 'strato' }, }) ``` ::: tip `strato` 对应 v1 版本的布局风格,`nimbo` 对应 v2 版本的布局风格。 在未来,Yun 主题的不同布局变更将以不同云的名称命名(如 cirro 卷云、cumulo 积云、alto 高云等)。 ::: ## 配色 {#colors} ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { colors: { /** * 主题色 * @default '#0078E7' */ primary: '#0078E7', }, }, }) ``` ## 导航栏 {#nav} 页面顶部的导航栏。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { nav: [ { text: '文章', link: '/posts/', icon: 'i-ri-article-line' }, { text: '友链', link: '/links/', icon: 'i-ri-link' }, ], }, }) ``` 每个 `NavItem` 包含以下属性: | 属性 | 类型 | 说明 | | --- | --- | --- | | `text` | `string` | 显示文本(支持 i18n key,如 `menu.posts`) | | `link` | `string` | 链接地址 | | `icon` | `string` | 图标名称,参见 [Icônes](https://icones.js.org/) | | `active` | `string` | 激活路由匹配模式 | ## 页面 {#pages} 显示在首页侧栏社交链接下方的页面入口。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { pages: [ { name: '友情链接', url: '/links/', icon: 'i-ri-link', color: 'dodgerblue', }, { name: '项目列表', url: '/projects', icon: 'i-ri-gallery-view', color: 'var(--va-c-text)', }, ], }, }) ``` | 属性 | 类型 | 说明 | | --- | --- | --- | | `name` | `string` | 页面名称 | | `url` | `string` | 页面链接 | | `icon` | `string` | 图标名称,参见 [Icônes](https://icones.js.org/) | | `color` | `string` | 图标颜色(CSS 值),默认 `var(--va-c-text)` | ## 侧边栏 {#sidebar} `docs` 布局会在页面左侧渲染这组导航。你可以为所有文档提供一份侧边栏, 也可以按路径前缀配置多份侧边栏。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { sidebar: { '/guide/': { base: '/guide/', items: [ { text: '指南', items: [ { text: '快速开始', link: 'getting-started' }, { text: '配置', link: 'config' }, ], }, { text: '进阶', collapsed: true, items: [ { text: '部署', link: 'deployment' }, ], }, ], }, }, }, }) ``` 在页面 frontmatter 中设置 `layout: docs` 即可启用。配置了 `collapsed` 的分组 可以折叠;`true` 表示初始收起,`false` 表示初始展开。 ## 页脚 {#footer} ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { footer: { since: 2022, cloud: { enable: true, // 页脚上方的流动云 }, icon: { enable: true, name: 'i-ri-heart-fill', animated: true, color: 'red', url: '', title: '', }, powered: true, // 显示 "Powered by Valaxy & valaxy-theme-yun" beian: { enable: false, icp: '', // 如 '苏ICP备xxxxxxxx号' icpLink: 'https://beian.miit.gov.cn/', police: '', // 公安网备案号 }, }, }, }) ``` ## 主题 Yun 自定义 - **Categories**: theme ## 编辑链接 {#edit-link} 为文章添加「在 GitHub 中编辑」链接。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { editLink: { pattern: 'https://github.com/user/repo/edit/main/:path', text: '在 GitHub 上编辑此页', }, }, }) ``` ## 大纲标题 {#outline-title} 目录标题文本。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { /** * @default 'On this page' */ outlineTitle: '本页目录', }, }) ``` ## 样式 {#styles} 通过创建 `styles/index.ts` 覆盖主题样式: ```ts [styles/index.ts] import './vars.scss' ``` ```scss [styles/vars.scss] :root { --yun-bg-img: url("https://example.com/bg.jpg"); --yun-sidebar-bg-img: url("https://example.com/sidebar.jpg"); --yun-c-cloud: pink; } ``` ## 主题 Yun 布局与视觉 - **Categories**: theme ## 文档布局 {#documentation-layout} 指南类页面可以使用 `docs` 布局。它会移除仅适用于博客的上一篇/下一篇导航与评论, 在左侧渲染文档导航,并在右侧保留当前页面目录。 ```md --- title: 快速开始 layout: docs --- ``` 通过 [`themeConfig.sidebar`](/zh/themes/yun/config#sidebar) 配置左侧导航。 ## 首页标语 {#banner} 首页的垂直交错排列文字效果。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { banner: { enable: true, title: '云游君的小站', // 手动分割 // title: ['云游君的', '小站'], // 支持 i18n // title: { 'zh-CN': '云游君的小站', en: ['Hello', 'World'] }, cloud: { enable: true, // 首页下方的流动云动画 }, // 自定义站点名称 CSS 类 siteNameClass: '', // 动画持续时间(仅 nimbo 模式) duration: 500, }, }, }) ``` 如果您想要更改云的色彩,请覆盖 CSS 变量 `--yun-c-cloud`: ```css :root { --yun-c-cloud: red; } ``` ## 背景图 {#bg-image} ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { bg_image: { enable: true, url: '/images/bg.jpg', dark: '/images/bg-dark.jpg', // 深色模式 opacity: 1, }, }, }) ``` 也可以通过 CSS 变量覆盖: ```css :root { --yun-bg-img: url("/images/bg.jpg"); --yun-sidebar-bg-img: url("/images/sidebar-bg.jpg"); } ``` ## 主题 Yun 功能组件与页面 - **Categories**: theme ## 公告 {#notice} 显示公告横幅。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { notice: { enable: true, hideInPages: false, // 是否在 /pages/[page] 中隐藏 content: '欢迎来到我的博客!', }, }, }) ``` ## 说说 {#say} 随机展示一句话。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { say: { enable: true, api: '', // 自定义 API 链接或 public/ 下的 JSON 路径 hitokoto: { enable: true, api: 'https://v1.hitokoto.cn', }, }, }, }) ``` ## 烟花 {#fireworks} 点击烟花效果,基于 [@explosions/fireworks](https://www.npmjs.com/package/@explosions/fireworks)。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { fireworks: { enable: true, colors: ['#66A7DD', '#3E83E1', '#214EC2'], }, }, }) ``` ## 文章卡片类型 {#types} 自定义文章类型标记,可配置图标和颜色。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { types: { link: { color: '#1890ff', icon: 'i-ri-link', }, bilibili: { color: '#FF8EB3', icon: 'i-ri-bilibili-line', }, }, }, }) ``` 然后在文章 frontmatter 中设置 `type`: ```md --- title: 我的视频 type: bilibili url: https://www.bilibili.com/video/xxx --- ``` ## 菜单 {#menu} 最右侧的自定义导航图标。 ```ts [valaxy.config.ts] import type { ThemeConfig } from 'valaxy-theme-yun' import { defineValaxyConfig } from 'valaxy' export default defineValaxyConfig<ThemeConfig>({ themeConfig: { menu: { custom: { title: 'Menu', url: '/', icon: 'i-ri-menu-line', }, }, }, }) ``` ## 友情链接页面 {#links} 新建 `pages/links/index.md`: ```md --- title: 我的小伙伴们 links: - url: https://www.yunyoujun.cn avatar: https://www.yunyoujun.cn/images/avatar.jpg name: 云游君 blog: 云游君的小站 desc: 希望能成为一个有趣的人。 color: "#0078e7" # 也可以是一个 JSON 链接 # links: https://friends.yunyoujun.cn/links.json random: true --- <YunLinks :links="frontmatter.links" :random="frontmatter.random" /> ``` ## 主题 Press 配置 - **Categories**: theme ## 主题配置参考 {#theme-config-reference} 下表列出 Press 自身的 `themeConfig` 选项。站点级配置,例如 `title`、`url`、`search`、`lastUpdated`,仍然放在 `siteConfig` 下。 | 选项 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `logo` | `string` | `''` | 顶部导航栏 Logo。通常使用 `/favicon.svg` 这样的 public 路径。 | | `colors.primary` | `string` | `'#0078E7'` | 主题色,会注入 Press SCSS 与 Valaxy 主题变量。 | | `nav` | `NavItem[]` | `[]` | 顶部导航链接与下拉分组。 | | `sidebar` | `Sidebar` | `[]` | 左侧边栏。支持分类名、显式树结构,以及按路径区分的多侧边栏。 | | `editLink.pattern` | `string` | Valaxy 文档仓库编辑地址 | 页底“编辑此页”链接模板。`:path` 会被替换为页面相对路径。 | | `editLink.text` | `string` | 多语言默认文案 | 自定义编辑链接文案。 | | `footer.message` | `string` | `undefined` | 页脚说明。允许 HTML。 | | `footer.copyright` | `string` | `undefined` | 页脚版权信息。允许 HTML。 | | `socialLinks` | `SocialLink[]` | `[]` | 导航栏中的图标链接。图标使用 UnoCSS 图标类,例如 `i-ri-github-line`。 | | `locales` | `Record<string, LocaleSpecificConfig>` | `undefined` | 语言切换器数据,以及每种语言的 `themeConfig` 覆盖。 | | `i18nRouting` | `boolean` | `false` | 切换语言时尽量保持当前路径。 | ## 首页 {#home-page} 使用 `layout: home`,并在 frontmatter 中配置 `hero` 与 `features`。 ```md [pages/index.md] --- layout: home title: Acme Docs hero: name: Acme text: 使用 Acme 更快构建 tagline: 安装、配置与扩展 Acme 所需的一切。 image: src: /logo.png alt: Acme Logo actions: - theme: brand text: 快速开始 link: /guide/getting-started type: fly - theme: alt text: 查看 GitHub link: https://github.com/acme/project features: - icon: i-logos:vitejs title: 快速 details: 基于 Vite 与 Valaxy。 - icon: i-logos:vue title: 可扩展 details: 可以直接在 Markdown 中使用 Vue 组件。 --- ``` `hero.image` 与 VitePress 的 `ThemeableImage` 格式一致,支持字符串、`{ src, alt }` 对象,以及 `{ light, dark, alt }` 对象。Press 不再注入默认的 Valaxy Logo;未配置 `hero.image` 时,首页不会显示主视觉图片。根绝对图片路径会自动适配 Vite 的 `base`。 `fly` 类型按钮的悬停图标读取 `siteConfig.favicon`,不再使用主题内硬编码资源。 当 `i18nRouting` 启用时,首页按钮中的内部链接会自动补齐当前语言前缀。 ## 页脚与编辑链接 {#footer-edit-link} 编辑链接会显示在文章页底部。`pattern` 中的 `:path` 会被替换为当前页面的相对路径。 ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ siteConfig: { lastUpdated: true, }, themeConfig: { editLink: { pattern: 'https://github.com/acme/project/edit/main/docs/:path', text: '编辑此页', }, footer: { message: 'Released under the MIT License.', copyright: 'Copyright (c) 2026 Acme.', }, }, }) ``` 如果某个页面不希望显示上一页/下一页导航,可以在页面 frontmatter 中设置 `nav: false`。 ## 页面布局 {#page-layouts} Press 提供以下常见布局: | 布局 | 用途 | | --- | --- | | `default` | 标准文档页 | | `home` | 带 hero 和 features 的首页 | | `posts` | 文章列表页 | | `post` | 博客文章详情页 | | `tags` | 标签归档页 | | `404` | 404 页面 | 归档页示例: ```md [pages/posts/index.md] --- title: Posts layout: posts --- ``` ```md [pages/tags/index.md] --- title: Tags layout: tags --- ``` ## 样式 {#styling} 通过 `themeConfig.colors.primary` 设置主题色: ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { colors: { primary: '#0078E7', }, }, }) ``` 也可以在自己的样式文件中覆盖 Press CSS 变量: ```scss [styles/index.scss] :root { --pr-nav-height-mobile: 56px; --pr-nav-text: var(--va-c-text-1); } ``` ## 组件自定义 {#component-customization} 和其他 Valaxy 主题一样,你可以在站点中创建同名组件来覆盖 Press 的默认组件。常见扩展点包括: | 组件 | 用途 | | --- | --- | | `PressHomeHero.vue` | 首页 hero | | `PressHomeFeatures.vue` | 首页功能网格 | | `PressNavBar.vue` | 顶部导航栏 | | `PressSidebar.vue` | 左侧边栏 | | `PressDocFooter.vue` | 编辑链接与上一页/下一页 | | `PressArticle.vue` | 文档文章容器 | 更底层的主题开发细节可参考 [编写主题](/zh/themes/write)。 ## 从 VitePress 迁移到主题 Press - **Categories**: theme Press 刻意保留了许多 VitePress 用户熟悉的体验,但它不是 `.vitepress/config.ts` 的直接替代品。 - 将站点和主题配置迁移到 `valaxy.config.ts`、`site.config.ts` 或 `theme.config.ts`。 - 将内容放入 Valaxy 的 `pages/` 目录。 - 使用 Valaxy frontmatter,例如 `categories`、`layout`、`search`。 - 通过 `siteConfig.search.provider` 配置搜索。 - 需要 Algolia、Git 贡献者、评论、音乐等集成时,使用 Valaxy 插件。 ## 常见映射 {#common-mappings} | VitePress | Valaxy + Press | | --- | --- | | `.vitepress/config.ts` | `valaxy.config.ts`、`site.config.ts` 或 `theme.config.ts` | | `title`、`description` | `siteConfig.title`、`siteConfig.description` | | `themeConfig.logo` | `themeConfig.logo` | | `themeConfig.nav` | `themeConfig.nav` | | `themeConfig.sidebar` | `themeConfig.sidebar` | | `themeConfig.socialLinks` | `themeConfig.socialLinks` | | `themeConfig.editLink` | `themeConfig.editLink` | | `themeConfig.footer` | `themeConfig.footer` | | `lastUpdated` | `siteConfig.lastUpdated` | | `locales` | `themeConfig.locales` 与 `siteConfig.languages` | | `themeConfig.search.provider: 'local'` | `siteConfig.search.provider: 'local'` | | `.vitepress/theme` 自定义布局/组件 | 在 Valaxy 站点中创建同名组件覆盖 | | VitePress 插件 | Valaxy 插件,或 `valaxy.config.ts` 中的 Vite 插件 | ## 侧边栏说明 {#sidebar-notes} Press 支持 VitePress 风格的 sidebar 数组、按路径区分的多侧边栏,以及 `{ base, items }` 对象: ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { sidebar: { '/guide/': { base: '/guide/', items: [ { text: '指南', items: [ { text: '介绍', link: '' }, { text: '快速开始', link: 'getting-started' }, ], }, ], }, }, }, }) ``` Valaxy 特有的补充是:顶层字符串(如 `'guide'`)仍然会根据页面 `categories` 自动展开。 ## 主题 Press 搜索与多语言 - **Categories**: theme ## 搜索 {#search} 当 `siteConfig.search.enable` 为 `true` 时,Press 会显示搜索入口。 多数文档站建议使用基于 MiniSearch 的本地搜索: ```ts [valaxy.config.ts] export default defineValaxyConfig({ siteConfig: { search: { enable: true, provider: 'local', }, }, }) ``` Press 也支持 Valaxy 的 Fuse 搜索: ```ts export default defineValaxyConfig({ siteConfig: { search: { enable: true, provider: 'fuse', }, }, }) ``` 如果要使用 Algolia DocSearch,将 provider 设置为 `algolia` 并安装 Algolia 插件: ```ts [valaxy.config.ts] import { defineValaxyConfig } from 'valaxy' import { addonAlgolia } from 'valaxy-addon-algolia' export default defineValaxyConfig({ siteConfig: { search: { enable: true, provider: 'algolia', }, }, addons: [ addonAlgolia({ appId: 'YOUR_APP_ID', apiKey: 'YOUR_SEARCH_API_KEY', indexName: 'YOUR_INDEX_NAME', }), ], }) ``` 如果某个页面不希望进入本地搜索索引,可以在页面 frontmatter 中设置 `search: false`。 ## 多语言站点 {#i18n} 使用 `locales` 配置语言切换器和每种语言的主题配置覆盖。启用 `i18nRouting` 后,切换语言时会尽量保持当前路径。 ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ siteConfig: { languages: ['en', 'zh-CN'], }, themeConfig: { i18nRouting: true, locales: { root: { label: 'English', lang: 'en', }, zh: { label: '简体中文', lang: 'zh-CN', link: '/zh/', themeConfig: { nav: [ { text: '指南', link: '/zh/guide/getting-started' }, ], sidebar: { '/zh/guide/': { base: '/zh/guide/', items: [ { text: '指南', items: [ { text: '快速开始', link: 'getting-started' }, ], }, ], }, }, editLink: { pattern: 'https://github.com/acme/project/edit/main/docs/:path', text: '编辑此页', }, }, }, }, }, }) ``` 翻译页面放在对应语言前缀下: ```txt pages/ ├── guide/getting-started.md └── zh/guide/getting-started.md ``` ## 主题 Press 导航栏与侧边栏 - **Categories**: theme ## 导航栏 {#nav} `themeConfig.nav` 控制顶部导航。导航项可以是直接链接,也可以是下拉分组。 ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { nav: [ { text: '指南', link: '/guide/getting-started' }, { text: '生态', items: [ { text: '插件', link: '/addons/' }, { text: '主题', link: '/themes/' }, ], }, ], }, }) ``` `text` 可以是普通文本,也可以是 `locales/*.yml` 中的 i18n key。 ## 侧边栏 {#sidebar} Press 支持与 VitePress 相近的配置风格:数组表示单一侧边栏,对象表示按路径前缀区分的多侧边栏。 ### 基于分类自动生成 {#category-sidebar} 将分类名称加入 `themeConfig.sidebar`,再在页面 frontmatter 中使用相同的 `categories`: ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { sidebar: ['guide', 'reference'], }, }) ``` ```md [pages/guide/getting-started.md] --- title: 快速开始 categories: - guide --- ``` 这种方式适合偏博客或知识库式的文档,页面顺序可由生成列表决定。 ### 显式树结构 {#explicit-tree} 需要精确排序、嵌套分组、外部链接或可折叠分组时使用显式树结构: ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { sidebar: [ { text: '指南', collapsed: false, items: [ { text: '快速开始', link: '/guide/getting-started' }, { text: '配置', link: '/guide/config' }, { text: '进阶', collapsed: true, items: [ { text: 'Markdown', link: '/guide/markdown' }, { text: '部署', link: '/guide/deploy' }, ], }, ], }, ], }, }) ``` 子目录应该嵌套在任意 `SidebarItem` 的 `items` 中。省略 `collapsed` 时,Press 会把子项渲染为始终展开的缩进列表;只有需要显示可折叠箭头时,才配置 `collapsed: true` 或 `collapsed: false`。 `docFooterText` 可以自定义上一页/下一页导航中展示的标题: ```ts const item = { text: '配置', link: '/guide/config', docFooterText: '配置 Valaxy', } ``` ### 多侧边栏 {#multiple-sidebars} 当文档存在多个子目录时,可以使用以路径为 key 的对象。Press 会按当前路由选择最长匹配的路径前缀。 ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { sidebar: { '/guide/': [ { text: '指南', items: [ { text: '介绍', link: '/guide/' }, { text: '快速开始', link: '/guide/getting-started' }, ], }, ], '/api/': [ { text: 'API 参考', items: [ { text: '配置', link: '/api/config' }, { text: '方法', link: '/api/methods' }, ], }, ], }, }, }) ``` ### Base Path {#base-path} 使用 `base` 可以避免重复书写共同路径前缀。这个格式与 VitePress 的 sidebar object 格式一致。 ```ts [valaxy.config.ts] export default defineValaxyConfig<PressTheme.Config>({ themeConfig: { sidebar: { '/guide/': { base: '/guide/', items: [ { text: '指南', items: [ { text: '介绍', link: '' }, { text: '快速开始', link: 'getting-started' }, { text: '配置', link: 'config' }, ], }, ], }, }, }, }) ``` 当侧边栏数组包含没有 `items` 的顶层链接时,Press 会把连续的链接收拢为匿名分组,以贴近 VitePress 默认侧边栏的渲染模型。