import * as cheerio from 'cheerio'
import fs from 'node:fs'
import path from 'node:path'
import {
defineConfig,
resolveSiteDataByRoute,
type HeadConfig
} from 'vitepress'
import {
groupIconMdPlugin,
groupIconVitePlugin,
localIconLoader
} from 'vitepress-plugin-group-icons'
import llmstxt from 'vitepress-plugin-llms'
const prod = !!process.env.NETLIFY
const headers: [string, string][] = [
['/assets/*', 'Cache-Control: max-age=31536000, immutable'],
['/_translations/*', 'X-Robots-Tag: noindex']
]
export default defineConfig({
title: 'VitePress',
rewrites: {
'en/:rest*': ':rest*'
},
lastUpdated: true,
cleanUrls: true,
metaChunk: true,
markdown: {
math: true,
codeTransformers: [
// We use `[!!code` in demo to prevent transformation, here we revert it back.
{
postprocess(code) {
return code.replace(/\[\!\!code/g, '[!code')
}
}
],
config(md) {
// TODO: remove when https://github.com/vuejs/vitepress/issues/4431 is fixed
const fence = md.renderer.rules.fence!
md.renderer.rules.fence = function (tokens, idx, options, env, self) {
const { localeIndex = 'root' } = env
const codeCopyButtonTitle = (() => {
switch (localeIndex) {
case 'es':
return 'Copiar código'
case 'fa':
return 'کپی کد'
case 'ko':
return '코드 복사'
case 'pt':
return 'Copiar código'
case 'ru':
return 'Скопировать код'
case 'zh':
return '复制代码'
default:
return 'Copy code'
}
})()
return fence(tokens, idx, options, env, self).replace(
'',
``
)
}
md.use(groupIconMdPlugin)
}
},
sitemap: {
hostname: 'https://vitepress.dev',
transformItems(items) {
return items.filter((item) => !item.url.includes('migration'))
}
},
head: [
[
'link',
{ rel: 'icon', type: 'image/svg+xml', href: '/vitepress-logo-mini.svg' }
],
[
'link',
{ rel: 'icon', type: 'image/png', href: '/vitepress-logo-mini.png' }
],
['meta', { name: 'theme-color', content: '#5f67ee' }],
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:site_name', content: 'VitePress' }],
[
'meta',
{
property: 'og:image',
content: 'https://vitepress.dev/vitepress-og.jpg'
}
],
['meta', { property: 'og:url', content: 'https://vitepress.dev/' }],
[
'script',
{
src: 'https://cdn.usefathom.com/script.js',
'data-site': 'AZBRSFGG',
'data-spa': 'auto',
defer: ''
}
]
],
themeConfig: {
logo: { src: '/vitepress-logo-mini.svg', width: 24, height: 24 },
socialLinks: [
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' }
],
search: {
provider: 'algolia',
options: {
appId: '8J64VVRP8K',
apiKey: '52f578a92b88ad6abde815aae2b0ad7c',
indexName: 'vitepress'
}
}
// carbonAds: { code: 'CEBDT27Y', placement: 'vuejsorg' } // TODO: temporarily disabled
},
locales: {
root: { label: 'English' },
zh: { label: '简体中文' },
pt: { label: 'Português' },
ru: { label: 'Русский' },
es: { label: 'Español' },
ko: { label: '한국어' },
fa: { label: 'فارسی' }
},
vite: {
plugins: [
groupIconVitePlugin({
customIcon: {
vitepress: localIconLoader(
import.meta.url,
'../public/vitepress-logo-mini.svg'
),
firebase: 'logos:firebase'
}
}),
prod &&
llmstxt({
workDir: 'en',
ignoreFiles: ['index.md']
})
],
experimental: {
enableNativePlugin: true
}
},
transformPageData: prod
? (pageData, ctx) => {
const site = resolveSiteDataByRoute(
ctx.siteConfig.site,
pageData.relativePath
)
const title = `${pageData.title || site.title} | ${pageData.description || site.description}`
;((pageData.frontmatter.head ??= []) as HeadConfig[]).push(
['meta', { property: 'og:locale', content: site.lang }],
['meta', { property: 'og:title', content: title }]
)
}
: undefined,
// TODO: add only on prod
transformHtml: (code, id, ctx) => {
if (id.endsWith('/404.html')) return
// TODO: provide this as manifest
const $ = cheerio.load(code)
const m = $.extract({
links: [
{
selector: 'link:is([rel*=preload],[rel*=preconnect])',
value: (el) => el.attribs
}
],
scripts: [
{
selector: 'script[type=module]',
value: (el) => {
const src = el.attribs.src
if (src && !src.startsWith('http')) {
return { href: src, rel: 'modulepreload' }
}
return null
}
}
]
})
const toPreload: HeadConfig[] = [
...m.links,
...m.scripts,
{ rel: 'preload', as: 'image', href: '/vitepress-logo-mini.svg' },
ctx.pageData.frontmatter.layout === 'home'
? { rel: 'preload', as: 'image', href: '/vitepress-logo-large.svg' }
: undefined
]
.filter((x) => x !== undefined)
.map((link) => ['link', link])
id = id
.slice(ctx.siteConfig.outDir.length)
.replace(/(^|\/)index(?:\.html)?$/, '$1')
if (ctx.siteConfig.cleanUrls) {
id = id.replace(/\.html$/, '')
}
headers.push([
id,
'Link: ' + toPreload.map((link) => toLinkHeader(link)).join(', ')
])
return code.replace(/(<\w+)/g, '$1\n') // FIXME: hacky line splitting
},
buildEnd: (siteConfig) => {
headers.sort(
(a, b) =>
b[0].length - a[0].length ||
a[0].localeCompare(b[0]) ||
a[1].localeCompare(b[1])
)
fs.mkdirSync(path.join(siteConfig.outDir, '.vite'), { recursive: true })
fs.writeFileSync(
path.join(siteConfig.outDir, '.vite/headers.json'),
JSON.stringify(headers, null, 2),
'utf-8'
)
fs.writeFileSync(
path.join(siteConfig.outDir, '_headers'),
headers.map(([id, header]) => `${id}\n\t${header}`).join('\n\n') + '\n',
'utf-8'
)
}
})
function toLinkHeader([_, { href, ...attributes }]: HeadConfig): string {
const attributeParts = [`<${encodeURI(href)}>`]
for (const [key, value] of Object.entries(attributes)) {
if (value === '') {
attributeParts.push(key)
} else {
attributeParts.push(`${key}="${value}"`)
}
}
return attributeParts.join('; ')
}