Skip to content

Commit

Permalink
Feat/og (#85)
Browse files Browse the repository at this point in the history
* feat: add generateBlogOGImage function

* feat: add OG Image feature description in project/website

* update: lockfile
  • Loading branch information
hidaviddong authored Jul 10, 2024
1 parent ecb957b commit 8910b8b
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 26 deletions.
Binary file modified bun.lockb
Binary file not shown.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"build": "vite build",
"preview": "vite preview",
"prepare": "husky",
"webp": "bun run ./scripts/webp.js"
"webp": "bun run ./scripts/webp.js",
"og": "node ./scripts/og.js && bun run webp"
},
"devDependencies": {
"@biomejs/biome": "1.8.1",
Expand All @@ -30,6 +31,7 @@
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.1.0",
"@resvg/resvg-js": "^2.6.2",
"ansis": "^3.2.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
Expand All @@ -39,6 +41,8 @@
"react": "^18.3.1",
"react-compare-slider": "^3.1.0",
"react-dom": "^18.3.1",
"satori": "^0.10.14",
"satori-html": "^0.3.2",
"sharp": "^0.33.4",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
Expand Down
Binary file added public/RobotoMono-Regular.ttf
Binary file not shown.
Binary file added public/images/og/nand2tetris.webp
Binary file not shown.
Binary file not shown.
49 changes: 49 additions & 0 deletions scripts/og.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Blog } from '../src/config/index.js'
import { readFile, writeFile } from "node:fs/promises";
import { html } from "satori-html";
import satori from "satori";
import { Resvg } from "@resvg/resvg-js";
import path from 'node:path';
import { existsSync } from 'node:fs';
import { greenBright, yellowBright } from 'ansis';

const basePath = './public'; // 基础路径
async function generateBlogOGImage() {
// related issue: https://github.com/natemoo-re/ultrahtml/issues/66
for (const blog of Blog) {
const filePath = path.join(basePath, blog.background);
if (!existsSync(filePath)) {
const htmlTemplate = html(
`<div tw="p-4 flex flex-col bg-white w-full h-full justify-center pl-24">
<div tw="text-zinc-700 text-5xl">David Dong</div>
<div tw="text-zinc-700 text-4xl mt-8">${blog.name}</div>
<div tw="text-zinc-500 text-2xl mt-1">${blog.time} · ${blog.description}</div>
</div>`
)
const svg = await satori(htmlTemplate, {
width: 1200,
height: 630,
fonts: [
{
name: "RobotoMono",
data: await readFile("./public/RobotoMono-Regular.ttf"),
weight: 400,
style: "normal",
},
],
});
const resvg = new Resvg(svg, {
background: "rgba(0, 0, 0)",
});
const pngData = resvg.render();
const pngBuffer = pngData.asPng();
const filePathWithoutExtension = filePath.replace(/\.[^/.]+$/, "");
await writeFile(`${filePathWithoutExtension}.png`, pngBuffer)
console.log(greenBright(`🚀 ${blog.name}'s OG Image is generate! `));
} else {
console.log(yellowBright(`✅ ${blog.name}'s OG Image is already exist! `));
}
}
}

await generateBlogOGImage()
34 changes: 19 additions & 15 deletions scripts/webp.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import sharp from "sharp";
import { readdir, unlink, stat } from "node:fs/promises";
import { extname, join } from 'path'
import { redBright, greenBright, cyanBright, yellowBright } from 'ansis';
import { extname, join, resolve } from 'path'
import { redBright, greenBright, cyanBright, yellowBright, red } from 'ansis';

const imagesDir = './public/images';

async function getFileSize(filePath) {
Expand All @@ -14,8 +15,6 @@ async function getFileSize(filePath) {
}
}



