Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Feb 12, 2024
1 parent 238ce8a commit a0d51c7
Show file tree
Hide file tree
Showing 19 changed files with 210 additions and 30 deletions.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion examples/_dev/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "example",
"name": "example-dev",
"private": true,
"scripts": {
"dev": "bun run --hot src/index.tsx"
Expand Down
1 change: 1 addition & 0 deletions examples/_dev/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button, Farc, TextInput } from 'farc'

import { app as todoApp } from './todos'

const app = new Farc({
Expand Down
28 changes: 24 additions & 4 deletions examples/_dev/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
"jsxImportSource": "farc/jsx",
"paths": {
"farc/jsx/jsx-runtime": ["../../src/jsx/jsx-runtime/index.ts"],
"farc/jsx/jsx-dev-runtime": ["../../src/jsx/jsx-dev-runtime/index.ts"]
},

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["**/*.ts", "**/*.tsx"]
}
3 changes: 3 additions & 0 deletions examples/vercel-edge/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules

.vercel
11 changes: 11 additions & 0 deletions examples/vercel-edge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
To install dependencies:
```sh
bun install
```

To run:
```sh
bun run dev
```

open http://localhost:3000
66 changes: 66 additions & 0 deletions examples/vercel-edge/api/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/** @jsx jsx */
/** @jsxImportSource hono/jsx */

import { Button, Farc, TextInput } from 'farc'
// @ts-ignore
import { jsx } from 'hono/jsx'
import { handle } from 'hono/vercel'

export const config = {
runtime: 'edge',
}

const app = new Farc({ basePath: '/api' })

app.frame('/', (context) => {
const { buttonValue, inputText, status } = context
const fruit = inputText || buttonValue
return {
image: (
<div
style={{
alignItems: 'center',
background:
status === 'response'
? 'linear-gradient(to right, #432889, #17101F)'
: 'black',
backgroundSize: '100% 100%',
display: 'flex',
flexDirection: 'column',
flexWrap: 'nowrap',
height: '100%',
justifyContent: 'center',
textAlign: 'center',
width: '100%',
}}
>
<div
style={{
color: 'white',
fontSize: 60,
fontStyle: 'normal',
letterSpacing: '-0.025em',
lineHeight: 1.4,
marginTop: 30,
padding: '0 120px',
whiteSpace: 'pre-wrap',
}}
>
{status === 'response'
? `Nice choice.${fruit ? ` ${fruit.toUpperCase()}!!` : ''}`
: 'Welcome!'}
</div>
</div>
),
intents: [
<TextInput placeholder="Enter custom fruit..." />,
<Button value="apples">Apples</Button>,
<Button value="oranges">Oranges</Button>,
<Button value="bananas">Bananas</Button>,
status === 'response' && <Button.Reset>Reset</Button.Reset>,
],
}
})

export const GET = handle(app.hono)
export const POST = handle(app.hono)
15 changes: 15 additions & 0 deletions examples/vercel-edge/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "example-vercel-edge",
"type": "module",
"scripts": {
"start": "vercel dev",
"deploy": "vercel"
},
"dependencies": {
"farc": "file:../../src",
"hono": "^4.0.1"
},
"devDependencies": {
"vercel": "^32.4.1"
}
}
21 changes: 21 additions & 0 deletions examples/vercel-edge/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"skipLibCheck": true,

"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["**/*.ts", "**/*.tsx"]
}
8 changes: 8 additions & 0 deletions examples/vercel-edge/vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"rewrites": [
{
"source": "/api/(.*)",
"destination": "/api"
}
]
}
11 changes: 6 additions & 5 deletions src/dev/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,18 @@ const html = `
<meta property="fc:frame:post_url" content="http://localhost:3001">
<meta property="og:image" content="https://example.com/og">
`
const selector = 'meta[property^="fc:"], meta[property^="og:"]'
const metaTags = htmlToMetaTags(html, selector)
const prefix = ['fc', 'og']
const metaTags = htmlToMetaTags(html, prefix)

test('htmlToMetaTags', () => {
const html = `
<title>foo</title>
<meta property="description" content="content">
<meta property="fc:frame" content="vNext">
<meta property="og:image" content="https://example.com/og">
<foo>bar</foo>
`
expect(htmlToMetaTags(html, selector).length).toEqual(2)
expect(htmlToMetaTags(html, prefix).length).toEqual(2)
})

