create-comity
Advanced tools
| import { writeFile, readFile, readdir } from 'fs/promises'; | ||
| import { join } from 'path'; | ||
| (async () => { | ||
| const versions = {}; | ||
| // Read all packages and get their versions | ||
| (await readdir('../', { withFileTypes: true })).forEach(async (folder) => { | ||
| if (folder.isDirectory()) { | ||
| const path = join('../', folder.name, 'package.json'); | ||
| try { | ||
| const manifest = JSON.parse(await readFile(path, 'utf-8')); | ||
| versions[manifest.name] = process.env.VERSION || `^${manifest.version}`; | ||
| } catch (error) { | ||
| console.error(`Failed to read or parse ${manifest}:`, error); | ||
| } | ||
| } | ||
| }); | ||
| // Update all templates with the latest versions | ||
| (await readdir('../../templates', { withFileTypes: true })).forEach( | ||
| async (folder) => { | ||
| if (folder.isDirectory()) { | ||
| const path = join('../../templates', folder.name); | ||
| try { | ||
| const manifest = JSON.parse( | ||
| await readFile(`${path}/package.json`, 'utf-8') | ||
| ); | ||
| // Update dependencies | ||
| manifest.dependencies = Object.keys(manifest.dependencies).reduce( | ||
| (accumulator, key) => { | ||
| accumulator[key] = versions[key] | ||
| ? versions[key] | ||
| : manifest.dependencies[key]; | ||
| return accumulator; | ||
| }, | ||
| {} | ||
| ); | ||
| // Update devDependencies | ||
| manifest.devDependencies = Object.keys( | ||
| manifest.devDependencies | ||
| ).reduce((accumulator, key) => { | ||
| accumulator[key] = versions[key] | ||
| ? versions[key] | ||
| : manifest.devDependencies[key]; | ||
| return accumulator; | ||
| }, {}); | ||
| await writeFile( | ||
| `${path}/package.json`, | ||
| JSON.stringify(manifest, undefined, 2) | ||
| ); | ||
| } catch (error) { | ||
| console.error(`Failed to read or parse ${path}/package.json:`, error); | ||
| } | ||
| } | ||
| } | ||
| ); | ||
| })() | ||
| .then(() => {}) | ||
| .catch(console.error); |
| { | ||
| "name": "@comity/example-minimal", | ||
| "type": "module", | ||
| "private": true, | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build": "vite build --mode client && vite build", | ||
| "preview": "wrangler pages dev dist", | ||
| "deploy": "$npm_execpath run build && wrangler pages deploy dist --env production" | ||
| }, | ||
| "dependencies": { | ||
| "@comity/islands": "^0.4.0", | ||
| "alpinejs": "^3.14.9", | ||
| "hono": "^4.7.5" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^4.20250327.0", | ||
| "@hono/vite-cloudflare-pages": "^0.4.2", | ||
| "@hono/vite-dev-server": "^0.18.3", | ||
| "@types/alpinejs": "^3.13.11", | ||
| "vite": "^6.2.3", | ||
| "wrangler": "^3.114.3" | ||
| } | ||
| } |
| body { | ||
| background-color: #e5e5f7; | ||
| opacity: 0.8; | ||
| background-image: radial-gradient(#44f 0.5px, #fff 0.5px); | ||
| background-size: 10px 10px; | ||
| } |
| # Comity Example | ||
| ## Usage | ||
| | npm | yarn | pnpm | | ||
| | ------------- | -------------- | -------------- | | ||
| | `npm install` | `yarn install` | `pnpm install` | | ||
| | `npm run dev` | `yarn run dev` | `pnpm run dev` | |
| import Alpine from 'alpinejs'; | ||
| window.Alpine = Alpine; | ||
| Alpine.start(); |
| import type { FC } from 'hono/jsx'; | ||
| type Props = { | ||
| server?: string; | ||
| client?: string; | ||
| }; | ||
| export const Badge: FC<Props> = ({ client, server }) => { | ||
| return ( | ||
| <div x-data={`{ env: '${client || 'Client'}' }`}> | ||
| <span x-text="env">Hey {server || 'Server'}!</span> | ||
| </div> | ||
| ); | ||
| }; |
| .count { | ||
| color: red; | ||
| } | ||
| .button { | ||
| padding: 0.5em 1em; | ||
| border: 1px solid purple; | ||
| background-color: antiquewhite; | ||
| color: purple; | ||
| text-transform: uppercase; | ||
| cursor: pointer; | ||
| } | ||
| /** | ||
| * Caveat | ||
| * @see https://github.com/honojs/honox/issues/141 | ||
| * @see https://zellwk.com/blog/overcoming-astro-styling-frustrations/ | ||
| */ | ||
| .badge > span { | ||
| color: darkolivegreen; | ||
| } |
| import type { FC, PropsWithChildren } from 'hono/jsx'; | ||
| import { Badge } from './badge.js'; | ||
| import style from './counter.module.css'; | ||
| export type Props = { | ||
| count: number; | ||
| }; | ||
| export const Counter: FC<PropsWithChildren<Props>> = ({ count }) => { | ||
| return ( | ||
| <> | ||
| <div className={style.badge}> | ||
| <Badge client="hydrated" server="SSR" /> | ||
| </div> | ||
| <div x-data={`{ count: ${count} }`}> | ||
| <p className={style.count}> | ||
| Count: <span x-text="count">{count}</span> | ||
| </p> | ||
| <button className={style.button} {...{ '@click': 'count++' }}> | ||
| Increment | ||
| </button> | ||
| </div> | ||
| </> | ||
| ); | ||
| }; |
| (function () { | ||
| alert('this function is called from example.client.ts'); | ||
| })(); |
| import type { Handler } from 'hono'; | ||
| import type { Alpine as AlpineType } from 'alpinejs'; | ||
| declare module 'hono' { | ||
| interface DefaultRenderer { | ||
| (children: ReactElement, props?: Props): Promise<Response>; | ||
| } | ||
| } | ||
| declare global { | ||
| var Alpine: AlpineType; | ||
| } |
| import { Server } from '@comity/islands'; | ||
| import { renderer } from './renderer.js'; | ||
| import { routes } from 'virtual:comity-routes'; | ||
| const app = new Server(); | ||
| // non-HTML output (API) should go here | ||
| // HTML rendered output | ||
| app.use(renderer); | ||
| app.registerRoutes(routes); | ||
| export default app; |
| import { honoRenderer } from '@comity/islands'; | ||
| export const renderer = honoRenderer(({ children, title }) => { | ||
| return ( | ||
| <html lang="en"> | ||
| <head> | ||
| <title>{title}</title> | ||
| <link rel="stylesheet" href="/static/style.css"></link> | ||
| </head> | ||
| <body>{children}</body> | ||
| {import.meta.env.PROD ? ( | ||
| <script type="module" src="/static/client.js" defer async></script> | ||
| ) : ( | ||
| <script type="module" src="/src/client.ts" defer async></script> | ||
| )} | ||
| </html> | ||
| ); | ||
| }); |
| import { atom } from 'nanostores'; | ||
| export const userStore = atom<string | undefined>(undefined); |
| import type { Context } from 'hono'; | ||
| import { Counter } from '~/components/counter.js'; | ||
| export default (ctx: Context) => | ||
| ctx.render( | ||
| <div> | ||
| <div style={{ padding: '30px 5px 5px' }}> | ||
| This island is hydrated on <b>load</b> | ||
| </div> | ||
| <div style={{ backgroundColor: 'aliceblue', padding: '20px' }}> | ||
| <Counter count={10} /> | ||
| </div> | ||
| <div style={{ padding: '30px 5px 5px' }}> | ||
| This island is hydrated on <b>idle</b> | ||
| </div> | ||
| <div style={{ backgroundColor: 'wheat', padding: '20px' }}> | ||
| <Counter count={20} /> | ||
| </div> | ||
| <div style={{ padding: '30px 5px 5px' }}> | ||
| This island is hydrated on <b>screen size <= 600px</b>. Resize the | ||
| screen to hydrate | ||
| </div> | ||
| <div style={{ backgroundColor: 'gold', padding: '20px' }}> | ||
| <Counter count={30} /> | ||
| </div> | ||
| <div style={{ padding: '30px 5px 5px', lineHeight: '1.5em' }}> | ||
| This island is hydrated on <b>visible</b>. Scroll down to see it{' '} | ||
| <span style={{ fontSize: '1.5em', lineHeight: '1em' }}>↓</span> | ||
| </div> | ||
| <div style={{ marginTop: '1000px', padding: '20px' }}> | ||
| <Counter count={30} /> | ||
| </div> | ||
| </div>, | ||
| { title: 'Comity | Hono renderer example' } | ||
| ); |
| { | ||
| "compilerOptions": { | ||
| "declaration": true, | ||
| "esModuleInterop": true, | ||
| "jsx": "react-jsx", | ||
| "jsxImportSource": "hono/jsx", | ||
| "lib": ["DOM", "DOM.Iterable", "ESNext"], | ||
| "module": "NodeNext", | ||
| "moduleResolution": "NodeNext", | ||
| "outDir": "./dist", | ||
| "paths": { | ||
| "~/*": ["./src/*"] | ||
| }, | ||
| "skipLibCheck": true, | ||
| "strict": true, | ||
| "target": "ES2022", | ||
| "types": [ | ||
| "@cloudflare/workers-types", | ||
| "node", | ||
| "vite/client", | ||
| "@comity/islands" | ||
| ] | ||
| }, | ||
| "exclude": ["**/*.test.ts"], | ||
| "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*.d.ts"] | ||
| } |
| import { resolve } from 'node:path'; | ||
| import { defineConfig } from 'vite'; | ||
| import build from '@hono/vite-cloudflare-pages'; | ||
| import devServer from '@hono/vite-dev-server'; | ||
| import adapter from '@hono/vite-dev-server/cloudflare'; | ||
| import { comityRoutes, comityIslands } from '@comity/islands/vite'; | ||
| export default defineConfig(({ mode }) => { | ||
| const alias = { | ||
| '~': resolve(__dirname, './src'), | ||
| }; | ||
| if (mode === 'client') { | ||
| return { | ||
| build: { | ||
| rollupOptions: { | ||
| input: ['./src/client.ts'], | ||
| output: { | ||
| entryFileNames: 'static/client.js', | ||
| chunkFileNames: 'static/assets/[name]-[hash].js', | ||
| assetFileNames: 'static/assets/[name].[ext]', | ||
| }, | ||
| }, | ||
| emptyOutDir: true, | ||
| }, | ||
| resolve: { | ||
| alias, | ||
| }, | ||
| plugins: [ | ||
| comityIslands({ | ||
| extension: '.client.ts', | ||
| css: '.css', | ||
| }), | ||
| ], | ||
| }; | ||
| } | ||
| return { | ||
| resolve: { | ||
| alias, | ||
| }, | ||
| plugins: [ | ||
| build({ | ||
| entry: 'src/index.ts', | ||
| }), | ||
| devServer({ | ||
| adapter, | ||
| entry: 'src/index.ts', | ||
| }), | ||
| comityIslands({ | ||
| extension: '.client.ts', | ||
| css: '.css', | ||
| }), | ||
| comityRoutes(), | ||
| ], | ||
| }; | ||
| }); |
| name = "comity-react-poc" | ||
| compatibility_date = "2024-04-14" | ||
| pages_build_output_dir = "./dist" |
| import { resolve } from 'node:path'; | ||
| import { defineConfig } from 'vite'; | ||
| import { comityIslands, withComity } from '@comity/islands/vite'; | ||
| export default defineConfig(({ mode }) => { | ||
| const alias = { | ||
| '~': resolve(__dirname, './src'), | ||
| }; | ||
| return withComity({ | ||
| build: { | ||
| rollupOptions: { | ||
| input: ['./src/client.ts'], | ||
| output: { | ||
| entryFileNames: 'static/client.js', | ||
| chunkFileNames: 'static/assets/[name]-[hash].js', | ||
| assetFileNames: 'static/assets/[name].[ext]', | ||
| }, | ||
| }, | ||
| emptyOutDir: true, | ||
| }, | ||
| resolve: { | ||
| alias, | ||
| }, | ||
| plugins: [comityIslands()], | ||
| }); | ||
| }); |
| import { resolve } from 'node:path'; | ||
| import { defineConfig } from 'vite'; | ||
| import build from '@hono/vite-cloudflare-pages'; | ||
| import devServer from '@hono/vite-dev-server'; | ||
| import adapter from '@hono/vite-dev-server/cloudflare'; | ||
| import { comityRoutes, comityIslands, withComity } from '@comity/islands/vite'; | ||
| export default defineConfig(({ mode }) => { | ||
| const alias = { | ||
| '~': resolve(__dirname, './src'), | ||
| }; | ||
| return withComity({ | ||
| ssr: { | ||
| external: ['react', 'react-dom'], | ||
| }, | ||
| resolve: { | ||
| alias, | ||
| }, | ||
| plugins: [ | ||
| build({ | ||
| entry: 'src/index.ts', | ||
| }), | ||
| devServer({ | ||
| adapter, | ||
| entry: 'src/index.ts', | ||
| }), | ||
| comityIslands(), | ||
| comityRoutes(), | ||
| ], | ||
| }); | ||
| }); |
| import { createClient } from '@comity/islands/client'; | ||
| import hono from "@comity/islands"; | ||
| const components = [null]; | ||
| createClient( | ||
| false, | ||
| components, | ||
| [ | ||
| hono, | ||
| ] | ||
| ); |
| import type { Handler } from 'hono'; | ||
| import type { FC } from 'hono/jsx'; | ||
| declare module 'virtual:comity-islands' { | ||
| const components: Record<string, () => Promise<Record<string, FC>>>; | ||
| export = components; | ||
| } | ||
| declare module 'virtual:comity-islands?client' { | ||
| const components: Record<string, () => Promise<Record<string, FC>>>; | ||
| export = components; | ||
| } | ||
| declare module 'virtual:comity-routes' { | ||
| const routes: Record<string, Handler>; | ||
| export { routes }; | ||
| } |
| import type { Handler } from 'hono'; | ||
| declare module 'hono' { | ||
| interface DefaultRenderer { | ||
| (children: ReactElement, props?: Props): Promise<Response>; | ||
| } | ||
| } |
| /// <reference types="vite/client" /> | ||
| declare module '*?hash' { | ||
| const hash: string; | ||
| export default hash; | ||
| } |
| { | ||
| "name": "@comity/example-react", | ||
| "type": "module", | ||
| "private": true, | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build": "vite build --mode client && vite build", | ||
| "preview": "wrangler pages dev dist", | ||
| "deploy": "$npm_execpath run build && wrangler pages deploy dist --env production" | ||
| }, | ||
| "dependencies": { | ||
| "@comity/islands": "^0.4.0", | ||
| "@comity/preact": "^0.4.0", | ||
| "@nanostores/preact": "^0.5.2", | ||
| "hono": "^4.7.5", | ||
| "nanostores": "^0.11.4", | ||
| "preact": "^10.26.4", | ||
| "preact-render-to-string": "^6.5.13" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^4.20250327.0", | ||
| "@hono/vite-cloudflare-pages": "^0.4.2", | ||
| "@hono/vite-dev-server": "^0.18.3", | ||
| "vite": "^6.2.3", | ||
| "wrangler": "^3.114.3" | ||
| } | ||
| } |
| h1 { font-family: Arial, Helvetica, sans-serif; } |
| # Comity React Example | ||
| ## Usage | ||
| | npm | yarn | pnpm | | ||
| | ------------- | -------------- | -------------- | | ||
| | `npm install` | `yarn install` | `pnpm install` | | ||
| | `npm run dev` | `yarn run dev` | `pnpm run dev` | |
| import { createClient } from '@comity/islands/client'; | ||
| import preact from '@comity/preact'; | ||
| const debug = false; | ||
| createClient({ | ||
| debug, | ||
| integrations: { | ||
| preact, | ||
| }, | ||
| }); |
| import type { FunctionComponent } from 'preact'; | ||
| import { useEffect, useState } from 'preact/hooks'; | ||
| type Props = { | ||
| server?: string; | ||
| client?: string; | ||
| }; | ||
| export const Badge: FunctionComponent<Props> = ({ client, server }) => { | ||
| const [env, setEnv] = useState<string>(server || 'server'); | ||
| useEffect(() => { | ||
| if (typeof window !== 'undefined') setEnv(client || 'client'); | ||
| }); | ||
| return <span>Hey {env}!</span>; | ||
| }; | ||
| export default Badge; |
| import type { FunctionComponent } from 'preact'; | ||
| import { useState } from 'preact/hooks'; | ||
| import { Island } from '@comity/preact/components'; | ||
| import badge from './badge.js?hash'; | ||
| import style from './counter.module.css'; | ||
| export type Props = { | ||
| count: number; | ||
| }; | ||
| export const Counter: FunctionComponent<Props> = (props) => { | ||
| const [count, setCount] = useState(props.count); | ||
| const onClickHandler = () => { | ||
| const log = 'Click ' + count; | ||
| setCount(count + 1); | ||
| console.log(log + ' -> ' + count); | ||
| }; | ||
| return ( | ||
| <> | ||
| {/* <div className={style.badge}> | ||
| <Island | ||
| $client:load | ||
| $component={badge} | ||
| client="hydrated" | ||
| server="SSR" | ||
| /> | ||
| </div> */} | ||
| <p className={style.count}>Count: {count}</p> | ||
| <button className={style.button} onClick={onClickHandler}> | ||
| Increment | ||
| </button> | ||
| </> | ||
| ); | ||
| }; | ||
| export default Counter; |
| .count { | ||
| color: red; | ||
| } | ||
| .button { | ||
| padding: 0.5em 1em; | ||
| border: 1px solid purple; | ||
| background-color: antiquewhite; | ||
| color: purple; | ||
| text-transform: uppercase; | ||
| cursor: pointer; | ||
| } | ||
| /** | ||
| * Caveat | ||
| * @see https://github.com/honojs/honox/issues/141 | ||
| * @see https://zellwk.com/blog/overcoming-astro-styling-frustrations/ | ||
| */ | ||
| .badge > span { | ||
| color: darkolivegreen; | ||
| } |
| import type { FunctionComponent } from 'preact'; | ||
| import { useEffect, useState } from 'preact/hooks'; | ||
| import { useStore } from '@nanostores/preact'; | ||
| import { userStore } from '~/stores.js'; | ||
| // shared state PoC | ||
| export const User: FunctionComponent = () => { | ||
| const [loading, setLoading] = useState(true); | ||
| const name = useStore(userStore); | ||
| // emulate a (slow) fetch | ||
| useEffect(() => { | ||
| setTimeout(() => { | ||
| userStore.set('John Smith'); | ||
| setLoading(false); | ||
| }, 2000); | ||
| }, []); | ||
| return loading ? ( | ||
| <span style={{ color: 'white' }}>Loading...</span> | ||
| ) : ( | ||
| <span style={{ color: 'blue' }}>Welcome, {name}</span> | ||
| ); | ||
| }; | ||
| export default User; |
| import { Server } from '@comity/islands'; | ||
| import { renderer } from './renderer.js'; | ||
| import { routes } from 'virtual:comity-routes'; | ||
| const app = new Server(); | ||
| // non-HTML output (API) should go here | ||
| // HTML rendered output | ||
| app.use(renderer); | ||
| app.registerRoutes(routes); | ||
| export default app; |
| import { preactRenderer } from '@comity/preact'; | ||
| export const renderer = preactRenderer( | ||
| ({ children, title }) => { | ||
| return ( | ||
| <html lang="en"> | ||
| <head> | ||
| <title>{title}</title> | ||
| </head> | ||
| <body>{children}</body> | ||
| {import.meta.env.PROD ? ( | ||
| <script type="module" src="/static/client.js" defer async></script> | ||
| ) : ( | ||
| <script type="module" src="/src/client.ts" defer async></script> | ||
| )} | ||
| </html> | ||
| ); | ||
| }, | ||
| { stream: true } | ||
| ); |
| import { atom } from 'nanostores'; | ||
| export const userStore = atom<string | undefined>(undefined); |
| import type { Context } from 'hono'; | ||
| import { Suspense } from 'preact/compat'; | ||
| import { Island } from '@comity/preact/components'; | ||
| import counter from '~/components/counter.island.js?hash'; | ||
| import user from '~/components/user.island.js?hash'; | ||
| export default async (ctx: Context) => | ||
| ctx.render( | ||
| <div> | ||
| <div style={{ backgroundColor: 'aquamarine', padding: '20px' }}> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <Island $client:load $component={user} /> | ||
| </Suspense> | ||
| </div> | ||
| <div style={{ padding: '30px 5px 5px' }}> | ||
| This island is hydrated on <b>load</b> | ||
| </div> | ||
| <div style={{ backgroundColor: 'aliceblue', padding: '20px' }}> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <Island $client:load $component={counter} count={10} /> | ||
| </Suspense> | ||
| </div> | ||
| <div style={{ padding: '30px 5px 5px' }}> | ||
| This island is hydrated on <b>idle</b> | ||
| </div> | ||
| <div style={{ backgroundColor: 'wheat', padding: '20px' }}> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <Island $client:idle $component={counter} count={20} /> | ||
| </Suspense> | ||
| </div> | ||
| <div style={{ padding: '30px 5px 5px' }}> | ||
| This island is hydrated on <b>screen size <= 600px</b>. Resize the | ||
| screen to hydrate | ||
| </div> | ||
| <div style={{ backgroundColor: 'gold', padding: '20px' }}> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <Island | ||
| $client:media="(max-width: 600px)" | ||
| $component={counter} | ||
| count={30} | ||
| /> | ||
| </Suspense> | ||
| </div> | ||
| <div style={{ padding: '30px 5px 5px', lineHeight: '1.5em' }}> | ||
| This island is hydrated on <b>visible</b>. Scroll down to see it{' '} | ||
| <span style={{ fontSize: '1.5em', lineHeight: '1em' }}>↓</span> | ||
| </div> | ||
| <div style={{ marginTop: '1000px', padding: '20px' }}> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <Island $client:visible $component={counter} count={30} /> | ||
| </Suspense> | ||
| </div> | ||
| </div>, | ||
| { title: 'Comity | React renderer example' } | ||
| ); |
| { | ||
| "compilerOptions": { | ||
| "declaration": true, | ||
| "esModuleInterop": true, | ||
| "jsx": "react-jsx", | ||
| "jsxImportSource": "preact", | ||
| "lib": ["DOM", "DOM.Iterable", "ESNext"], | ||
| "module": "NodeNext", | ||
| "moduleResolution": "NodeNext", | ||
| "outDir": "./dist", | ||
| "paths": { | ||
| "~/*": ["./src/*"] | ||
| }, | ||
| "skipLibCheck": true, | ||
| "strict": true, | ||
| "target": "ES2022", | ||
| "types": ["@cloudflare/workers-types", "node", "vite/client"] | ||
| }, | ||
| "exclude": ["**/*.test.ts"], | ||
| "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*.d.ts"] | ||
| } |
| import type { Handler } from 'hono'; | ||
| import type { FunctionalComponent as FC } from 'preact'; | ||
| declare module 'virtual:comity-islands' { | ||
| const components: Record<string, () => Promise<{ default: FC }>>; | ||
| const filename: string | undefined; | ||
| export { components, filename }; | ||
| } | ||
| declare module 'virtual:comity-routes' { | ||
| const routes: Record<string, Handler>; | ||
| export { routes }; | ||
| } | ||
| declare module '@comity/preact' { | ||
| interface Props { | ||
| title: string; | ||
| } | ||
| } |
| import type { VNode, PropsWithChildren } from 'preact'; | ||
| declare module 'hono' { | ||
| interface DefaultRenderer { | ||
| (children: Vnode, props?: any): Promise<Response>; | ||
| } | ||
| } |
| /// <reference types="vite/client" /> | ||
| declare module '*?hash' { | ||
| const src: string; | ||
| export default src; | ||
| } |
| import { resolve } from 'node:path'; | ||
| import { defineConfig } from 'vite'; | ||
| import build from '@hono/vite-cloudflare-pages'; | ||
| import devServer from '@hono/vite-dev-server'; | ||
| import adapter from '@hono/vite-dev-server/cloudflare'; | ||
| import { comityRoutes, comityIslands } from '@comity/islands/vite'; | ||
| export default defineConfig(({ mode }) => { | ||
| const alias = { | ||
| '~': resolve(__dirname, './src'), | ||
| }; | ||
| if (mode === 'client') { | ||
| return { | ||
| build: { | ||
| rollupOptions: { | ||
| input: ['./src/client.ts'], | ||
| output: { | ||
| entryFileNames: 'static/client.js', | ||
| chunkFileNames: 'static/assets/[name]-[hash].js', | ||
| assetFileNames: 'static/assets/[name].[ext]', | ||
| }, | ||
| }, | ||
| emptyOutDir: true, | ||
| }, | ||
| resolve: { | ||
| alias, | ||
| }, | ||
| plugins: [comityIslands()], | ||
| }; | ||
| } | ||
| return { | ||
| ssr: { | ||
| external: ['react', 'react-dom'], | ||
| }, | ||
| resolve: { | ||
| alias, | ||
| }, | ||
| plugins: [ | ||
| build({ | ||
| entry: 'src/index.ts', | ||
| }), | ||
| devServer({ | ||
| adapter, | ||
| entry: 'src/index.ts', | ||
| }), | ||
| comityIslands(), | ||
| comityRoutes(), | ||
| ], | ||
| }; | ||
| }); |
| name = "comity-react-poc" | ||
| compatibility_date = "2024-04-14" | ||
| pages_build_output_dir = "./dist" |
| import { resolve } from 'node:path'; | ||
| import { defineConfig } from 'vite'; | ||
| import { comityIslands, withComity } from '@comity/islands/vite'; | ||
| export default defineConfig(({ mode }) => { | ||
| const alias = { | ||
| '~': resolve(__dirname, './src'), | ||
| }; | ||
| return withComity({ | ||
| build: { | ||
| rollupOptions: { | ||
| input: ['./src/client.ts'], | ||
| output: { | ||
| entryFileNames: 'static/client.js', | ||
| chunkFileNames: 'static/assets/[name]-[hash].js', | ||
| assetFileNames: 'static/assets/[name].[ext]', | ||
| }, | ||
| }, | ||
| emptyOutDir: true, | ||
| }, | ||
| resolve: { | ||
| alias, | ||
| }, | ||
| plugins: [comityIslands()], | ||
| }); | ||
| }); |
| import { resolve } from 'node:path'; | ||
| import { defineConfig } from 'vite'; | ||
| import build from '@hono/vite-cloudflare-pages'; | ||
| import devServer from '@hono/vite-dev-server'; | ||
| import adapter from '@hono/vite-dev-server/cloudflare'; | ||
| import { comityRoutes, comityIslands, withComity } from '@comity/islands/vite'; | ||
| export default defineConfig(({ mode }) => { | ||
| const alias = { | ||
| '~': resolve(__dirname, './src'), | ||
| }; | ||
| return withComity({ | ||
| ssr: { | ||
| external: ['react', 'react-dom', 'react-dom/server'], | ||
| }, | ||
| resolve: { | ||
| alias, | ||
| }, | ||
| plugins: [ | ||
| build({ | ||
| entry: 'src/index.ts', | ||
| }), | ||
| devServer({ | ||
| adapter, | ||
| entry: 'src/index.ts', | ||
| }), | ||
| comityIslands(), | ||
| comityRoutes(), | ||
| ], | ||
| optimizeDeps: { | ||
| include: ['react-dom/server'], | ||
| }, | ||
| }); | ||
| }); |
| import type { Handler } from 'hono'; | ||
| import type { FC } from 'react'; | ||
| declare module 'virtual:comity-islands' { | ||
| const components: Record<string, () => Promise<{ default: FC }>>; | ||
| const filename: string | undefined; | ||
| export { components, filename }; | ||
| } | ||
| declare module 'virtual:comity-routes' { | ||
| const routes: Record<string, Handler>; | ||
| export { routes }; | ||
| } | ||
| declare module '@comity/react' { | ||
| interface Props { | ||
| title: string; | ||
| } | ||
| } |
| import type { ReactElement, PropsWithChildren } from 'react'; | ||
| declare module 'hono' { | ||
| interface DefaultRenderer { | ||
| (children: ReactElement, props?: PropsWithChildren): Promise<Response>; | ||
| } | ||
| } |
| /// <reference types="vite/client" /> | ||
| declare module '*?hash' { | ||
| const src: string; | ||
| export default src; | ||
| } |
+6
-6
| { | ||
| "name": "create-comity", | ||
| "version": "0.3.4", | ||
| "version": "0.4.0", | ||
| "description": "Starter for Comity", | ||
@@ -15,10 +15,10 @@ "type": "module", | ||
| "@types/ejs": "^3.1.5", | ||
| "@types/node": "^20.17.24", | ||
| "@vitest/coverage-v8": "^3.0.8", | ||
| "@types/node": "^20.17.28", | ||
| "@vitest/coverage-v8": "^3.0.9", | ||
| "copy-folder-util": "^1.1.5", | ||
| "typescript": "^5.8.2", | ||
| "vitest": "^3.0.8" | ||
| "vitest": "^3.0.9" | ||
| }, | ||
| "dependencies": { | ||
| "@inquirer/prompts": "^7.3.3", | ||
| "@inquirer/prompts": "^7.4.0", | ||
| "ejs": "^3.1.10" | ||
@@ -28,3 +28,3 @@ }, | ||
| "build": "tsc", | ||
| "prepublish": "copy-folder ../../templates templates --summary && tsc", | ||
| "prepublish": "node scripts/update-templates.mjs && copy-folder ../../templates templates --summary && tsc", | ||
| "clean": "tsc --build --clean", | ||
@@ -31,0 +31,0 @@ "test": "vitest run --coverage" |
@@ -10,12 +10,12 @@ { | ||
| "dependencies": { | ||
| "@comity/graphql": "^0.3.4", | ||
| "@comity/graphql": "^0.4.0", | ||
| "@envelop/core": "^5.2.3", | ||
| "graphql": "^16.10.0", | ||
| "hono": "^4.7.4" | ||
| "hono": "^4.7.5" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^4.20250313.0", | ||
| "@cloudflare/workers-types": "^4.20250327.0", | ||
| "@hono/vite-cloudflare-pages": "^0.4.2", | ||
| "wrangler": "^3.114.1" | ||
| "wrangler": "^3.114.3" | ||
| } | ||
| } | ||
| } |
@@ -6,3 +6,3 @@ { | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "dev": "comity dev", | ||
| "build": "vite build --mode client && vite build", | ||
@@ -13,13 +13,13 @@ "preview": "wrangler pages dev dist", | ||
| "dependencies": { | ||
| "@comity/islands": "^0.3.4", | ||
| "hono": "^4.7.4", | ||
| "nanostores": "^0.11.4" | ||
| "@comity/islands": "^0.4.0", | ||
| "hono": "^4.7.5" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^4.20250312.0", | ||
| "@cloudflare/workers-types": "^4.20250327.0", | ||
| "@hono/vite-cloudflare-pages": "^0.4.2", | ||
| "@hono/vite-dev-server": "^0.18.3", | ||
| "vite": "^6.2.1", | ||
| "wrangler": "^3.114.1" | ||
| "vite": "^6.2.3", | ||
| "vite-plugin-dynamic-import": "^1.6.0", | ||
| "wrangler": "^3.114.3" | ||
| } | ||
| } | ||
| } |
| import { createClient } from '@comity/islands/client'; | ||
| import hono from '@comity/islands'; | ||
| // import islands | ||
| import islands from 'virtual:comity-islands'; | ||
@@ -10,3 +8,2 @@ const debug = false; | ||
| debug, | ||
| islands, | ||
| integrations: { | ||
@@ -13,0 +10,0 @@ hono, |
| import type { FC, PropsWithChildren } from 'hono/jsx'; | ||
| import { useState } from 'hono/jsx'; | ||
| import { withHydration } from '@comity/islands'; | ||
| import Badge from './badge.island.js'; | ||
| import { Island } from '@comity/islands/components'; | ||
| import badge from './badge.island.js?hash'; | ||
| import style from './counter.module.css'; | ||
@@ -11,6 +11,3 @@ | ||
| // test island inside island | ||
| const BadgeIsland = withHydration(Badge); | ||
| const Counter: FC<PropsWithChildren<Props>> = (props) => { | ||
| export const Counter: FC<PropsWithChildren<Props>> = (props) => { | ||
| const [count, setCount] = useState(props.count); | ||
@@ -26,5 +23,10 @@ const onClickHandler = () => { | ||
| <> | ||
| <div className={style.badge}> | ||
| <BadgeIsland client:load client="hydrated" server="SSR" /> | ||
| </div> | ||
| {/* <div className={style.badge}> | ||
| <Island | ||
| $client:load | ||
| $component={badge} | ||
| client="hydrated" | ||
| server="SSR" | ||
| /> | ||
| </div> */} | ||
| <p className={style.count}>Count: {count}</p> | ||
@@ -31,0 +33,0 @@ <button className={style.button} onClick={onClickHandler}> |
| import { Server } from '@comity/islands'; | ||
| import { renderer } from './renderer.js'; | ||
| // import routes | ||
| import routes from 'virtual:comity-routes'; | ||
| import { routes } from 'virtual:comity-routes'; | ||
@@ -6,0 +5,0 @@ const app = new Server(); |
| import { honoRenderer } from '@comity/islands'; | ||
| export const renderer = honoRenderer(({ children, title }) => { | ||
| return ( | ||
| <html lang="en"> | ||
| <head> | ||
| <title>{title}</title> | ||
| </head> | ||
| <body>{children}</body> | ||
| {import.meta.env.PROD ? ( | ||
| <script type="module" src="/static/client.js" defer async></script> | ||
| ) : ( | ||
| <script type="module" src="/src/client.ts" defer async></script> | ||
| )} | ||
| </html> | ||
| ); | ||
| }); | ||
| export const renderer = honoRenderer( | ||
| ({ children, title }) => { | ||
| return ( | ||
| <html lang="en"> | ||
| <head> | ||
| <title>{title}</title> | ||
| </head> | ||
| <body>{children}</body> | ||
| {import.meta.env.PROD ? ( | ||
| <script type="module" src="/static/client.js" defer async></script> | ||
| ) : ( | ||
| <script type="module" src="/src/client.ts" defer async></script> | ||
| )} | ||
| </html> | ||
| ); | ||
| }, | ||
| { stream: true } | ||
| ); |
| import type { Context } from 'hono'; | ||
| import { withHydration } from '@comity/islands'; | ||
| import Counter from '~/components/counter.island.js'; | ||
| import { Island } from '@comity/islands/components'; | ||
| import counter from '~/components/counter.island.js?hash'; | ||
| // enable islands architecture | ||
| const CounterIsland = withHydration(Counter); | ||
| export default (ctx: Context) => | ||
| export default async (ctx: Context) => | ||
| ctx.render( | ||
@@ -15,3 +12,3 @@ <div> | ||
| <div style={{ backgroundColor: 'aliceblue', padding: '20px' }}> | ||
| <CounterIsland client:load count={10} /> | ||
| <Island $client:load $component={counter} count={10} /> | ||
| </div> | ||
@@ -22,3 +19,3 @@ <div style={{ padding: '30px 5px 5px' }}> | ||
| <div style={{ backgroundColor: 'wheat', padding: '20px' }}> | ||
| <CounterIsland client:idle count={20} /> | ||
| <Island $client:idle $component={counter} count={20} /> | ||
| </div> | ||
@@ -30,3 +27,7 @@ <div style={{ padding: '30px 5px 5px' }}> | ||
| <div style={{ backgroundColor: 'gold', padding: '20px' }}> | ||
| <CounterIsland client:media="(max-width: 600px)" count={30} /> | ||
| <Island | ||
| $client:media="(max-width: 600px)" | ||
| $component={counter} | ||
| count={30} | ||
| /> | ||
| </div> | ||
@@ -38,3 +39,3 @@ <div style={{ padding: '30px 5px 5px', lineHeight: '1.5em' }}> | ||
| <div style={{ marginTop: '1000px', padding: '20px' }}> | ||
| <CounterIsland client:visible count={30} /> | ||
| <Island $client:visible $component={counter} count={30} /> | ||
| </div> | ||
@@ -41,0 +42,0 @@ </div>, |
@@ -17,5 +17,11 @@ { | ||
| "target": "ES2022", | ||
| "types": ["@cloudflare/workers-types", "node", "vite/client"] | ||
| "types": [ | ||
| "@cloudflare/workers-types", | ||
| "node", | ||
| "vite/client", | ||
| "@comity/islands" | ||
| ] | ||
| }, | ||
| "include": ["./src"] | ||
| "exclude": ["**/*.test.ts"], | ||
| "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*.d.ts"] | ||
| } |
@@ -29,7 +29,3 @@ import { resolve } from 'node:path'; | ||
| }, | ||
| plugins: [ | ||
| comityIslands({ | ||
| framework: 'hono', | ||
| }), | ||
| ], | ||
| plugins: [comityIslands()], | ||
| }; | ||
@@ -50,5 +46,3 @@ } | ||
| }), | ||
| comityIslands({ | ||
| framework: 'hono', | ||
| }), | ||
| comityIslands(), | ||
| comityRoutes(), | ||
@@ -55,0 +49,0 @@ ], |
@@ -12,6 +12,6 @@ { | ||
| "dependencies": { | ||
| "@comity/islands": "^0.3.4", | ||
| "@comity/react": "^0.3.4", | ||
| "@comity/islands": "^0.4.0", | ||
| "@comity/react": "^0.4.0", | ||
| "@nanostores/react": "^0.8.4", | ||
| "hono": "^4.7.4", | ||
| "hono": "^4.7.5", | ||
| "nanostores": "^0.11.4", | ||
@@ -22,10 +22,10 @@ "react": "^18.3.1", | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^4.20250312.0", | ||
| "@cloudflare/workers-types": "^4.20250327.0", | ||
| "@hono/vite-cloudflare-pages": "^0.4.2", | ||
| "@hono/vite-dev-server": "^0.18.3", | ||
| "@types/react": "^18.3.18", | ||
| "@types/react": "^18.3.20", | ||
| "@types/react-dom": "^18.3.5", | ||
| "vite": "^6.2.1", | ||
| "wrangler": "^3.114.1" | ||
| "vite": "^6.2.3", | ||
| "wrangler": "^3.114.3" | ||
| } | ||
| } | ||
| } |
| import { createClient } from '@comity/islands/client'; | ||
| import react from '@comity/react'; | ||
| // import islands | ||
| import islands from 'virtual:comity-islands'; | ||
@@ -10,3 +8,2 @@ const debug = false; | ||
| debug, | ||
| islands, | ||
| integrations: { | ||
@@ -13,0 +10,0 @@ react, |
@@ -1,2 +0,2 @@ | ||
| import type { FunctionComponent } from 'react'; | ||
| import type { FC } from 'react'; | ||
| import { useEffect, useState } from 'react'; | ||
@@ -9,4 +9,3 @@ | ||
| // testing island inside island | ||
| const Badge: FunctionComponent<Props> = ({ client, server }) => { | ||
| export const Badge: FC<Props> = ({ client, server }) => { | ||
| const [env, setEnv] = useState<string>(server || 'server'); | ||
@@ -13,0 +12,0 @@ |
| import type { FunctionComponent, PropsWithChildren } from 'react'; | ||
| import { useState } from 'react'; | ||
| import { withHydration } from '@comity/react'; | ||
| import Badge from './badge.island.js'; | ||
| import { Island } from '@comity/react/components'; | ||
| import badge from './badge.js?hash'; | ||
| import style from './counter.module.css'; | ||
@@ -11,6 +11,3 @@ | ||
| // test island inside island | ||
| const BadgeIsland = withHydration(Badge); | ||
| const Counter: FunctionComponent<PropsWithChildren<Props>> = (props) => { | ||
| export const Counter: FunctionComponent<PropsWithChildren<Props>> = (props) => { | ||
| const [count, setCount] = useState(props.count); | ||
@@ -26,5 +23,10 @@ const onClickHandler = () => { | ||
| <> | ||
| <div className={style.badge}> | ||
| <BadgeIsland client:load client="hydrated" server="SSR" /> | ||
| </div> | ||
| {/* <div className={style.badge}> | ||
| <Island | ||
| $client:load | ||
| $component={badge} | ||
| client="hydrated" | ||
| server="SSR" | ||
| /> | ||
| </div> */} | ||
| <p className={style.count}>Count: {count}</p> | ||
@@ -31,0 +33,0 @@ <button className={style.button} onClick={onClickHandler}> |
@@ -7,3 +7,3 @@ import type { FunctionComponent } from 'react'; | ||
| // shared state PoC | ||
| const User: FunctionComponent = () => { | ||
| export const User: FunctionComponent = () => { | ||
| const [loading, setLoading] = useState(true); | ||
@@ -10,0 +10,0 @@ const name = useStore(userStore); |
| import { Server } from '@comity/islands'; | ||
| import { renderer } from './renderer.js'; | ||
| // import routes | ||
| import routes from 'virtual:comity-routes'; | ||
| import { routes } from 'virtual:comity-routes'; | ||
@@ -6,0 +5,0 @@ const app = new Server(); |
| import { reactRenderer } from '@comity/react'; | ||
| export const renderer = reactRenderer(({ children, title }) => { | ||
| return ( | ||
| <html lang="en"> | ||
| <head> | ||
| <title>{title}</title> | ||
| </head> | ||
| <body>{children}</body> | ||
| {import.meta.env.PROD ? ( | ||
| <script type="module" src="/static/client.js" defer async></script> | ||
| ) : ( | ||
| <script type="module" src="/src/client.ts" defer async></script> | ||
| )} | ||
| </html> | ||
| ); | ||
| }); | ||
| export const renderer = reactRenderer( | ||
| ({ children, title }) => { | ||
| return ( | ||
| <html lang="en"> | ||
| <head> | ||
| <title>{title}</title> | ||
| </head> | ||
| <body>{children}</body> | ||
| {import.meta.env.PROD ? ( | ||
| <script type="module" src="/static/client.js" defer async></script> | ||
| ) : ( | ||
| <script type="module" src="/src/client.ts" defer async></script> | ||
| )} | ||
| </html> | ||
| ); | ||
| }, | ||
| { stream: true } | ||
| ); |
| import type { Context } from 'hono'; | ||
| import { withHydration } from '@comity/react'; | ||
| import Counter from '~/components/counter.island.js'; | ||
| import User from '~/components/user.island.js'; | ||
| import { Suspense } from 'react'; | ||
| import { Island } from '@comity/react/components'; | ||
| import counter from '~/components/counter.island.js?hash'; | ||
| import user from '~/components/user.island.js?hash'; | ||
| // enable islands architecture | ||
| const CounterIsland = withHydration(Counter); | ||
| const UserIsland = withHydration(User); | ||
| export default (ctx: Context) => | ||
| export default async (ctx: Context) => | ||
| ctx.render( | ||
| <div> | ||
| <div style={{ backgroundColor: 'aquamarine', padding: '20px' }}> | ||
| <UserIsland client:load /> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <Island $client:load $component={user} /> | ||
| </Suspense> | ||
| </div> | ||
@@ -20,3 +19,5 @@ <div style={{ padding: '30px 5px 5px' }}> | ||
| <div style={{ backgroundColor: 'aliceblue', padding: '20px' }}> | ||
| <CounterIsland client:load count={10} /> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <Island $client:load $component={counter} count={10} /> | ||
| </Suspense> | ||
| </div> | ||
@@ -27,3 +28,5 @@ <div style={{ padding: '30px 5px 5px' }}> | ||
| <div style={{ backgroundColor: 'wheat', padding: '20px' }}> | ||
| <CounterIsland client:idle count={20} /> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <Island $client:idle $component={counter} count={20} /> | ||
| </Suspense> | ||
| </div> | ||
@@ -35,3 +38,9 @@ <div style={{ padding: '30px 5px 5px' }}> | ||
| <div style={{ backgroundColor: 'gold', padding: '20px' }}> | ||
| <CounterIsland client:media="(max-width: 600px)" count={30} /> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <Island | ||
| $client:media="(max-width: 600px)" | ||
| $component={counter} | ||
| count={30} | ||
| /> | ||
| </Suspense> | ||
| </div> | ||
@@ -43,6 +52,8 @@ <div style={{ padding: '30px 5px 5px', lineHeight: '1.5em' }}> | ||
| <div style={{ marginTop: '1000px', padding: '20px' }}> | ||
| <CounterIsland client:visible count={30} /> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <Island $client:visible $component={counter} count={30} /> | ||
| </Suspense> | ||
| </div> | ||
| </div>, | ||
| { title: 'Comity | Hono renderer example' } | ||
| { title: 'Comity | React renderer example' } | ||
| ); |
@@ -19,3 +19,4 @@ { | ||
| }, | ||
| "include": ["./src"] | ||
| "exclude": ["**/*.test.ts"], | ||
| "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*.d.ts"] | ||
| } |
@@ -1,22 +0,22 @@ | ||
| import { resolve } from "node:path"; | ||
| import { defineConfig } from "vite"; | ||
| import build from "@hono/vite-cloudflare-pages"; | ||
| import devServer from "@hono/vite-dev-server"; | ||
| import adapter from "@hono/vite-dev-server/cloudflare"; | ||
| import { comityRoutes, comityIslands } from "@comity/islands/vite"; | ||
| import { resolve } from 'node:path'; | ||
| import { defineConfig } from 'vite'; | ||
| import build from '@hono/vite-cloudflare-pages'; | ||
| import devServer from '@hono/vite-dev-server'; | ||
| import adapter from '@hono/vite-dev-server/cloudflare'; | ||
| import { comityRoutes, comityIslands } from '@comity/islands/vite'; | ||
| export default defineConfig(({ mode }) => { | ||
| const alias = { | ||
| "~": resolve(__dirname, "./src"), | ||
| '~': resolve(__dirname, './src'), | ||
| }; | ||
| if (mode === "client") { | ||
| if (mode === 'client') { | ||
| return { | ||
| build: { | ||
| rollupOptions: { | ||
| input: ["./src/client.ts"], | ||
| input: ['./src/client.ts'], | ||
| output: { | ||
| entryFileNames: "static/client.js", | ||
| chunkFileNames: "static/assets/[name]-[hash].js", | ||
| assetFileNames: "static/assets/[name].[ext]", | ||
| entryFileNames: 'static/client.js', | ||
| chunkFileNames: 'static/assets/[name]-[hash].js', | ||
| assetFileNames: 'static/assets/[name].[ext]', | ||
| }, | ||
@@ -29,7 +29,3 @@ }, | ||
| }, | ||
| plugins: [ | ||
| comityIslands({ | ||
| framework: "react", | ||
| }), | ||
| ], | ||
| plugins: [comityIslands()], | ||
| }; | ||
@@ -40,3 +36,3 @@ } | ||
| ssr: { | ||
| external: ["react", "react-dom"], | ||
| external: ['react', 'react-dom', 'react-dom/server'], | ||
| }, | ||
@@ -48,11 +44,9 @@ resolve: { | ||
| build({ | ||
| entry: "src/index.ts", | ||
| entry: 'src/index.ts', | ||
| }), | ||
| devServer({ | ||
| adapter, | ||
| entry: "src/index.ts", | ||
| entry: 'src/index.ts', | ||
| }), | ||
| comityIslands({ | ||
| framework: "react", | ||
| }), | ||
| comityIslands(), | ||
| comityRoutes(), | ||
@@ -59,0 +53,0 @@ ], |
| import type { Handler } from 'hono'; | ||
| declare module 'hono' { | ||
| interface DefaultRenderer { | ||
| (children: ReactElement, props?: Props): Promise<Response>; | ||
| } | ||
| } | ||
| declare module '@comity/islands' { | ||
| interface Props { | ||
| title: string; | ||
| } | ||
| } | ||
| declare module 'virtual:comity-routes' { | ||
| const routes: Record<string, Handler>; | ||
| export default routes; | ||
| } |
| import { atom } from 'nanostores'; | ||
| export const userStore = atom<string | undefined>(undefined); |
| import type { Handler } from 'hono'; | ||
| import type {} from 'react'; | ||
| import type {} from '@comity/react'; | ||
| declare module 'hono' { | ||
| interface DefaultRenderer { | ||
| (children: ReactElement, props?: Props): Promise<Response>; | ||
| } | ||
| } | ||
| declare module '@comity/react' { | ||
| interface Props { | ||
| title: string; | ||
| } | ||
| } | ||
| declare module 'virtual:comity-routes' { | ||
| const routes: Record<string, Handler>; | ||
| export default routes; | ||
| } |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
57615
62.7%97
79.63%1705
70.5%3
200%2
100%Updated