feat: post build script for opengraph
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITE_BASE_URL=http://localhost:8080/
|
||||
VITE_APP_TITLE=<i icon=rss></i> My Blog
|
||||
VITE_APP_TITLE_NO_HTML=My Blog
|
||||
VITE_APP_SIGNATURE=By <b>me</b>
|
||||
VITE_APP_LANG=en
|
||||
@@ -13,7 +13,7 @@ This template should help get you started developing with Vue 3 in Vite.
|
||||
- [x] link to home
|
||||
- [ ] link to previous/next article
|
||||
- [x] set page title
|
||||
- [ ] SPA and opengraph
|
||||
- [x] SPA and opengraph
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
|
||||
+3
-1
@@ -2,12 +2,14 @@
|
||||
<html lang="%VITE_APP_LANG%">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="icon" href="@articles/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>%VITE_APP_TITLE_NO_HTML%</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato&display=swap" rel="stylesheet" />
|
||||
<link rel="stylesheet" type="text/css" href="/src/style.scss" />
|
||||
<meta property="og:url" content="%VITE_BASE_URL%" />
|
||||
<meta property="og:title" content="%VITE_APP_TITLE_NO_HTML%" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
+3
-2
@@ -6,14 +6,15 @@
|
||||
"repository": "https://github.com/klemek/md-blog",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"build": "run-p type-check \"build-only {@}\" -- && run-p post-build",
|
||||
"post-build": "node ./post-build.ts",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"lint": "run-s lint:*",
|
||||
"lint:oxlint": "oxlint . --fix",
|
||||
"lint:eslint": "eslint . --fix --cache",
|
||||
"format": "prettier --write src/"
|
||||
"format": "prettier --write src/ *.ts *.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"highlight.js": "^11.11.1",
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import fs from 'fs'
|
||||
import process from 'process'
|
||||
|
||||
process.loadEnvFile()
|
||||
|
||||
function getFiles(dir: string): string[] {
|
||||
return fs.readdirSync(dir).flatMap((name) => {
|
||||
const path = `${dir}/${name}`
|
||||
if (fs.statSync(path).isDirectory()) {
|
||||
return getFiles(path)
|
||||
} else {
|
||||
return [path]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const METADATA_BLOCK_REGEX = /^---\n([\s\S]*?)---\n/m
|
||||
const METADATA_REGEX = /^(\w+):(.*)$/gm
|
||||
|
||||
function readArticleMetadata(path: string): Record<string, string> | null {
|
||||
const content = fs.readFileSync(path, { encoding: 'utf8' })
|
||||
const match: RegExpExecArray | null = METADATA_BLOCK_REGEX.exec(content)
|
||||
if (!match || !match[1]) {
|
||||
console.warn(`No metadata for: ${path}`)
|
||||
return null
|
||||
}
|
||||
let subMatch: RegExpExecArray | null = null
|
||||
const metadata: Record<string, string> = {
|
||||
path: path.replaceAll('/index.md', ''),
|
||||
}
|
||||
do {
|
||||
subMatch = METADATA_REGEX.exec(match[1])
|
||||
if (subMatch && subMatch[1] && subMatch[2]) {
|
||||
metadata[subMatch[1]] = subMatch[2].trim()
|
||||
}
|
||||
} while (subMatch)
|
||||
return metadata
|
||||
}
|
||||
|
||||
function formatArticlePage(metadata: Record<string, string>, baseHtml: string): string {
|
||||
let outHtml = baseHtml
|
||||
outHtml = outHtml.replace(/<.*?property="og:url".*?>/gm, '')
|
||||
outHtml = outHtml.replace(
|
||||
/<\/head>/gm,
|
||||
`<meta property="og:url" content="${process.env.VITE_BASE_URL}${metadata.path}/">\n</head>`,
|
||||
)
|
||||
const blog_title = process.env.VITE_APP_TITLE?.replace(/(<([^>]+)>)/gi, '').trim()
|
||||
if (metadata.title) {
|
||||
const title = metadata.title.replace(/(<([^>]+)>)/gi, '').trim()
|
||||
outHtml = outHtml.replace(/<title>.*?<\/title>/gm, `<title>${blog_title} — ${title}</title>`)
|
||||
outHtml = outHtml.replace(/<.*?property="og:title".*?>/gm, '')
|
||||
outHtml = outHtml.replace(
|
||||
/<\/head>/gm,
|
||||
`<meta property="og:title" content="${title}">\n</head>`,
|
||||
)
|
||||
outHtml = outHtml.replace(/<.*?property="og:description".*?>/gm, '')
|
||||
outHtml = outHtml.replace(
|
||||
/<\/head>/gm,
|
||||
`<meta property="og:description" content="${blog_title}">\n</head>`,
|
||||
)
|
||||
}
|
||||
if (metadata.thumbnail) {
|
||||
outHtml = outHtml.replace(/<.*?property="og:image".*?>/gm, '')
|
||||
outHtml = outHtml.replace(
|
||||
/<\/head>/gm,
|
||||
`<meta property="og:image" content="${metadata.thumbnail.replace('./', process.env.VITE_BASE_URL + metadata.path + '/')}">\n</head>`,
|
||||
)
|
||||
}
|
||||
return outHtml
|
||||
}
|
||||
|
||||
const indexContent = fs.readFileSync('dist/index.html', { encoding: 'utf8' })
|
||||
|
||||
if (!indexContent) {
|
||||
console.error('Could not read dist/index.html')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
getFiles('articles')
|
||||
.filter((path) => path.match(/index.md$/))
|
||||
.forEach((path) => {
|
||||
const metadata = readArticleMetadata(path)
|
||||
if (metadata) {
|
||||
fs.writeFileSync(
|
||||
`dist/${metadata.path}/index.html`,
|
||||
formatArticlePage(metadata, indexContent),
|
||||
)
|
||||
console.info(`Wrote dist/${metadata.path}/index.html`)
|
||||
}
|
||||
})
|
||||
+1
-1
@@ -84,7 +84,7 @@ export async function listArticles(): Promise<ArticleMetadata[]> {
|
||||
return null
|
||||
}
|
||||
const date = dateFromParts(match[1], match[2], match[3])
|
||||
return parseMetadata(data.attributes, key.replace('/index.md', ''), date)
|
||||
return parseMetadata(data.attributes, key.replace('index.md', ''), date)
|
||||
}),
|
||||
)
|
||||
).filter((item) => item !== null)
|
||||
|
||||
+2
-1
@@ -6,7 +6,8 @@
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
"eslint.config.*",
|
||||
"post-build.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
// Most tools use transpilation instead of Node.js's native type-stripping.
|
||||
|
||||
+23
-14
@@ -1,21 +1,30 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import { plugin as mdPlugin, Mode } from 'vite-plugin-markdown'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueDevTools(), mdPlugin({ mode: [Mode.HTML] })],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@views': fileURLToPath(new URL('./src/views', import.meta.url)),
|
||||
'@lib': fileURLToPath(new URL('./src/lib', import.meta.url)),
|
||||
'@articles': fileURLToPath(new URL('./articles', import.meta.url)),
|
||||
'@interfaces': fileURLToPath(new URL('./src/interfaces.ts', import.meta.url)),
|
||||
'@components': fileURLToPath(new URL('./src/components', import.meta.url)),
|
||||
export default ({ mode }: { mode: string }) => {
|
||||
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }
|
||||
|
||||
process.env.VITE_APP_TITLE_NO_HTML = process.env.VITE_APP_TITLE?.replace(
|
||||
/(<([^>]+)>)/gi,
|
||||
'',
|
||||
).trim()
|
||||
|
||||
return defineConfig({
|
||||
plugins: [vue(), vueDevTools(), mdPlugin({ mode: [Mode.HTML] })],
|
||||
base: process.env.VITE_BASE_URL,
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@views': fileURLToPath(new URL('./src/views', import.meta.url)),
|
||||
'@lib': fileURLToPath(new URL('./src/lib', import.meta.url)),
|
||||
'@articles': fileURLToPath(new URL('./articles', import.meta.url)),
|
||||
'@interfaces': fileURLToPath(new URL('./src/interfaces.ts', import.meta.url)),
|
||||
'@components': fileURLToPath(new URL('./src/components', import.meta.url)),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user