指南:基于動態(tài)路由與數(shù)據(jù)加載的內(nèi)容集成方案)
VitePress 連接無頭 CMS 實戰(zhàn)指南基于動態(tài)路由與數(shù)據(jù)加載的內(nèi)容集成方案【免費(fèi)下載鏈接】vitepressVite Vue powered static site generator.項目地址: https://gitcode.com/gh_mirrors/vi/vitepress在 VitePress 中站點內(nèi)容默認(rèn)來自倉庫內(nèi)的 Markdown 文件而無頭 CMSHeadless CMS將內(nèi)容與前端解耦通過 API 提供數(shù)據(jù)。本指南基于 VitePress 官方文檔 的《連接到 CMS》一章結(jié)合倉庫源碼dynamicRoutesPlugin.ts與端到端測試dynamic-routes 測試系統(tǒng)講解如何利用動態(tài)路由Dynamic Routes與構(gòu)建時數(shù)據(jù)加載把 VitePress 接入任意無頭 CMS如 Storyblok、Contentful、Sanity 等在構(gòu)建時拉取遠(yuǎn)程內(nèi)容并生成靜態(tài)頁面。讀完本文你將掌握環(huán)境變量安全注入、路徑加載器編寫、$params參數(shù)消費(fèi)與!-- content --原始內(nèi)容注入的完整鏈路。前置知識為什么 CMS 集成圍繞動態(tài)路由展開將 VitePress 連接到 CMS 主要圍繞動態(tài)路由展開。動態(tài)路由允許用單個 Markdown 文件配合動態(tài)數(shù)據(jù)生成大量頁面例如創(chuàng)建一個posts/[id].md就能為 CMS 中的每一篇文章生成對應(yīng)的靜態(tài)頁面其中[id]是區(qū)分每個頁面的路由參數(shù)。由于 VitePress 是靜態(tài)站點生成器必須在構(gòu)建時確定所有可能的頁面路徑因此動態(tài)路由頁面必須伴隨一個路徑加載文件paths loader file對于posts/[id].md需要同級提供posts/[id].paths.js也支持.ts、.mjs、.mts. └─ posts ├─ [id].md # 路由模板 └─ [id].paths.js # 路由路徑加載器路徑加載器以默認(rèn)導(dǎo)出一個帶paths方法的對象paths方法返回具有params屬性的對象數(shù)組每個對象對應(yīng)生成一個頁面。關(guān)于動態(tài)路由的完整語法多參數(shù)、動態(tài)生成路徑、路徑重寫等請參閱路由指南。一般工作流三步完成 CMS 接入每個 CMS 的工作方式各不相同官方文檔只提供一個通用工作流需要根據(jù)具體場景認(rèn)證方式、API 形態(tài)、內(nèi)容結(jié)構(gòu)進(jìn)行調(diào)整。整個流程可分為三步注入憑證 → 拉取數(shù)據(jù) → 渲染內(nèi)容。第一步通過.env安全注入 API Token如果 CMS 需要身份驗證請創(chuàng)建一個.env文件存放 API token并在路徑加載器中使用 VitePress 從 Vite 復(fù)導(dǎo)出的loadEnv讀取它。loadEnv在 src/node/index.ts 中被導(dǎo)出export { loadEnv, type Plugin } from vite因此可以直接從vitepress包導(dǎo)入// posts/[id].paths.js import { loadEnv } from vitepress const env loadEnv(, process.cwd()).env文件示例MY_CMS_API_TOKENyour-super-secret-token MY_CMS_ENDPOINThttps://my-cms-api.example.comloadEnv(, process.cwd())會以當(dāng)前工作目錄為基準(zhǔn)加載環(huán)境變量第一個參數(shù)是環(huán)境前綴傳空字符串表示加載全部第二個參數(shù)是解析.env文件的根目錄。這樣API token 就不會被硬編碼進(jìn)源碼也避免了被提交到版本庫的風(fēng)險記得把.env加入.gitignore。第二步從 CMS 拉取數(shù)據(jù)并格式化為路徑數(shù)據(jù)在路徑加載器中調(diào)用 CMS 的 API把返回的數(shù)據(jù)映射為{ params, content }結(jié)構(gòu)。這里可以使用任何 HTTP 客戶端Node.js 18 內(nèi)置的fetch即可勝任// posts/[id].paths.js import { loadEnv } from vitepress const env loadEnv(, process.cwd()) export default { async paths() { // 如有需要使用相應(yīng)的 CMS 客戶端庫 const data await (await fetch(${env.MY_CMS_ENDPOINT}/posts, { headers: { // 如有必要可使用 token Authorization: Bearer ${env.MY_CMS_API_TOKEN} } })).json() return data.map(entry { return { params: { id: entry.id, title: entry.title, author: entry.author, date: entry.date }, content: entry.content } }) } }params路由參數(shù)其中id會被用來替換[id]生成實際路徑其余字段作為附加數(shù)據(jù)傳遞給頁面content文章正文原始 Markdown 或 HTML通過!-- content --注入到頁面模板中詳見下文。路徑加載器模塊在Node.js 環(huán)境、僅構(gòu)建期間執(zhí)行這與構(gòu)建時數(shù)據(jù)加載一致因此可以按需使用 Node API 和任意 npm 依賴也可以對數(shù)據(jù)做排序、過濾、分頁等預(yù)處理。第三步在頁面模板中渲染內(nèi)容posts/[id].md路由模板中使用$params全局屬性消費(fèi)參數(shù)可在 Vue 插值表達(dá)式中訪問并通過!-- content --特殊注釋把 CMS 返回的正文渲染為 Markdown 文件本身的一部分# {{ $params.title }} - by {{ $params.author }} on {{ $params.date }} !-- content --構(gòu)建后每一條 CMS 數(shù)據(jù)都會生成一個對應(yīng)頁面例如id為hello-world的文章會生成posts/hello-world.html頁面標(biāo)題、作者、日期與正文全部來自 CMS。源碼級原理content注入與$params的底層實現(xiàn)!-- content --如何工作!-- content --并非 VitePress 的通用語法而是動態(tài)路由插件專為 CMS 集成設(shè)計的一種注入標(biāo)記。在插件load鉤子中當(dāng)請求的文件命中已解析的動態(tài)路由時會讀取路由模板文件并用路徑數(shù)據(jù)中的content替換該標(biāo)記// src/node/plugins/dynamicRoutesPlugin.ts let baseContent await readTextFile(routeFile) // inject raw content // this is intended for integration with CMS // we use a special injection syntax so the content is rendered as // static local content instead of included as runtime data. if (content) { baseContent baseContent.replace( /!--\s*content\s*--/, content.replace(/\$/g, $$$) ) }源碼注釋明確指出這種做法的目的是讓遠(yuǎn)程內(nèi)容像本地靜態(tài)內(nèi)容一樣被渲染而不是作為運(yùn)行時數(shù)據(jù)打進(jìn)客戶端 bundle。注意content.replace(/\$/g, $$$)對$符號做了轉(zhuǎn)義避免內(nèi)容中的$與替換字符串語義沖突。如果路由模板中沒有!-- content --標(biāo)記該替換不會生效content會被靜默忽略。$params與客戶端 payload 的大小權(quán)衡插件同樣在load鉤子中把參數(shù)序列化注入到模塊內(nèi)容頭部// src/node/plugins/dynamicRoutesPlugin.ts // params are injected with special markers and extracted as part of // __pageData in ../markdownToVue.ts return __VP_PARAMS_START${JSON.stringify(params)}__VP_PARAMS_END__${baseContent}這些帶特殊標(biāo)記的參數(shù)隨后在 markdownToVue.ts 中被提取為__pageData的一部分最終在瀏覽器端通過$params模板全局變量或useData()運(yùn)行時 APIconst { params } useData()訪問。關(guān)鍵注意事項傳遞給params的數(shù)據(jù)會在客戶端 JavaScript payload 中被序列化因此避免在參數(shù)中傳遞大量數(shù)據(jù)例如從 CMS 拉取的整篇 Markdown 或 HTML 正文。這正是content屬性存在的意義——正文走靜態(tài)注入輕量元數(shù)據(jù)標(biāo)題、作者、日期、標(biāo)簽走params。更多運(yùn)行時 API 細(xì)節(jié)參見運(yùn)行時 API 參考。路徑加載器查找與defineRoutes插件通過正則dynamicRouteRE /\[(\w?)\]/g識別含[param]片段的 Markdown 文件并為每個動態(tài)路由嘗試查找[id].paths.js/.ts/.mjs/.mts四種后綴的加載器文件缺失時會輸出黃色警告Missing paths file for dynamic route ...。加載器默認(rèn)導(dǎo)出需包含paths屬性數(shù)組或函數(shù)否則會報 Missingpathsproperty 警告。對于 TypeScript 用戶可借助defineRoutes輔助函數(shù)獲得類型推斷源碼見 dynamicRoutesPlugin.ts 的defineRoutes定義。倉庫的端到端測試就是一個完整范例[id].paths.tsimport { defineRoutes } from vitepress import paths from ./paths export default defineRoutes({ async paths(_watchedFiles: string[]) { return paths }, watch: [../data-loading/**/*.json], async transformPageData(pageData) { pageData.title - transformed } })其中paths數(shù)據(jù)來自paths.tsexport default [ { params: { id: foo }, content: # Foo }, { params: { id: bar }, content: # Bar } ]路由模板[id].md只有兩行!-- content -- pre classparams{{ $params }}/pre進(jìn)階能力watch 熱更新與 transformPageData監(jiān)聽 CMS 數(shù)據(jù)文件的變化如果 CMS 內(nèi)容會同步到本地例如 git 子模塊或同步腳本可以在路徑加載器中配置watch選項用 glob 模式匹配內(nèi)容文件。開發(fā)服務(wù)器下一旦這些文件發(fā)生變化插件會通過hotUpdate鉤子觸發(fā)路由重解析實現(xiàn)內(nèi)容的即時刷新export default defineRoutes({ watch: [../data-loading/**/*.json], async paths(watchedFiles: string[]) { // watchedFiles 是匹配到的絕對路徑數(shù)組 return watchedFiles.map((file) { // 從文件內(nèi)容構(gòu)造路徑數(shù)據(jù) return { params: { id: foo }, content: ... } }) } })從 dynamicRoutesPlugin.ts 源碼可見當(dāng)pathLoaderRE/\.paths\.m?[jt]s$/命中的加載器模塊或其依賴變化時會調(diào)用resolvePages(config)重新解析全部路由routeModuleCache用于在 HMR 未失效時復(fù)用已解析的路由保證開發(fā)性能。用 transformPageData 統(tǒng)一改寫頁面數(shù)據(jù)transformPageData允許在每個動態(tài)路由生成頁面數(shù)據(jù)時做統(tǒng)一改寫如拼接標(biāo)題、注入自定義字段其簽名與站點級transformPageData一致。測試中用它給所有動態(tài)頁面標(biāo)題追加- transformed后綴dynamic-routes.test.ts并在插件側(cè)通過getPageDataTransformer按加載器路徑取出對應(yīng)的轉(zhuǎn)換函數(shù)。用端到端測試驗證集成效果倉庫的動態(tài)路由端到端測試驗證了上述鏈路在真實構(gòu)建/開發(fā)流程中的行為describe(dynamic routes, () { test(render correct content, async () { await goto(/dynamic-routes/foo) expect(await page.textContent(h1)).toMatch(Foo) expect(await page.textContent(pre.params)).toMatch(id: foo) await goto(/dynamic-routes/bar) expect(await page.textContent(h1)).toMatch(Bar) expect(await page.textContent(pre.params)).toMatch(id: bar) }) })它斷言content注入的 Markdown# Foo/# Bar被正確渲染為h1同時$params在頁面中以 JSON 形式可見。這印證了params與content兩條數(shù)據(jù)通道在渲染層的最終效果可作為你自行編寫 CMS 集成測試的模板。與構(gòu)建時數(shù)據(jù)加載Data Loader的分工除動態(tài)路由外VitePress 還提供構(gòu)建時數(shù)據(jù)加載.data.js/.data.ts文件通過load()方法在構(gòu)建時獲取數(shù)據(jù)以data具名導(dǎo)出暴露給頁面和組件。兩者的定位不同動態(tài)路由 paths loader適合每個數(shù)據(jù)條目生成一個獨(dú)立頁面的場景文章詳情頁content可注入完整正文數(shù)據(jù)加載器適合在一個頁面內(nèi)批量消費(fèi)數(shù)據(jù)的場景文章索引、歸檔列表、分類頁配合createContentLoader可輕松生成內(nèi)容歸檔且數(shù)據(jù)以 JSON 內(nèi)聯(lián)進(jìn)客戶端 bundle。實際 CMS 站點通常兩者結(jié)合用動態(tài)路由渲染詳情頁用數(shù)據(jù)加載器渲染列表頁/標(biāo)簽頁二者數(shù)據(jù)源一致只是消費(fèi)方式不同。完整的 CMS 博客集成示例綜合以上內(nèi)容一個最小可用的CMS 驅(qū)動博客結(jié)構(gòu)如下. ├─ .env # 存放 CMS 憑證 ├─ .vitepress │ └─ config.js └─ posts ├─ [id].md # 詳情頁模板 └─ [id].paths.js # 路徑加載器posts/[id].paths.jsimport { loadEnv } from vitepress const env loadEnv(, process.cwd()) export default { async paths() { const data await (await fetch(${env.CMS_URL}/articles, { headers: { Authorization: Bearer ${env.CMS_TOKEN} } })).json() return data.map((article) ({ params: { id: article.slug, title: article.title, author: article.author, date: article.published_at }, content: article.body_markdown })) } }posts/[id].md# {{ $params.title }} - by {{ $params.author }} on {{ $params.date }} !-- content --之后運(yùn)行vitepress buildVitePress 會請求 CMS API為每篇文章生成對應(yīng)的posts/slug.html。由于內(nèi)容在構(gòu)建期已靜態(tài)化產(chǎn)出站點完全不需要 CMS 參與運(yùn)行時渲染可部署到任意靜態(tài)托管平臺。提交你的 CMS 集成指南每個 CMS 的認(rèn)證方式、查詢語言與內(nèi)容模型差異很大官方倉庫歡迎社區(qū)沉淀特定 CMS 的集成實踐。如果你已經(jīng)寫好了將 VitePress 與某個具體 CMS 集成的指南可以點擊對應(yīng)文檔頁面的在 GitHub 上編輯此頁面Edit this page鏈接將其提交到docs/zh/guide/cms.md文檔的整合指南一節(jié)幫助更多開發(fā)者快速接入。【免費(fèi)下載鏈接】vitepressVite Vue powered static site generator.項目地址: https://gitcode.com/gh_mirrors/vi/vitepress創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考