ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

QOwnNotes 官网全新改版实录:VuePress 2 + Vue.js 3 + Vuetify 3 技术栈重构解析

QOwnNotes 官网全新改版实录:VuePress 2 + Vue.js 3 + Vuetify 3 技术栈重构解析
  • 桌面应用

【免费下载链接】QOwnNotes

QOwnNotes is a plain-text file notepad and todo-list manager with Markdown support and Nextcloud / ownCloud integration.

项目地址:https://gitcode.com/gh_mirrors/qo/QOwnNotes
点击查看免费下载

本文以 QOwnNotes 项目仓库中发布的 改版公告文档 为主体,结合 webpage 目录下的真实源码、配置与测试,系统拆解这次官网重构的动机、技术选型、站点架构与工程实践。读完你将掌握:VuePress 2 与 Vue 3 时代如何组织多语言静态站点、Vuetify 3 如何与 VuePress 客户端入口协同、博客内容流水线与 SEO/Feed 插件如何配置,以及基于 Playwright 的端到端测试和 Docker/nginx 部署流水线是如何落地的。

为什么需要这次改版

QOwnNotes 官网(承载产品文档、安装指南、脚本 API 文档与官方博客)此前长期运行在VuePress 1之上。随着 Web 技术生态的演进,VuePress 1 已经进入生命周期终点(end of life),不再获得维护与安全更新,因此向下一代框架迁移既是必要的技术升级,也是一次时机合适的整体翻新。

这次改版是 QOwnNotes 项目的第 #3414 号工作项(见公告原文)的直接产出,核心目标有三:让站点跟上现代前端技术栈、改善性能与可访问性、为读者提供更现代的使用体验。

新版技术栈概览

公告文档给出了新官网的依赖全景:

{ "vuepress": "next (v2.x)", "vue": "^3.x (via VuePress 2)", "vuetify": "^3.7.4", "@vuepress/bundler-vite": "next", "@vuepress/plugin-docsearch": "next", "@vuepress/plugin-feed": "next", "@vuepress/plugin-seo": "next", "@vuepress/plugin-sitemap": "next" }

对照仓库中的 webpage/package.json 可以看到实际锁定版本比公告更进一步:vuetify已推进到3.11.5,vuepress、@vuepress/bundler-vite及 docsearch、feed、seo、sitemap 等官方生态插件均以next(v2 分支)版本引入,并额外加入了:

  • Markdown 增强插件族:@vuepress/plugin-markdown-chart(Mermaid 图表)、markdown-container、markdown-ext(任务列表)、markdown-hint、markdown-stylize;
  • 主题与样式工具:@vuepress/theme-default、sass、@mdi/font(Material Design 图标字体)、vuepress-plugin-md-enhance;
  • 内容与测试设施:markdown-it+markdown-it-meta(frontmatter 元数据解析)、date-fns(日期格式化)、mermaid、@playwright/test与playwright-core(版本1.57.0)。

VuePress 2:构建与插件体系的代际升级

公告将 VuePress 2 的优势概括为五点:Vite 驱动的打包带来更快的构建速度与开发期热更新(HMR);更完善的插件系统提升可扩展性与可维护性;增强的 Markdown 支持带来更好的语法高亮与扩展能力;更好的 TypeScript 支持;以及优化后的生产构建(代码分割与懒加载)。

这些特性在仓库配置中有直接落点。webpage/src/.vuepress/config.js 中通过defineUserConfig显式选择viteBundler作为打包器,并做了三处针对 SSR 与静态产物的关键调优:

// 关闭 SSR 预加载,规避 CSS 导入问题 shouldPreload: false, shouldPrefetch: false, bundler: viteBundler({ viteOptions: { ssr: { noExternal: ["vuetify"], // 让 Vuetify 参与 SSR 编译 }, build: { rollupOptions: { output: { // 关闭资源文件名的 hash 后缀,输出固定 assets/ 目录 assetFileNames: ..., }, }, }, }, }),

从源码结构看,这三项配置分别解决:Vuetify 组件在服务端渲染时无法外部化的兼容问题、静态资源路径稳定性(利于 CDN 与缓存策略)以及预加载对页面首屏的干扰。这正是公告中“Vite 打包 + 优化生产构建”的实际工程体现。

Vue.js 3:以 Composition API 重写自定义组件

公告指出 Vue 3 带来的核心收益包括Composition API(更灵活、可复用的组件逻辑)、更快的虚拟 DOM 与响应式系统、通过 tree-shaking 与优化运行时实现的更小包体积、原生 TypeScript 支持以及更好的开发者体验。