test('parseFrameProperties', () => {
Expand Down Expand Up @@ -100,7 +101,7 @@ describe('validateFrameButtons', () => {
<meta property="fc:frame:button:2" content="bar">
<meta property="fc:frame:button:3" content="baz">
`
const metaTags = htmlToMetaTags(html, selector)
const metaTags = htmlToMetaTags(html, prefix)
const buttons = parseButtons(metaTags)
const result = validateButtons(buttons)
expect(result).toEqual({
Expand All @@ -114,7 +115,7 @@ describe('validateFrameButtons', () => {
<meta property="fc:frame:button:1" content="foo">
<meta property="fc:frame:button:3" content="baz">
`
const metaTags = htmlToMetaTags(html, selector)
const metaTags = htmlToMetaTags(html, prefix)
const buttons = parseButtons(metaTags)
const result = validateButtons(buttons)
expect(result).toEqual({
Expand Down
35 changes: 18 additions & 17 deletions src/dev/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Window } from 'happy-dom'
import { type Node, parseFromString } from 'dom-parser'
import { inspectRoutes } from 'hono/dev'

import {
Expand All @@ -15,16 +15,17 @@ import {
type FrameMetaTagPropertyName,
} from './types.js'

export function htmlToMetaTags(html: string, selector: string) {
const window = new Window()
window.document.write(html)
const document = window.document
return document.querySelectorAll(
selector,
) as unknown as readonly HTMLMetaElement[]
export function htmlToMetaTags(html: string, prefix?: string[]) {
const dom = parseFromString(html)
const nodes = dom.getElementsByTagName('meta')
if (!prefix) return nodes
return nodes.filter((node) => {
const property = node.getAttribute('property')
return property && prefix.some((x) => property.startsWith(x))
})
}

export function parseProperties(metaTags: readonly HTMLMetaElement[]) {
export function parseProperties(metaTags: readonly Node[]) {
const validPropertyNames = new Set<FrameMetaTagPropertyName>([
'fc:frame',
'fc:frame:image',
Expand Down Expand Up @@ -54,8 +55,11 @@ export function parseProperties(metaTags: readonly HTMLMetaElement[]) {
const imageAspectRatio =
(properties['fc:frame:image:aspect_ratio'] as FrameImageAspectRatio) ??
'1.91:1'
const imageUrl = properties['fc:frame:image'] ?? ''
const postUrl = properties['fc:frame:post_url'] ?? ''
const imageUrl = (properties['fc:frame:image'] ?? '').replaceAll('&amp;', '&')
const postUrl = (properties['fc:frame:post_url'] ?? '').replaceAll(
'&amp;',
'&',
)
const title = properties['og:title'] ?? ''
const version = (properties['fc:frame'] as FrameVersion) ?? 'vNext'

Expand All @@ -70,7 +74,7 @@ export function parseProperties(metaTags: readonly HTMLMetaElement[]) {
}
}

export function parseButtons(metaTags: readonly HTMLMetaElement[]) {
export function parseButtons(metaTags: readonly Node[]) {
// https://regexr.com/7rlm0
const buttonRegex = /fc:frame:button:(1|2|3|4)(?::(action|target))?$/

Expand Down Expand Up @@ -152,7 +156,7 @@ export type State = {
}

export function htmlToState(html: string) {
const metaTags = htmlToMetaTags(html, 'meta[property^="farc:"]')
const metaTags = htmlToMetaTags(html, ['farc'])

const properties: Partial<Record<FarcMetaTagPropertyName, string>> = {}
for (const metaTag of metaTags) {
Expand All @@ -174,10 +178,7 @@ export function htmlToState(html: string) {
}

export function htmlToFrame(html: string) {
const metaTags = htmlToMetaTags(
html,
'meta[property^="fc:"], meta[property^="og:"]',
)
const metaTags = htmlToMetaTags(html, ['fc', 'og'])
const properties = parseProperties(metaTags)
const buttons = parseButtons(metaTags)

Expand Down
3 changes: 3 additions & 0 deletions src/farc.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Buffer } from 'node:buffer'
import {
FrameActionBody,
Message,
Expand Down Expand Up @@ -28,6 +29,8 @@ import { parsePath } from './utils/parsePath.js'
import { requestToContext } from './utils/requestToContext.js'
import { serializeJson } from './utils/serializeJson.js'

globalThis.Buffer = Buffer

export type FarcConstructorParameters<
state = undefined,
env extends Env = Env,
Expand Down
1 change: 1 addition & 0 deletions src/jsx/jsx-dev-runtime/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from 'hono/jsx/jsx-dev-runtime'
6 changes: 6 additions & 0 deletions src/jsx/jsx-dev-runtime/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "module",
"types": "../../_lib/jsx/jsx-dev-runtime/index.d.ts",
"module": "../../_lib/jsx/jsx-dev-runtime/index.js",
"main": "../../_lib/jsx/jsx-dev-runtime/index.js"
}
1 change: 1 addition & 0 deletions src/jsx/jsx-runtime/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from 'hono/jsx/jsx-runtime'
6 changes: 6 additions & 0 deletions src/jsx/jsx-runtime/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "module",
"types": "../../_lib/jsx/jsx-runtime/index.d.ts",
"module": "../../_lib/jsx/jsx-runtime/index.js",
"main": "../../_lib/jsx/jsx-runtime/index.js"
}
16 changes: 14 additions & 2 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,18 @@
"typings": "_lib/index.d.ts",
"sideEffects": false,
"exports": {
".": "./_lib/index.js"
".": {
"types": "./_lib/index.d.ts",
"default": "./_lib/index.js"
},
"./jsx/jsx-runtime": {
"types": "./_lib/jsx/jsx-runtime/index.d.ts",
"default": "./_lib/jsx/jsx-runtime/index.js"
},
"./jsx/jsx-dev-runtime": {
"types": "./_lib/jsx/jsx-dev-runtime/index.d.ts",
"default": "./_lib/jsx/jsx-dev-runtime/index.js"
}
},
"peerDependencies": {
"hono": "^4"
Expand All @@ -16,7 +27,8 @@
"@farcaster/core": "^0.13.7",
"@noble/curves": "^1.3.0",
"happy-dom": "^13.3.8",
"hono-og": "~0.0.3",
"dom-parser": "1.1.5",
"hono-og": "~0.0.5",
"immer": "^10.0.3",
"lz-string": "^1.5.0",
"shiki": "^1.0.0"
Expand Down
6 changes: 5 additions & 1 deletion tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@
"target": "ES2021", // Setting this to `ES2021` enables native support for `Node v16+`: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping.
"lib": ["ES2022", "DOM"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"jsxImportSource": "farc/jsx",
"types": ["@types/bun", "typed-htmx"],
"paths": {
"farc/jsx/jsx-runtime": ["./src/jsx/jsx-runtime/index.ts"],
"farc/jsx/jsx-dev-runtime": ["./src/jsx/jsx-dev-runtime/index.ts"]
},

// Skip type checking for node modules
"skipLibCheck": true
Expand Down

0 comments on commit a0d51c7

Please sign in to comment.