Skip to content

Commit

Permalink
tmp
Browse files Browse the repository at this point in the history
  • Loading branch information
patsissons committed Feb 18, 2024
1 parent b6adfa7 commit 0fe5733
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 40 deletions.
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"clean": "rimraf ./.svelte-kit",
"dev": "vite dev",
"build": "vite build",
"build": "pnpm sync && vite build",
"preview": "vite preview",
"sync": "svelte-kit sync",
"validate:svelte": "pnpm sync && svelte-check --tsconfig ./tsconfig.json",
Expand All @@ -21,7 +21,7 @@
"dependencies": {
"@download/blockies": "^1.0.3",
"@ensdomains/ensjs": "^3.2.0",
"@ethercorps/sveltekit-og": "^3.0.0",
"@ethercorps/svelte-h2j": "^0.1.0",
"@fontsource/inter": "^5.0.16",
"@fontsource/roboto-mono": "^5.0.16",
"@napi-rs/canvas": "^0.1.45",
Expand All @@ -30,6 +30,7 @@
"@urql/svelte": "^4.0.4",
"date-fns": "^3.3.1",
"dotenv": "^16.3.2",
"satori": "^0.10.11",
"svelte": "^4.2.7",
"tailwindcss": "^3.4.1",
"viem": "^2.5.0"
Expand Down Expand Up @@ -60,7 +61,8 @@
"svelte-check": "^3.6.0",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^5.0.3"
"vite": "^5.0.3",
"vite-plugin-arraybuffer": "^0.0.2"
},
"type": "module"
}
31 changes: 15 additions & 16 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/lib/components/Seo/seo.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script lang="ts">
import type { POAPEventMetadata } from '$lib/types/poap'
const baseUrl = 'https://vpoap.vercel.app'
// const baseUrl = 'https://vpoap.vercel.app'
const baseUrl = 'https://vpoap-git-opengraph-goldsky.vercel.app'
const title = 'Visual POAP'
const description = 'Watch POAP mints live!'
Expand Down
5 changes: 5 additions & 0 deletions src/lib/types/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ declare module '@download/blockies' {
export function createIcon(opts: Options): HTMLCanvasElement
export function renderIcon(opts: Options, canvas: HTMLCanvasElement | Canvas): HTMLCanvasElement
}

declare module '*.woff?arraybuffer' {
const content: ArrayBuffer
export default content
}
36 changes: 18 additions & 18 deletions src/routes/og/[token_id]/+server.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
import { ImageResponse } from '@ethercorps/sveltekit-og'
import { loadTokenData } from '../../token/[id]/data'
import type { RequestHandler } from './$types'
import inter400 from '@fontsource/inter/files/inter-latin-400-normal.woff?arraybuffer'
import inter600 from '@fontsource/inter/files/inter-latin-600-normal.woff?arraybuffer'
import robotoMono400 from '@fontsource/roboto-mono/files/roboto-mono-latin-400-normal.woff?arraybuffer'
import { error } from '@sveltejs/kit'
import { loadTokenData } from '../../token/[id]/data'
import OgToken from './og-token.svelte'
import inter400 from '@fontsource/inter/files/inter-latin-400-normal.woff'
import inter600 from '@fontsource/inter/files/inter-latin-600-normal.woff'
import robotoMono400 from '@fontsource/roboto-mono/files/roboto-mono-latin-400-normal.woff'
import { svelteToPngResponse } from './svelte-to-png'
import type { RequestHandler } from './$types'

export const GET: RequestHandler = async ({ params: { token_id }, fetch }) => {
const props = await loadTokenData(token_id, fetch)
if (!props.token) throw error(404, 'Token not found')
try {
const props = await loadTokenData(token_id, fetch)
if (!props.token) throw error(404, 'Token not found')

return new ImageResponse(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
OgToken as any,
{
return await svelteToPngResponse(OgToken, props, {
width: 1200,
height: 630,
fonts: [
{
name: 'sans-serif',
data: await fetch(inter400).then((res) => res.arrayBuffer()),
data: inter400,
weight: 400,
},
{
name: 'sans-serif',
data: await fetch(inter600).then((res) => res.arrayBuffer()),
data: inter600,
weight: 600,
},
{
name: 'monospace',
data: await fetch(robotoMono400).then((res) => res.arrayBuffer()),
data: robotoMono400,
weight: 400,
},
],
},
props,
)
})
} catch (err) {
console.error('error building opengraph image', err)
throw error(500, err instanceof Error ? err.message : 'Opengraph image error')
}
}
34 changes: 34 additions & 0 deletions src/routes/og/[token_id]/svelte-to-png/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Resvg } from '@resvg/resvg-js'
import satori, { type SatoriOptions } from 'satori'
import type { ComponentType, SvelteComponent } from 'svelte'
import { svelteComponentToJsx } from './svelteToJsx'