Vue 3 的能力直接作用于站点客户端运行时。webpage/src/.vuepress/client.ts 是 VuePress 2 的客户端入口,它做了三件事:

  1. 创建并挂载 Vuetify 实例:通过createVuetify({ components, directives, theme: { defaultTheme: 'light' } })组装 Vuetify,并在app.use(vuetify)中注册,站点内的v-*组件(如v-snackbar、v-chip、v-carousel、v-icon)全部由此驱动;
  2. 全局注册自研组件:Poll(用户调查浮层)、RssBlock(RSS 订阅入口)、BlogDate(博客日期)、BlogIndex(博客列表)、ProfileCard、ScreenshotCarousel(截图轮播)六个组件通过app.component(...)全局可用;
  3. 以原生脚本替代插件实现 Matomo 埋点:原先的vuepress-plugin-matomo被移除,改在enhance钩子中手动初始化window._paq,并通过router.afterEach在 SPA 路由切换时同步上报自定义 URL 与标题。

其中Poll.vue、RssBlock.vue、ScreenshotCarousel.vue等组件大量使用<script setup>组合式语法与computed、ref、onMounted、nextTick等 Composition API,是 Vue 3 新编程模型在真实站点中的典型示范。

Vuetify 3:Material Design 3 与可访问性

公告将 Vuetify 3 的价值归纳为Material Design 3 组件、更强的 ARIA 与键盘导航支持、全尺寸响应式布局、基于 CSS 变量的可定制主题以及更丰富的组件库。

仓库中的证据包括:

  • ScreenshotCarousel.vue 使用v-carousel+v-carousel-item构建官网首页截图轮播,并通过onMounted+nextTick+ 延迟挂载规避 SSR 时序问题;
  • Poll.vue 使用v-snackbar、v-text-field、v-btn构建底部调查问卷,提交后通过window._paq.trackEvent上报到 Matomo;
  • Layout.vue 在页脚使用v-icon(mdi-github、mdi-mastodon)渲染社交图标;
  • styles/index.scss 中针对 Vuetify 的全局 reset 做了补偿(p, ul, ol { margin: 16px 0; }),并为阿拉伯语、波斯语等 RTL 语言补齐完整的镜像布局规则——这正是“响应式 + 可访问性 + 可定制主题”在真实站点上的落地。

值得留意的是,Vuetify 与 VuePress 的结合存在 SSR 兼容问题,仓库通过ssr.noExternal: ["vuetify"](见 config.js)将 Vuetify 纳入 SSR 编译链解决,Dockerfile 中甚至保留了一段被注释的patch-vuetify-ssr.sh补丁脚本作为备选方案,可见官方团队在集成过程中做过反复验证。

多语言站点架构

改版后的官网是一套11 语种的多语言静态站点。VuePress 的locales配置在 config.js 中声明了/(英语)、/de/、/it/、/fr/、/es/、/fa/、/nl/、/hu/、/ar/、/pl/、/ko/共 11 个语言目录,每个语言分别维护独立的导航栏(navbar)与侧边栏(sidebar)文案。

侧边栏的自动生成由 utils.js 完成:getChildren用globSync扫描src/**/*.md,通过markdown-it-meta读取每篇文档 frontmatter 中的order字段,再按order与path排序输出导航项;getBlogChildrenWithTitles则在博客侧边栏中提取每篇文章的 H1 标题作为显示文本。也就是说,调整文档顺序只需改 frontmatter 的order值,无需手工维护导航清单。

RTL 语言(阿拉伯语、波斯语)的排版由 styles/index.scss 中的html[dir="rtl"]规则统一处理,涵盖侧边栏、代码块、页码导航、搜索框等全部布局的镜像翻转。

博客内容流水线

博客是本次改版的重点场景之一,其内容管理链路值得单独梳理:

  1. 元数据声明:每篇博客以---frontmatter 声明title、description、image、date、order,例如本文依据的改版公告即位于 webpage/src/blog/2025-12-30-QOwnNotes-Webpage-Relaunch-with-VuePress-2-Vue-3-and-Vuetify-3.md;
  2. 索引生成:generate-blog-data.js 在开发启动与生产构建前自动运行,扫描src/blog/**/*.md,从文件名提取YYYY-MM-DD日期、从 frontmatter 提取标题与描述,生成src/.vuepress/blog-data.json(该文件被 git 忽略、每次构建重新生成);
  3. 列表渲染:BlogIndex.vue 读取blog-data.json渲染文章列表,BlogDate.vue 负责将 frontmatter 中的日期统一格式化为yyyy-MM-dd;
  4. Feed 输出:feedPlugin同时开启rss、atom、json三种格式(输出rss.xml、feed.atom、feed.json),并通过filter限定只收录blog/目录下的文章,按日期倒序排列。

这条链路让博客作者只需维护 Markdown 文件本身,索引、列表、订阅源全部自动化生成。

SEO 与站点元信息

