/
githubmirror
/
vuetify
Обзор
Документация
Войти
/
githubmirror
/
vuetify
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
packages/docs/vite.config.mts
401 строка
12 KB
John Leider
docs: generate dynamic OG images at build time
06 апр 2026, 01:10
06 апр 2026, 01:10
e288177
Код
Авторство
О чём код?
import path from 'upath' import fs from 'node:fs/promises' import { fileURLToPath } from 'node:url' import { defineConfig, loadEnv } from 'vite' import AutoImport from 'unplugin-auto-import/vite' import Vue, { parseVueRequest } from '@vitejs/plugin-vue' import ViteFonts from 'unplugin-fonts/vite' import Pages from 'vite-plugin-pages' import Layouts from 'vite-plugin-vue-layouts' import Components from 'unplugin-vue-components/vite' import Markdown from 'vite-plugin-md' import { VitePWA } from 'vite-plugin-pwa' import VueI18n from '@intlify/unplugin-vue-i18n/vite' import Inspect from 'vite-plugin-inspect' import Vuetify from 'vite-plugin-vuetify' import basicSsl from '@vitejs/plugin-basic-ssl' import MagicString from 'magic-string' import { configureMarkdown } from './build/markdown-it' import Api from './build/api-plugin' import { Examples } from './build/examples-plugin' import { generateOgImages } from './build/generate-og-images' import { genAppMetaInfo } from './src/utils/metadata' import { MdiJs } from './build/mdi-js' import { frontmatterBuilder, getRouteMeta, scriptFixer } from './build/markdownBuilders' const resolve = (file: string) => fileURLToPath(new URL(file, import.meta.url)) const ssrTransformCustomDirective = () => { return { props: [], needRuntime: true, } } export default defineConfig(({ command, mode, isSsrBuild }) => { Object.assign(process.env, loadEnv(mode, process.cwd(), '')) let allRoutes: any[] return { logLevel: 'info', resolve: { alias: [ { find: '@', replacement: `${resolve('src')}/` }, { find: 'node-fetch', replacement: 'isomorphic-fetch' }, { find: /^vue$/, replacement: isSsrBuild ? 'vue' : 'vue/dist/vue.runtime.esm-bundler.js' }, ], }, define: { 'process.env.NODE_ENV': mode === 'production' || isSsrBuild ? '"production"' : '"development"', __INTLIFY_PROD_DEVTOOLS__: 'false', }, css: { preprocessorOptions: { sass: { api: 'modern-compiler' } }, }, build: { sourcemap: true, modulePreload: false, cssCodeSplit: false, minify: true, rollupOptions: { output: isSsrBuild ? { inlineDynamicImports: true } : { // TODO: these options currently cause a request cascade // experimentalMinChunkSize: 20 * 1024, // manualChunks (id) { // if ( // ['vue/', '@vue/', 'vue-', 'pinia', '@vueuse/'].some(part => id.includes('node_modules/' + part)) || // id === '\0plugin-vue:export-helper' // ) return 'vendor' // if (id.includes('packages/vuetify/')) return 'vuetify' // if (id.includes('packages/docs/src/api')) return 'api-pages' // } }, }, }, esbuild: { lineLimit: 1000, }, plugins: [ // https://github.com/unplugin/unplugin-auto-import AutoImport({ include: [/\.[tj]sx?$/, /\.vue$/, /\.vue\?vue/, /\.md$/], dirs: [ './src/composables/**', './src/stores/**', './src/utils/**', ], imports: [ 'vue', 'vue-router', 'pinia', { '@vuetify/one': [ 'createOne', 'useAuthStore', 'useHttpStore', 'useOneStore', 'useUserStore', 'useQueueStore', 'useSettingsStore', 'useProductsStore', ], 'lodash-es': ['camelCase', 'kebabCase', 'upperFirst'], vue: ['camelize', 'mergeProps'], vuetify: ['useDate', 'useDisplay', 'useGoTo', 'useRtl', 'useTheme'], 'vue-i18n': ['useI18n'], } ], vueTemplate: true, }), // https://github.com/stafyniaksacha/vite-plugin-fonts ViteFonts({ google: { families: [{ name: 'Roboto', styles: 'wght@100;300;400;500;700;900', }], }, }), Api(), // https://github.com/antfu/unplugin-vue-components Components({ directoryAsNamespace: true, include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.md\?vue/], exclude: [], excludeNames: ['AppMarkdown'], }), // https://github.com/JohnCampionJr/vite-plugin-vue-layouts Layouts({ layoutsDirs: 'src/layouts', importMode (name) { return name === 'home' ? 'sync' : 'async' } }), // https://github.com/antfu/vite-plugin-md Markdown({ wrapperClasses: '', exposeFrontmatter: true, exposeExcerpt: false, markdownItSetup: configureMarkdown, builders: [frontmatterBuilder(), scriptFixer()] }), // https://github.com/hannoeru/vite-plugin-pages Pages({ extensions: ['vue', 'md'], dirs: [ { dir: 'src/pages', baseRoute: '' }, ], extendRoute (route) { let [locale, category, ...rest] = route.path.split('/').slice(1) const idx = route.component.toLowerCase().indexOf(locale) locale = ~idx ? route.component.slice(idx, idx + locale.length) : locale const meta = getRouteMeta(route.component, locale) if (meta.disabled) { return { disabled: true } } return { ...route, path: '/' + [locale, category, ...rest].filter(Boolean).join('/') + '/', meta: { ...meta, category, locale, }, } }, onRoutesGenerated (routes) { allRoutes = routes.filter(route => !route.disabled) return allRoutes.map(route => ({ ...route, meta: JSON.parse(JSON.stringify({ // remove undefined category: route.meta.category, emphasized: route.meta.emphasized, layout: route.meta.layout, locale: route.meta.locale, nav: route.meta.nav, title: route.meta.title, })) })) }, importMode (filepath) { return [ '/src/pages/en/getting-started/installation.md', '/src/pages/en/index.md' ].includes(filepath) ? 'sync' : 'async' } }), // https://github.com/antfu/vite-plugin-pwa VitePWA({ srcDir: 'src', filename: 'service-worker.js', strategies: 'injectManifest', includeAssets: ['favicon.ico'], injectRegister: false, injectManifest: { globIgnores: ['**/*.html', '**/*.map'], additionalManifestEntries: [ { url: '_fallback.html', revision: Date.now().toString(16) }, ], dontCacheBustURLsMatching: /^\/?assets\//, maximumFileSizeToCacheInBytes: 24 * 1024 ** 2, }, manifest: { name: 'Vuetify', description: 'Vuetify UI Library Documentation', short_name: 'Vuetify', theme_color: '#1867C0', display: 'minimal-ui', display_override: ['minimal-ui', 'browser'], icons: [ { src: 'img/icons/android-chrome-192x192.png', sizes: '192x192', type: 'image/png', }, { src: 'img/icons/android-chrome-512x512.png', sizes: '512x512', type: 'image/png', }, ], }, }), { // Remove options block from examples name: 'vuetify:example-setup', transform (code, id) { const { filename, query } = parseVueRequest(id) if (query.raw || query.url) return if (filename.includes('packages/docs/src/examples')) { const composition = /(<script setup>[\w\W]*?<\/script>)/g.exec(code) const options = /(<script>[\w\W]*?<\/script>)/g.exec(code) if (composition && options) { const s = new MagicString(code) s.remove(options.index, options.index + options[0].length + 1) return { code: s.toString(), map: s.generateMap({ hires: true }), } } } } }, // mdi js names and aliases from `@mdi/svg` MdiJs(), Vue({ include: [/\.vue$/, /\.md$/], // https://github.com/vuejs/vue-next/issues/3298 template: { compilerOptions: { directiveTransforms: { ripple: ssrTransformCustomDirective, }, }, }, }), Vuetify({ autoImport: { labs: true }, styles: command === 'serve' || mode === 'development' ? 'sass' : true, }), // https://github.com/intlify/bundle-tools/tree/main/packages/vite-plugin-vue-i18n VueI18n({ compositionOnly: true, include: [resolve('src/i18n/messages/**')], }), Examples(), { name: 'vuetify:example-blocks', transform (code, id) { const type = id.includes('vue&type=playground-resources') ? 'playgroundResources' : id.includes('vue&type=playground-setup') ? 'playgroundSetup' : id.includes('vue&type=example-meta') ? 'exampleMeta' : null if (!type) return return { code: `export default Comp => Comp['${type}'] = \`${code.replace(/`/g, '\\`')}\``, map: null, } }, }, { // lightweight head-only ssg name: 'vuetify:ssg', enforce: 'post', transformIndexHtml: { order: 'post', async handler (html) { if (mode !== 'production') return html await fs.mkdir('dist', { recursive: true }) await fs.writeFile(path.join('dist/_fallback.html'), html) const routes = allRoutes.filter(({ path: route }) => { return route !== '/' && !['/eo-UY/', '/api/', '/user/', ':', '*'].some(v => route.includes(v)) }).map(route => { const ogPath = route.path.replace(/^\/en/, '').replace(/\/$/, '') || '/index' const meta = genAppMetaInfo({ title: `${route.meta.title}${route.path === '/en/' ? '' : ' — Vuetify'}`, description: route.meta.description, keywords: route.meta.keywords, ogImage: `https://vuetifyjs.com/og${ogPath}.png`, }) const metaContent = [ `<title>${meta.title}</title>`, ...meta.meta.map((v: any) => { const attrs = Object.keys(v).filter(k => k !== 'key').map(k => `${k}="${v[k]}"`).join(' ') return `<meta ${attrs}>` }), ...meta.link.map((v: any) => { const attrs = Object.keys(v).map(k => `${k}="${v[k]}"`).join(' ') return `<link ${attrs}>` }), ].join('\n ') const content = html.replace('<!-- @inject-meta -->', metaContent) return { path: route.path, content } }) for (const route of routes) { const filename = path.join('dist', route.path, 'index.html') await fs.mkdir(path.dirname(filename), { recursive: true }) await fs.writeFile(filename, route.content) } await generateOgImages() return routes.find(r => r.path === '/en/')?.content } }, }, Inspect(), process.env.HTTPS === 'true' ? basicSsl() : undefined, ], optimizeDeps: { include: [ 'vue', 'vue-router', 'vue-instantsearch/vue3/es/src/instantsearch.js', 'algoliasearch', 'markdown-it-prism', 'markdown-it-link-attributes', 'markdown-it-attrs', 'markdown-it-anchor', 'markdown-it-header-sections', 'markdown-it-emoji/bare.js', 'markdown-it-container', 'markdown-it/lib/token.mjs', 'markdown-it-multimd-table', 'lodash-es', 'fflate', '@cosmicjs/sdk', ], // In development mode, prevent pre-bundling of @vuetify libs for HMR linking exclude: process.env.NODE_ENV ==='development' ? ['@vuetify/one'] : [], }, ssr: { noExternal: ['vue-i18n', '@vuelidate/core', 'pinia'], }, server: { port: +(process.env.PORT ?? 8095), warmup: { clientFiles: ['./index.html'], }, }, } })