New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

create-comity

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

create-comity - npm Package Compare versions

Comparing version
0.3.4
to
0.4.0
+68
scripts/update-templates.mjs
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 &lt;= 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' }}>&darr;</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 &lt;= 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' }}>&darr;</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;
}