改版对搜索引擎与社交分享的投入体现在 config.js 的插件组合上:

  • DocSearch(Algolia):docsearchPlugin配置了appId、apiKey、indexName: "qownnotes",并为全部 11 种语言提供了搜索框占位符与按钮文案的本地化翻译,实现全站文档搜索;
  • SEO 插件:seoPlugin设定hostname与canonical,并通过ogp回调为每篇博客生成 Open Graph 与 Twitter Card 标签(og:image、twitter:card: summary_large_image、twitter:creator等),图片地址优先取 frontmatter 中的image字段;
  • Sitemap 插件:sitemapPlugin生成sitemap.xml并排除404.html;
  • head 元信息:包括theme-color、apple-mobile-web-app系列、favicon、Material Design 图标字体 CDN 以及 RSS 订阅入口。

对应的端到端断言可在 tests/head-tags.spec.js 中看到,测试逐项校验 meta description、og:title、twitter:card、favicon、三种 Feed 链接与rel="me"社交验证链接;tests/algolia.spec.js 则验证首页、概述页与博客页的搜索弹窗可正常唤起。

端到端测试与质量保障

公告将“用 Playwright 实现全面的端到端测试”列为改版工作之一,仓库中的测试设施非常完整:

  • playwright.config.js 定义了Chromium 与 Firefox 双浏览器测试项目,并针对 CI 环境自动放大超时(timeoutMultiplier = 3)、启用失败重试(CI 下 2 次);若环境未提供CHROMIUM_BIN/FIREFOX_BIN,会在启动阶段自动下载安装浏览器;
  • config.js 通过TEST_ENV环境变量切换测试目标,支持prod(www.qownnotes.org)、next(next.qownnotes.org)、local(localhost:8080)与local-docker(localhost:8081)四套环境,fixtures.js将其注入 Playwright fixture;
  • package.json 提供playwright:test系列命令,并区分--ui交互模式与local/next/local-docker环境变体。

测试用例覆盖五个维度:页面可达性与标题(basic.spec.js)、博客列表结构与具体文章渲染(blog.spec.js)、SEO 头部标签(head-tags.spec.js)、Matomo 埋点请求是否真实上报且携带idsite与url参数(matomo.spec.js)、RSS/Atom/JSON Feed 结构与内容(rss.spec.js)、sitemap 可访问性(sitemap.spec.js)以及多语言页面的导航、正文与lang属性(translations.spec.js),另有 mermaid、carousel、survey-popup 等专项测试。

构建与部署流水线

新站点的工程化部署链路分为开发、构建、容器化三层:

开发启动:npm run dev调用 run-dev.sh,该脚本依次完成三项预处理——把../screenshots截图与根目录CHANGELOG.md拷贝进 VuePress 的public资源目录、运行node scripts/generate-blog-data.js生成博客索引、最后以npx vuepress dev src启动开发服务器(默认http://0.0.0.0:8080/)。按 webpage/README.md 的说明,源码修改会自动热更新到页面。

生产构建:npm run build产出静态站点到webpage/src/.vuepress/dist(构建脚本在 package.json 中声明为./scripts/run-build.sh)。

容器化部署:多阶段 Dockerfile 以node:22-alpine为构建基座,通过npm ci依据 lockfile 精确安装依赖,构建完成后将src/.vuepress/dist拷贝进nginx:latest,同时写入org.flathub.VerifiedApps.txt的 Well-Known 校验文件;docker-compose.yml 提供dev(8080 端口开发服务)与web(8081 端口生产站点,容器名qownnotes-webpage)两个服务。

反向代理:nginx.conf 承担三件事:把qownnotes.org/docs.qownnotes.org301 跳转到www.qownnotes.org;用map规则将旧版站点的历史 URL(如/changelog/QOwnNotes、/en/latest/...文档路径)重定向到新版对应页面,避免链接失效;将/api/与rss/project_version反向代理到qownnotes-api后端。此外 justfile 提供了dev、build、clean、install、playwright-test等一组便捷命令。

小结

这次官网改版不是一次简单的脚手架升级,而是一次围绕VuePress 2 + Vue.js 3 + Vuetify 3的体系化重建:以 Vite 打包与 Vue 3 Composition API 重构站点运行时,以 Vuetify 3 统一视觉与交互,以 11 语种 locales 与 RTL 样式支撑国际化,以 frontmatter 驱动的流水线管理博客内容,以 DocSearch/SEO/Sitemap/Feed 插件完善站点曝光,再以 Playwright 双浏览器端到端测试与 Docker + nginx 流水线保障发布质量。对于计划将存量 VuePress 1 站点迁移到 VuePress 2、或希望在同一套技术栈下搭建多语言文档站 + 博客的团队,QOwnNotes 官网的这份源码实现是一份相当完整、可直接参照的工程范本。

  • 桌面应用

【免费下载链接】QOwnNotes

QOwnNotes is a plain-text file notepad and todo-list manager with Markdown support and Nextcloud / ownCloud integration.

项目地址:https://gitcode.com/gh_mirrors/qo/QOwnNotes
点击查看免费下载
上一篇:RPCS3 汉化补丁 3 步安装指南:让 PS3 游戏秒变中文
下一篇:3个颠覆性理由:为什么你的下一个看板必须是Nullboard

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进