async function convertToWebp(inputFile, outputFile) {
try {
const inputFileSize = await getFileSize(inputFile);
Expand All @@ -37,24 +36,29 @@ async function deleteOriginalFile(filePath) {
}
}


async function processImages() {
async function processImagesInDirectory(directory) {
try {
const files = await readdir(imagesDir);
const files = await readdir(directory, { withFileTypes: true });
const supportedFormats = ['.jpg', '.jpeg', '.png'];

await Promise.all(files.map(async (file) => {
const fileExtension = extname(file).toLowerCase();
if (supportedFormats.includes(fileExtension)) {
const inputFilePath = join(imagesDir, file);
const outputFilePath = inputFilePath.replace(/\.(jpg|jpeg|png)$/i, '.webp');
const fullPath = resolve(directory, file.name);
if (file.isDirectory()) {
await processImagesInDirectory(fullPath);
} else if (file.isFile() && supportedFormats.includes(extname(file.name).toLowerCase())) {
const outputFilePath = fullPath.replace(/\.(jpg|jpeg|png)$/i, '.webp');

await convertToWebp(inputFilePath, outputFilePath);
await deleteOriginalFile(inputFilePath)
await convertToWebp(fullPath, outputFilePath);
await deleteOriginalFile(fullPath);
}
}));
} catch (error) {
console.error(redBright(`❎ Error processing images:${error}`));
console.error(redBright(`❎ Error processing images in directory ${directory}: ${error}`));
}
}
processImages()

async function processImages() {
await processImagesInDirectory(imagesDir);
}

processImages();
4 changes: 2 additions & 2 deletions src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ const Blog = [
description: "Understand how hardware and software worked at a low level.",
time: '2024',
href: '/blog/nand2tetris',
background: "/images/og.webp",
background: "/images/og/nand2tetris.webp",
keywords: ["hardware", "software"]
},
{
name: 'TypeScript Type Challenges in Real Projects',
description: "Managing complex key combinations in TypeScript.",
time: '2024',
href: '/blog/typescript-type-challenges-in-real-projects',
background: "/images/og.webp",
background: "/images/og/typescript-type-challenges-in-real-projects.webp",
keywords: ["TypeScript", "Type Challenge"]
}
]
Expand Down
1 change: 1 addition & 0 deletions src/pages/blog/+Head.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { Head }
function Head() {
const { blog } = useData()
const URL = "https://daviddong.me"
console.log('blog', blog)
if (blog) {
return (
<>
Expand Down
31 changes: 23 additions & 8 deletions src/pages/project/website/+Page.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import Code from "@/components/ui/code";
import { Image } from "@/components/ui/image";
import { useData } from 'vike-react/useData'
export default function Page() {
const { generatedHtml, project } = useData()
const { project } = useData()
return (
<div className="flex flex-col w-full">
<div className="font-medium text-base flex flex-col gap-1">
<p className="my-0 text-zinc-700">{project.name}</p>
<p className="my-0 text-zinc-500 text-sm">{project.time} · {project.description}</p>
</div>
<p>My Personal Website.</p>
<div className="flex flex-col justify-center items-center">

<div className="flex flex-col justify-center items-center my-4">
<div className="flex max-w-full">
<Image
src="/images/daviddong-mobile.webp"
Expand All @@ -27,7 +27,8 @@ export default function Page() {
daviddong.me
</a>
</div>
<div className="flex flex-col justify-center items-center mt-4">

<div className="flex flex-col justify-center items-center my-4">
<Image
src="/images/puppeteer.webp"
alt="puppeteer"
Expand All @@ -39,22 +40,36 @@ export default function Page() {
</p>
</div>

<div className="flex flex-col justify-center items-center mt-4">
<div className="flex flex-col justify-center items-center my-4">
<div className="flex flex-col justify-center items-center space-y-4 w-full ">
<Image
src="/images/image-optimization.webp"
alt="image-optimization"
className="bg-gray-50 border rounded-md my-0 w-full"
/>
<Code dangerouslySetInnerHTML={{ __html: generatedHtml }} />
</div>
<p className="my-0 mt-4 text-sm font-medium">Image Optimization</p>
<p className="my-0 text-zinc-500 text-sm">
Size optimization, automatically determine image width and height .
Image Size optimization.
</p>
</div>

<div className="flex flex-col justify-center items-center mt-4">
<div className="flex flex-col justify-center items-center my-4">
<div className="flex flex-col justify-center items-center space-y-4 w-full ">
<Image
src="/images/og/nand2tetris.webp"
alt="image-optimization"
className="bg-gray-50 border rounded-md my-0 w-full"
/>
</div>
<p className="my-0 mt-4 text-sm font-medium">OG Image</p>
<p className="my-0 text-zinc-500 text-sm">
Automatically generate OG images for blog posts.
</p>
</div>


<div className="flex flex-col justify-center items-center my-4">
<Image
src="/images/pagespeed.webp"
alt="pagespeed"
Expand Down

0 comments on commit 8910b8b

Please sign in to comment.