diff --git a/README.md b/README.md index babd299..d414250 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ export const GET: RequestHandler = async () => { ``` -Then run `pnpm dev` and access localhost:5173/og, the React element will be rendered and responded as a PNG from that endpoint: +Then run `pnpm dev` and access localhost:5173/og, the api/route endpoint be rendered and responded as a PNG from that api/endpoint: ![Rendered OG image](static/demo.png) @@ -62,32 +62,53 @@ Read more about the API, supported features and check out the examples on Satori ## API Reference -The package exposes an `ImageResponse` constructor, with the following options available: +The package exposes an `ImageResponse` and `componentToImageResponse` constructors, with the following options available: ```typescript -import { ImageResponse } from '@ethercorps/sveltekit-og' +import {ImageResponse, componentToImageResponse} from '@ethercorps/sveltekit-og' +import {SvelteComponent} from "svelte"; // ... new ImageResponse( - element: string, - options: { - width?: number = 1200 - height?: number = 630 - fonts?: { - name: string, - data: ArrayBuffer, - weight: number, - style: 'normal' | 'italic' + element : string, + options : { + width ? : number = 1200 + height ? : number = 630 + fonts ? : { + name: string, + data: ArrayBuffer, + weight: number, + style: 'normal' | 'italic' }[] - debug?: boolean = false - graphemeImages?: Record; - loadAdditionalAsset?: (languageCode: string, segment: string) => Promise; + debug ? : boolean = false + graphemeImages ? : Record; + loadAdditionalAsset ? : (languageCode: string, segment: string) => Promise; // Options that will be passed to the HTTP response - status?: number = 200 - statusText?: string - headers?: Record - }, -) + status ? : number = 200 + statusText ? : string + headers ? : Record + }) + +new componentToImageResponse( + component : typeof SvelteComponent, + props : {}, // All export let example inside prop dictionary + options : { + width ? : number = 1200 + height ? : number = 630 + fonts ? : { + name: string, + data: ArrayBuffer, + weight: number, + style: 'normal' | 'italic' + }[] + debug ? : boolean = false + graphemeImages ? : Record; + loadAdditionalAsset ? : (languageCode: string, segment: string) => Promise; + // Options that will be passed to the HTTP response + status ? : number = 200 + statusText ? : string + headers ? : Record + }) ``` When running in production, these headers will be included by `@ethercorps/sveltekit-og`: @@ -111,6 +132,7 @@ By default, `@ethercorps/sveltekit-og` only has the 'Noto Sans' font included. I This project will not be possible without the following projects: - [Satori & @vercel/og](https://github.com/vercel/satori) +- [Satori-Html](https://github.com/natemoo-re/satori-html) - [Noto by Google Fonts](https://fonts.google.com/noto) - [Resvg.js](https://github.com/yisibl/resvg-js) diff --git a/package.json b/package.json index 4549c3e..2b84c8d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ethercorps/sveltekit-og", - "version": "0.1.1", + "version": "0.1.2", "private": false, "scripts": { "dev": "vite dev", @@ -35,8 +35,9 @@ "type": "module", "dependencies": { "@resvg/resvg-wasm": "^2.1.0", - "satori": "^0.0.42", - "satori-html": "^0.2.0" + "satori": "^0.0.43", + "satori-html": "^0.2.0", + "yoga-wasm-web": "0.1.2" }, "keywords": [ "open graph image", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bcd71aa..9b4d924 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,16 +21,18 @@ specifiers: tslib: ^2.4.0 typescript: ^4.8.4 vite: ^3.1.8 + yoga-wasm-web: 0.1.2 dependencies: '@resvg/resvg-wasm': 2.1.0 satori: 0.0.42 satori-html: 0.2.0 + yoga-wasm-web: 0.1.2 devDependencies: '@playwright/test': 1.27.1 '@sveltejs/adapter-auto': 1.0.0-next.84 - '@sveltejs/kit': 1.0.0-next.520_svelte@3.52.0+vite@3.1.8 + '@sveltejs/kit': 1.0.0-next.522_svelte@3.52.0+vite@3.1.8 '@sveltejs/package': 1.0.0-next.1_besnmoibwkhwtentvwuriss7pa '@typescript-eslint/eslint-plugin': 5.40.1_ukgdydjtebaxmxfqp5v5ulh64y '@typescript-eslint/parser': 5.40.1_z4bbprzjrhnsfa24uvmcbu7f5q @@ -256,8 +258,8 @@ packages: - supports-color dev: true - /@sveltejs/kit/1.0.0-next.520_svelte@3.52.0+vite@3.1.8: - resolution: {integrity: sha512-flroQQEwIHx6IEkskHGQ90Ri+gFCnAs4ZFRsG7QuTbuBu7XSeJNQs3J73pmBQoKp0pWnbwZ3YZMtwBJWF/I7qA==} + /@sveltejs/kit/1.0.0-next.522_svelte@3.52.0+vite@3.1.8: + resolution: {integrity: sha512-dVVrRPbXlAut4vg8kbCeZOjUZnYB7ZvDKp/yAx8InpDF0P1bvUDlc++DXFeMBdXX8bakTA3NGonWnDjqslCsZw==} engines: {node: '>=16.14'} hasBin: true requiresBuild: true @@ -2492,3 +2494,7 @@ packages: dependencies: '@types/yoga-layout': 1.9.2 dev: false + + /yoga-wasm-web/0.1.2: + resolution: {integrity: sha512-8SkgawHcA0RUbMrnhxbaQkZDBi8rMed8pQHixkFF9w32zGhAwZ9/cOHWlpYfr6RCx42Yp3siV45/jPEkJxsk6w==} + dev: false diff --git a/src/lib/index.ts b/src/lib/index.ts index dbbfd4a..7909139 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -2,6 +2,7 @@ import { html as toReactNode } from 'satori-html'; import satori from 'satori'; import { Resvg, initWasm } from '@resvg/resvg-wasm'; import type { SatoriOptions } from 'satori'; +import type {SvelteComponent} from "svelte"; const resSvgWasm = initWasm(fetch('https://sveltekit-og.ethercorps.io/resvg.wasm')); const fontFile = await fetch('https://sveltekit-og.ethercorps.io/noto-sans.ttf'); @@ -27,7 +28,7 @@ export const ImageResponse = class { ] }); const pngData = new Resvg(svg, { fitTo: { mode: 'width', value: options.width } }); - a.enqueue(await pngData.render().asPng()); + a.enqueue(pngData.render().asPng()); a.close(); } }); @@ -47,17 +48,22 @@ export const ImageResponse = class { }; export const componentToImageResponse = class { - constructor(component, props = {}, optionsByUser: ImageResponseOptions) { - const SvelteRenderedMarkup = component.render(props); - let htmlTemplate = `${SvelteRenderedMarkup.html}`; - if (SvelteRenderedMarkup && SvelteRenderedMarkup.css && SvelteRenderedMarkup.css.code) { - htmlTemplate = `${SvelteRenderedMarkup.html}`; - } + constructor(component: typeof SvelteComponent, props = {}, optionsByUser: ImageResponseOptions) { + const htmlTemplate = componentToMarkup(component, props) return new ImageResponse(htmlTemplate, optionsByUser); } }; -export type ImageResponseOptions = ConstructorParameters[1] & ImageOptions; +const componentToMarkup = (component: typeof SvelteComponent, props={}) => { + const SvelteRenderedMarkup = (component as any).render(props); + let htmlTemplate = `${SvelteRenderedMarkup.html}`; + if (SvelteRenderedMarkup && SvelteRenderedMarkup.css && SvelteRenderedMarkup.css.code) { + htmlTemplate = `${SvelteRenderedMarkup.html}`; + } + return htmlTemplate +} + +type ImageResponseOptions = ConstructorParameters[1] & ImageOptions; type ImageOptions = { width?: number;