// this is all a cleaned up version of https://github.com/etherCorps/svelte-og and https://github.com/etherCorps/svelte-h2j

export async function svelteToPngResponse(
component: ComponentType<SvelteComponent>,
props: Record<string, unknown>,
options: SatoriOptions,
) {
const reactElement = svelteComponentToJsx(component, props)
const svg = await satori(reactElement, options)
const resvg = new Resvg(svg, {
fitTo: {
mode: 'width',
value: 'width' in options ? options.width : 1200,
},
font: {
// It will be faster to disable loading system fonts.
loadSystemFonts: false,
},
})
const pngData = resvg.render()
const pngBuffer = pngData.asPng()
return new Response(pngBuffer, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, immutable, no-transform, max-age=31536000',
},
status: 200,
})
}
80 changes: 80 additions & 0 deletions src/routes/og/[token_id]/svelte-to-png/inlineCSS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { Style } from 'svelte/types/compiler/interfaces'

// 3. Make it better if possible
export function extractStyles(cssAst: Style) {
const styles: Record<string, string> = {}
if (Object.hasOwn(cssAst, 'children') && cssAst.children.length) {
for (const { prelude, block } of cssAst.children) {
if (hasChildren(prelude)) {
for (const selector of prelude.children) {
if (!hasClassnames(selector)) continue

const classNames = selector.children
.filter((node) => node.type === 'ClassSelector')
.map((node) => node.name)
for (const className of classNames) {
if (!hasDeclarations(block)) continue

styles[className] = block.children
.map((declaration) => {
return `${declaration.property}: ${declaration.value.children
.map((identifier) => {
return identifier.name
})
.join(' ')}`
})
.join('; ')
}
}
}
}
}
return styles

function hasChildren(node: unknown): node is { children: unknown[] } {
return Boolean(
node &&
typeof node === 'object' &&
'children' in node &&
Array.isArray(node.children) &&
node.children.length > 0,
)
}

function hasClassnames(node: unknown): node is { children: { type: string; name: string }[] } {
if (!hasChildren(node)) return false

const [first] = node.children

return Boolean(
first &&
typeof first === 'object' &&
'type' in first &&
typeof first.type === 'string' &&
'name' in first &&
typeof first.name === 'string',
)
}

function hasDeclarations(
node: unknown,
): node is { children: { property: string; value: { children: { name: string }[] } }[] } {
if (!hasChildren(node)) return false

const [first] = node.children

return Boolean(
first &&
typeof first === 'object' &&
'property' in first &&
typeof first.property === 'string' &&
'value' in first &&
typeof first.value === 'object' &&
hasChildren(first.value) &&
first.value.children[0] &&
typeof first.value.children[0] === 'object' &&
'name' in first.value.children[0] &&
typeof first.value.children[0].name === 'string',
)
}
}
31 changes: 31 additions & 0 deletions src/routes/og/[token_id]/svelte-to-png/svelteToJsx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ComponentType, SvelteComponent } from 'svelte'
import { toReactElement } from './toReactElement.js'

type SvelteSSRComponent = ComponentType<SvelteComponent> & {
render: (props: Record<string, unknown>) => {
html: string
css: { code: string }
}
}

export function svelteComponentToJsx(
component: ComponentType<SvelteComponent>,
props: Record<string, unknown>,
) {
if (!isSvelteSSRComponent(component)) throw new Error('Component is not a Svelte SSR component')

const SvelteRenderedMarkup = component.render(props)
let htmlTemplate = SvelteRenderedMarkup.html || ''

if (SvelteRenderedMarkup.css.code) {
htmlTemplate = `${htmlTemplate}<style>${SvelteRenderedMarkup.css.code}</style>`
}

return toReactElement(htmlTemplate)

function isSvelteSSRComponent(
component: ComponentType<SvelteComponent>,
): component is SvelteSSRComponent {
return Boolean('render' in component && typeof component.render === 'function')
}
}
Loading

0 comments on commit 0fe5733

Please sign in to comment.