Skip to content

Commit

Permalink
move meta from router to components expose
Browse files Browse the repository at this point in the history
  • Loading branch information
KaelWD committed Aug 7, 2024
1 parent b068585 commit c363026
Show file tree
Hide file tree
Showing 14 changed files with 654 additions and 386 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"vue-ecosystem-ci:test": "pnpm --filter vuetify run lint && pnpm --filter vuetify run test"
},
"engines": {
"node": ">=18.20.0 || >=20.10.0"
"node": ">=22.0.0"
},
"devDependencies": {
"@babel/cli": "^7.24.8",
Expand Down Expand Up @@ -79,7 +79,7 @@
"semver": "^7.6.0",
"shelljs": "^0.8.5",
"stringify-object": "^5.0.0",
"typescript": "~5.3.3",
"typescript": "~5.5.4",
"upath": "^2.0.1",
"vite-plugin-inspect": "^0.8.3",
"vite-plugin-warmup": "^0.1.0",
Expand Down
159 changes: 159 additions & 0 deletions packages/docs/auto-imports.d.ts

Large diffs are not rendered by default.

181 changes: 181 additions & 0 deletions packages/docs/build/frontmatterMeta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { createBuilder } from '@yankeeinlondon/builder-api'
import type { Pipeline } from '@yankeeinlondon/builder-api'
import type { Plugin } from 'vite'
import Ajv from 'ajv'
import { md } from './markdown-it'
import fm from 'front-matter'
import fs from 'node:fs'
import path from 'node:path'

const ajv = new Ajv()
const validate = ajv.compile({
type: 'object',
additionalProperties: false,
properties: {
meta: {
type: 'object',
additionalProperties: false,
properties: {
nav: { type: 'string' }, // Title used in navigation links
title: { type: 'string' }, // SEO title
description: { type: 'string' }, // SEO description
keywords: { type: 'string' }, // SEO keywords
},
},
layout: { type: 'string' },
related: {
type: 'array',
maxItems: 3,
uniqueItems: true,
items: { type: 'string' }, // Absolute paths to related pages
},
assets: {
type: 'array',
uniqueItems: true,
items: { type: 'string' }, // Additional stylesheets to load
},
disabled: { type: 'boolean' }, // The page is not published
emphasized: { type: 'boolean' }, // The page is emphasized in the navigation
fluid: { type: 'boolean' }, // Hide the Toc
backmatter: { type: 'boolean' }, // Hide the backmatter
features: {
type: 'object',
additionalProperties: false,
properties: {
figma: { type: 'boolean' },
label: { type: 'string' },
report: { type: 'boolean' },
github: { type: 'string' },
spec: { type: 'string' },
},
},
},
})

export const frontmatterBuilder = createBuilder('frontmatterBuilder', 'metaExtracted')
.options<{
files?: Map<string, Pipeline<'metaExtracted'>>
awaiting?: Map<string, ((v: Pipeline<'metaExtracted'>) => void)[]>
pages?: ReadonlyArray<Record<'name' | 'path' | 'component', string>>
}>()
.initializer()
.handler(async (payload, options) => {
const { frontmatter, fileName } = payload

if (!options.pages) {
const pagesPlugin = payload.viteConfig.plugins!
.find(p => p && 'name' in p && p.name === 'vite-plugin-pages') as Plugin
options.pages = await pagesPlugin.api.getResolvedRoutes() as []
}

const page = options.pages.find(p => fileName.endsWith(p.component))

if (!page) throw new Error('Unable to find page')

const locale = page.path.split('/').at(0)!

options.files ??= new Map()
options.files.set(page.path, payload)

const { meta, ...rest } = frontmatter

if (locale !== 'en') {
const originalPath = page.path.replace(`/${locale}/`, '/en/')
let original = options.files.get(originalPath)
if (!original) {
options.awaiting ??= new Map()
const awaiting = options.awaiting.get(originalPath) ?? []
const { promise, resolve } = Promise.withResolvers<Pipeline<'metaExtracted'>>()
awaiting.push(resolve)
options.awaiting.set(originalPath, awaiting)
original = await promise
}
Object.assign(rest, {
assets: original.frontmatter.assets,
related: original.frontmatter.related,
})
}

if (options.awaiting?.has(page.path)) {
options.awaiting.get(page.path)!.forEach(fn => fn(payload))
}

payload.frontmatter = {
meta,
assets: rest.assets,
backmatter: rest.backmatter,
features: rest.features,
fluid: rest.fluid,
related: rest.related,
toc: generateToc(payload.md),
}

return payload
})
.meta()

export function getRouteMeta (componentPath: string, locale: string) {
const str = fs.readFileSync(path.resolve(componentPath.slice(1)), { encoding: 'utf-8' })
const { attributes } = fm(str)

const valid = validate(attributes)
if (!valid && locale !== 'eo-UY') {
throw new Error(`\nInvalid frontmatter: ${componentPath}` + validate.errors!.map(error => (
`\n | Property ${error.instancePath} ${error.message}`
)).join())
}

const a = attributes as any

if (locale !== 'en') {
const original = getRouteMeta(componentPath.replace(`/${locale}/`, '/en/'), 'en')
a.disabled = original.disabled
a.emphasized = original.emphasized
a.layout = original.layout
}

return {
disabled: a.disabled,
emphasized: a.emphasized,
layout: a.layout,
...a.meta,
}
}

function generateToc (content: string) {
const headings = []
const tokens = md.parse(content, {})
const length = tokens.length

for (let i = 0; i < length; i++) {
const token = tokens[i]

if (token.type === 'inline' && token.content.startsWith('<!--') && !token.content.endsWith('-->')) {
do {
i++
} while (i < length && !tokens[i].content.endsWith('-->'))
continue
}

if (token.type !== 'heading_open') continue

// heading level by hash length '###' === h3
const level = token.markup.length

if (level <= 1) continue

const next = tokens[i + 1]
const link = next.children?.find(child => child.type === 'link_open')
const text = next.children?.filter(child => !!child.content).map(child => child.content).join('')
const anchor = link?.attrs?.find(([attr]) => attr === 'href')
const [, to] = anchor ?? []

headings.push({
text,
to,
level,
})
}

return headings
}
118 changes: 0 additions & 118 deletions packages/docs/build/markdown-it.ts
Original file line number Diff line number Diff line change
@@ -1,123 +1,5 @@
import fs from 'fs'
import path from 'path'
import Ajv from 'ajv'
import fm from 'front-matter'
import MarkdownIt from 'markdown-it'
import { configureMarkdown } from '../src/utils/markdown-it'
export { configureMarkdown } from '../src/utils/markdown-it'

export const md = configureMarkdown(new MarkdownIt())

const generateToc = (content: string) => {
const headings = []
const tokens = md.parse(content, {})
const length = tokens.length

for (let i = 0; i < length; i++) {
const token = tokens[i]

if (token.type === 'inline' && token.content.startsWith('<!--') && !token.content.endsWith('-->')) {
do {
i++
} while (i < length && !tokens[i].content.endsWith('-->'))
continue
}

if (token.type !== 'heading_open') continue

// heading level by hash length '###' === h3
const level = token.markup.length

if (level <= 1) continue

const next = tokens[i + 1]
const link = next.children?.find(child => child.type === 'link_open')
const text = next.children?.filter(child => !!child.content).map(child => child.content).join('')
const anchor = link?.attrs?.find(([attr]) => attr === 'href')
const [, to] = anchor ?? []

headings.push({
text,
to,
level,
})
}

return headings
}

const ajv = new Ajv()
const validate = ajv.compile({
type: 'object',
additionalProperties: false,
properties: {
meta: {
type: 'object',
additionalProperties: false,
properties: {
nav: { type: 'string' }, // Title used in navigation links
title: { type: 'string' }, // SEO title
description: { type: 'string' }, // SEO description
keywords: { type: 'string' }, // SEO keywords
},
},
layout: { type: 'string' },
related: {
type: 'array',
maxItems: 3,
uniqueItems: true,
items: { type: 'string' }, // Absolute paths to related pages
},
assets: {
type: 'array',
uniqueItems: true,
items: { type: 'string' }, // Additional stylesheets to load
},
disabled: { type: 'boolean' }, // The page is not published
emphasized: { type: 'boolean' }, // The page is emphasized in the navigation
fluid: { type: 'boolean' }, // Hide the Toc
backmatter: { type: 'boolean' }, // Hide the backmatter
features: {
type: 'object',
additionalProperties: false,
properties: {
figma: { type: 'boolean' },
label: { type: 'string' },
report: { type: 'boolean' },
github: { type: 'string' },
spec: { type: 'string' },
},
},
},
})

export function parseMeta (componentPath: string, locale: string) {
const str = fs.readFileSync(path.resolve(componentPath.slice(1)), { encoding: 'utf-8' })
const { attributes, body } = fm(str)

const valid = validate(attributes)
if (!valid && locale !== 'eo-UY') {
throw new Error(`\nInvalid frontmatter: ${componentPath}` + validate.errors!.map(error => (
`\n | Property ${error.instancePath} ${error.message}`
)).join())
}

const { meta, ...rest } = attributes as any

if (locale !== 'en') {
const original = parseMeta(componentPath.replace(`/${locale}/`, '/en/'), 'en')
Object.assign(rest, {
layout: original.layout,
related: original.related,
assets: original.assets,
disabled: original.disabled,
emphasized: original.emphasized,
})
}

return {
...rest,
...meta,
toc: generateToc(body),
}
}
1 change: 1 addition & 0 deletions packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"@vitejs/plugin-vue": "^5.0.4",
"@vue/compiler-sfc": "^3.4.27",
"@vuetify/api-generator": "workspace:*",
"@yankeeinlondon/builder-api": "^1.4.1",
"ajv": "^8.12.0",
"algoliasearch-helper": "^3.22.3",
"async-es": "^3.2.5",
Expand Down
7 changes: 4 additions & 3 deletions packages/docs/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@
const theme = useTheme()
const { locale } = useI18n()
const auth = useAuthStore()
const frontmatter = useFrontmatter()
const path = computed(() => route.path.replace(`/${locale.value}/`, ''))
const meta = computed(() => {
return genAppMetaInfo({
title: `${route.meta.title}${path.value === '' ? '' : ' — Vuetify'}`,
description: route.meta.description,
keywords: route.meta.keywords,
assets: route.meta.assets,
description: frontmatter.value?.meta.description,
keywords: frontmatter.value?.meta.keywords,
assets: frontmatter.value?.assets,
})
})
Expand Down
Loading

0 comments on commit c363026

Please sign in to comment.