From 5248d067494b4fbbe7b1f53e70c3f7e0a4301b23 Mon Sep 17 00:00:00 2001 From: jxom Date: Wed, 21 Feb 2024 21:10:13 +1100 Subject: [PATCH] feat: worker support (#15) --- bun.lockb | Bin 183912 -> 185792 bytes src/index.test.tsx | 177 +-------------------------------------------- src/index.ts | 42 +---------- src/package.json | 4 +- src/utils.test.tsx | 177 +++++++++++++++++++++++++++++++++++++++++++++ src/utils.ts | 41 +++++++++++ src/worker.ts | 11 +++ 7 files changed, 234 insertions(+), 218 deletions(-) create mode 100644 src/utils.test.tsx create mode 100644 src/utils.ts create mode 100644 src/worker.ts diff --git a/bun.lockb b/bun.lockb index ada547382fe7985a7a5457313e77a9cc3853ebc0..4192a8b535319eb36ab670c835e7bd6c375f2260 100755 GIT binary patch delta 4449 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 Mf0XD?QDpN!0C<|&^#A|> delta 3054 zcmd^>dr;I>6vywqfUvBhX8GnJq5`t(@^V207BwbQ(W#W0rgBv@QxYGO=w#wp0ja6| z#5<-IIrvCZL9{h9ADBIKsSGrN`Uw%Mf;xRP=D5+&2(nYo^w9;?ET$ye!t(n zzXMy$m)Dq=StDj|+EQ6#agKO)OxpMl62j(ZX)zsHs$fJ7dumPTV>TPz@14Jo^JQ=2BG$Iv|zcD@5Zh93_1|l!IzTZ!Xh9jZ^Y@T(8 zFBT%f^WW+5b~)CFFj7q$z@7j@Ezx#$XR)!_NHL8=+s1kzmaZz67?CSdOt%0B11h6F zuAea$y~qjjF27jRE+B%Qh->)qg8hvGw*Iicb4*qlZAv;lL*N0^e`9G@ML+*wm62T% zEWOH47j^niEvbBOdB|$*&XhxR$Vi#312g3?SItP7+)8JUk;CLk7w%AgFMV!;^mgs5 zlDE8FrE{gX`v3fD)LY=GU!#7@U!92YHp_(L(sSo8pVW0Va^QRl?muW2^0#1^G6I^@ zJTX07q`}O#at=L2ccCQPn0doL>>)yIXXb<0&8(DJE5vJs@LXS%G4utb&5vXU7+us4 z8bkoHlk>2diI@42-vvesp_e0^*>27=gY{+h3A5H;9 z{){blUC|Rh$$rkG1@~rlfEhML(L_m*gUqnQidrajsDfDss{c5YT6BmZc3SZsl;m?T zS^#!oQ4FQFeZhGh;je>|e95d6d~7U&I{6jez^L+z%=*GlginkAg;^B*Bx2P6OAIaW zZSYBcWoCs>3#5uJGwTQcLnu{rg_#ZhW@cBxXkF3JEzGWQUJT-`%&vpYhl*I}HioqT z)Ydp?7-pd=>-o>|@ToIYWdk!D!eRt`s`3UiJLgfAjm&URitSJ;@+PxHIspmN0&X$H z*(^$#-DZ}AI0HUi><+WebQ2->&TH!>8LFes$T$<7n%AMVuIw|ipX@=WXC1U2+5o)) zZG_&0-h$qC9Xu-+%0M~~y`Y}+6ZS5~?m_94{ln!vFF%#7n&978uFwmzLb|S9lnK&5 zoSp^9kXp|geCQ26=^A}WZZyk7+NVK|@3;%Yi=gzz=TdVa)@0Yd2H9n}nVvz~@bcXG z`pq;I6W{X1S5^C?qiFrYHA>p7u~uqN%O}{er_V0!6I1$}l1_Xc;SJ1Npy#EkF5Ozb z*?b>!zEZa8{ADGhtv0L85-Un{S*nWd-tzVLb?PbizJk>|m3*EuFnLA6N{8y)zvZ)X zf|t+R7#?HR5-n~ z9+^FI#;}>AoP&$q%~Nf1>l0Nfl=jTvkDiL$F{fX29S?<6{=EQS-K$zX=|76U%5y3` Y|I#JZD$Kl_AwJpvTClIaUahMB1a_ZZW&i*H diff --git a/src/index.test.tsx b/src/index.test.tsx index 8f1d6f4..c7e4e9d 100644 --- a/src/index.test.tsx +++ b/src/index.test.tsx @@ -3,11 +3,7 @@ /** @jsxFrag */ import { describe, expect, test } from "vitest"; -import { - ImageResponse, - toReactNode, - unstable_createNodejsStream, -} from "./index.js"; +import { ImageResponse, unstable_createNodejsStream } from "./index.js"; describe("ImageResponse", () => { test("default", () => { @@ -24,174 +20,3 @@ describe("unstable_createNodejsStream", () => { ).toBeDefined(); }); }); - -describe("toReactNode", () => { - test("primitive", () => { - expect(toReactNode("hello")).toMatchInlineSnapshot(` - "hello" - `); - expect(toReactNode(1)).toMatchInlineSnapshot("1"); - expect(toReactNode([1, "hello"])).toMatchInlineSnapshot(` - [ - 1, - "hello", - ] - `); - }); - - test("empty", () => { - expect(toReactNode(
)).toMatchInlineSnapshot(` - { - "key": null, - "props": { - "children": [], - }, - "type": "div", - } - `); - }); - - test("children", () => { - expect(toReactNode(
hello
)).toMatchInlineSnapshot(` - { - "key": null, - "props": { - "children": "hello", - }, - "type": "div", - } - `); - }); - - test("JSX children", () => { - expect( - toReactNode( -
-
hello
-
world
-
{69}
-
, - ), - ).toMatchInlineSnapshot(` - { - "key": null, - "props": { - "children": [ - { - "key": null, - "props": { - "children": "hello", - }, - "type": "div", - }, - { - "key": null, - "props": { - "children": "world", - }, - "type": "div", - }, - { - "key": null, - "props": { - "children": 69, - }, - "type": "div", - }, - ], - }, - "type": "div", - } - `); - }); - - test("fragment", () => { - expect( - toReactNode( -
- <> -
hello
-
world
- -
, - ), - ).toMatchInlineSnapshot(` - { - "key": null, - "props": { - "children": [ - { - "key": null, - "props": { - "children": "hello", - }, - "type": "div", - }, - { - "key": null, - "props": { - "children": "world", - }, - "type": "div", - }, - ], - }, - "type": "div", - } - `); - }); - - test("array", () => { - expect( - toReactNode([
hello
,
world
]), - ).toMatchInlineSnapshot(` - [ - { - "key": null, - "props": { - "children": "hello", - }, - "type": "div", - }, - { - "key": null, - "props": { - "children": "world", - }, - "type": "div", - }, - ] - `); - }); - - test("props", () => { - expect( - toReactNode( -
- ok -
, - ), - ).toMatchInlineSnapshot(` - { - "key": null, - "props": { - "baz": { - "barry": true, - }, - "children": "ok", - "className": "lol", - "foo": "bar", - "style": { - "backgroundColor": "red", - }, - }, - "type": "div", - } - `); - }); -}); diff --git a/src/index.ts b/src/index.ts index 955d41a..ff1ed3d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,5 @@ import * as Og from "@wevm/vercel-og"; -import type { Child } from "hono/jsx"; -import type { HtmlEscapedString } from "hono/utils/html"; - -type HonoElement = HtmlEscapedString | Promise; -type ReactElement = { - type: string; - props: object; - key: string | null; -}; +import { type HonoElement, toReactNode } from "./utils.js"; export class ImageResponse extends Og.ImageResponse { constructor( @@ -22,35 +14,3 @@ export const unstable_createNodejsStream = ( element: HonoElement, options?: Parameters[1], ) => Og.unstable_createNodejsStream(toReactNode(element), options); - -export function toReactNode< - const jsx extends Child, - returnType = jsx extends HonoElement[] - ? ReactElement[] - : jsx extends HonoElement - ? ReactElement - : jsx, ->(jsx_: jsx): returnType { - const jsx = jsx_ as Exclude>; - - if (Array.isArray(jsx)) - return jsx.map((child) => toReactNode(child)) as returnType; - if (typeof jsx === "string") return jsx as returnType; - if (typeof jsx === "number") return jsx as returnType; - if (typeof jsx.tag === "function") return toReactNode(jsx.children); // fragment - - const { tag, props } = jsx; - - const children = jsx.children?.map((child) => - toReactNode(child as HonoElement), - ); - - return { - type: tag, - key: null, - props: { - ...props, - children: children.length === 1 ? children[0] : children, - }, - } as returnType; -} diff --git a/src/package.json b/src/package.json index f3b00a6..d861db2 100644 --- a/src/package.json +++ b/src/package.json @@ -8,6 +8,7 @@ "exports": { ".": { "types": "./lib/index.d.ts", + "worker": "./lib/worker.js", "default": "./lib/index.js" } }, @@ -16,6 +17,7 @@ "hono": ">=3" }, "dependencies": { - "@wevm/vercel-og": "~0.6.10" + "@wevm/vercel-og": "~0.6.10", + "workers-og": "~0.0.20" } } diff --git a/src/utils.test.tsx b/src/utils.test.tsx new file mode 100644 index 0000000..de318a2 --- /dev/null +++ b/src/utils.test.tsx @@ -0,0 +1,177 @@ +/** @jsx jsx */ +/** @jsxImportSource hono/jsx */ +/** @jsxFrag */ + +import { describe, expect, test } from "vitest"; +import { toReactNode } from "./utils.js"; + +describe("toReactNode", () => { + test("primitive", () => { + expect(toReactNode("hello")).toMatchInlineSnapshot(` + "hello" + `); + expect(toReactNode(1)).toMatchInlineSnapshot("1"); + expect(toReactNode([1, "hello"])).toMatchInlineSnapshot(` + [ + 1, + "hello", + ] + `); + }); + + test("empty", () => { + expect(toReactNode(
)).toMatchInlineSnapshot(` + { + "key": null, + "props": { + "children": [], + }, + "type": "div", + } + `); + }); + + test("children", () => { + expect(toReactNode(
hello
)).toMatchInlineSnapshot(` + { + "key": null, + "props": { + "children": "hello", + }, + "type": "div", + } + `); + }); + + test("JSX children", () => { + expect( + toReactNode( +
+
hello
+
world
+
{69}
+
, + ), + ).toMatchInlineSnapshot(` + { + "key": null, + "props": { + "children": [ + { + "key": null, + "props": { + "children": "hello", + }, + "type": "div", + }, + { + "key": null, + "props": { + "children": "world", + }, + "type": "div", + }, + { + "key": null, + "props": { + "children": 69, + }, + "type": "div", + }, + ], + }, + "type": "div", + } + `); + }); + + test("fragment", () => { + expect( + toReactNode( +
+ <> +
hello
+
world
+ +
, + ), + ).toMatchInlineSnapshot(` + { + "key": null, + "props": { + "children": [ + { + "key": null, + "props": { + "children": "hello", + }, + "type": "div", + }, + { + "key": null, + "props": { + "children": "world", + }, + "type": "div", + }, + ], + }, + "type": "div", + } + `); + }); + + test("array", () => { + expect( + toReactNode([
hello
,
world
]), + ).toMatchInlineSnapshot(` + [ + { + "key": null, + "props": { + "children": "hello", + }, + "type": "div", + }, + { + "key": null, + "props": { + "children": "world", + }, + "type": "div", + }, + ] + `); + }); + + test("props", () => { + expect( + toReactNode( +
+ ok +
, + ), + ).toMatchInlineSnapshot(` + { + "key": null, + "props": { + "baz": { + "barry": true, + }, + "children": "ok", + "className": "lol", + "foo": "bar", + "style": { + "backgroundColor": "red", + }, + }, + "type": "div", + } + `); + }); +}); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..6b7ae17 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,41 @@ +import type { Child } from "hono/jsx"; +import type { HtmlEscapedString } from "hono/utils/html"; + +export type HonoElement = HtmlEscapedString | Promise; +export type ReactElement = { + type: string; + props: object; + key: string | null; +}; + +export function toReactNode< + const jsx extends Child, + returnType = jsx extends HonoElement[] + ? ReactElement[] + : jsx extends HonoElement + ? ReactElement + : jsx, +>(jsx_: jsx): returnType { + const jsx = jsx_ as Exclude>; + + if (Array.isArray(jsx)) + return jsx.map((child) => toReactNode(child)) as returnType; + if (typeof jsx === "string") return jsx as returnType; + if (typeof jsx === "number") return jsx as returnType; + if (typeof jsx.tag === "function") return toReactNode(jsx.children); // fragment + + const { tag, props } = jsx; + + const children = jsx.children?.map((child) => + toReactNode(child as HonoElement), + ); + + return { + type: tag, + key: null, + props: { + ...props, + children: children.length === 1 ? children[0] : children, + }, + } as returnType; +} diff --git a/src/worker.ts b/src/worker.ts new file mode 100644 index 0000000..bb2869d --- /dev/null +++ b/src/worker.ts @@ -0,0 +1,11 @@ +import * as Og from "workers-og"; +import { type HonoElement, toReactNode } from "./utils.js"; + +export class ImageResponse extends Og.ImageResponse { + constructor( + element: HonoElement, + options?: ConstructorParameters[1], + ) { + super(toReactNode(element), options); + } +}