Skip to content

Commit

Permalink
feat: client components
Browse files Browse the repository at this point in the history
  • Loading branch information
joseferben committed Oct 6, 2024
1 parent a6b270c commit fab1f49
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 22 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ yarn-error.log*
.DS_Store
*.pem

data.db*
data.db*
static
4 changes: 2 additions & 2 deletions example/0-todo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Hono } from "hono";
import { jsxRenderer } from "hono/jsx-renderer";
import { logger } from "hono/logger";
import { form, store } from "plainstack";
import { bunSqlite, secret } from "plainstack/bun";
import { secret, sqlite } from "plainstack/bun";
import { session } from "plainstack/session";

interface Items {
Expand All @@ -15,7 +15,7 @@ interface DB {
items: Items;
}

const { database, migrate } = bunSqlite<DB>();
const { database, migrate } = sqlite<DB>();

await migrate(({ schema }) => {
return schema
Expand Down
18 changes: 13 additions & 5 deletions example/1-background-job.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,28 @@ import { Hono } from "hono";
import { jsxRenderer } from "hono/jsx-renderer";
import { logger } from "hono/logger";
import { JobStatus } from "plainjob";
import { job, perform, work } from "plainstack";
import { bunSqlite } from "plainstack/bun";
import { job, perform, schedule, work } from "plainstack";
import { sqlite } from "plainstack/bun";

const { queue } = bunSqlite();
const { queue } = sqlite();

const randomJob = job<string>({
name: "random",
name: "fails-randomly",
run: async ({ data }) => {
if (Math.random() > 0.5) throw new Error("Random error");
console.log("Processing job", data);
},
});

void work(queue, { job: randomJob }, {});
const minuteSchedule = schedule({
name: "every-minute",
cron: "* * * * *",
run: async () => {
console.log("this runs every minute");
},
});

void work(queue, { randomJob }, { minuteSchedule });

const app = new Hono();

Expand Down
27 changes: 27 additions & 0 deletions example/2-client-component/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Hono } from "hono";
import { serveStatic } from "hono/bun";
import { build } from "plainstack/bun";
import { render } from "plainstack/client";
import { Counter } from "./counter";

const app = new Hono();

app.use("/static/*", serveStatic({ root: "./example/2-client-component" }));

await build({
entrypoints: ["example/2-client-component/counter.tsx"],
outdir: "example/2-client-component/static",
});

app.get("/", async (c) => {
return c.html(
<html lang="en">
<body>
<div id="counter" />
{render(Counter, { path: "/static" })}
</body>
</html>,
);
});

export default app;
15 changes: 15 additions & 0 deletions example/2-client-component/counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useState } from "hono/jsx";
import { mount } from "plainstack/client";

export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
{/* biome-ignore lint/a11y/useButtonType: <explanation> */}
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

mount(Counter);
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"exports": {
".": "./dist/plainstack.js",
"./bun": "./dist/bun.js",
"./session": "./dist/middleware/session.js"
"./session": "./dist/middleware/session.js",
"./client": "./dist/client.js"
},
"types": "dist/plainstack.d.ts",
"scripts": {
Expand Down
32 changes: 26 additions & 6 deletions src/bun.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import { Database } from "bun:sqlite";
import { randomBytes } from "node:crypto";
import { readdir } from "node:fs/promises";
import type { BuildConfig as BunBuildConfig } from "bun";
import { CamelCasePlugin, Kysely } from "kysely";
import { BunSqliteDialect } from "kysely-bun-sqlite";
import { bun } from "plainjob";
import { migrate as migrate_ } from "./database";
import { test } from "./env";
import { prod, test } from "./env";
import { queue } from "./job";

export function bunSqlite<DB = unknown>() {
const sqlite = new Database(test() ? ":memory:" : "data.db", {
export function sqlite<DB = unknown>() {
const sqlite_ = new Database(test() ? ":memory:" : "data.db", {
strict: true,
});
const q = queue({
connection: bun(sqlite),
connection: bun(sqlite_),
});
const database = new Kysely<DB>({
dialect: new BunSqliteDialect({
database: sqlite,
database: sqlite_,
}),
plugins: [new CamelCasePlugin()],
});
const migrate = migrate_(database);
return { sqlite, database, migrate, queue: q };
return { sqlite: sqlite_, database, migrate, queue: q };
}

export async function secret(): Promise<string> {
Expand All @@ -36,3 +38,21 @@ export async function secret(): Promise<string> {
}
return newSecret;
}

type BuildConfig = Omit<BunBuildConfig, "entrypoints"> & {
entrypoints: string | string[];
};

export async function build(options: BuildConfig) {
const entrypointfiles =
typeof options.entrypoints === "string"
? await readdir(options.entrypoints)
: options.entrypoints;
return await Bun.build({
outdir: "static",
sourcemap: "linked",
minify: false, // TODO
...options,
entrypoints: entrypointfiles,
});
}
40 changes: 40 additions & 0 deletions src/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { kebabCase } from "change-case";
import { type Child, render as renderHono } from "hono/jsx/dom";
import type { JSX } from "hono/jsx/jsx-runtime";

export function render<T>(
Component: (props: T) => Child,
options: { path: string },
props?: T,
) {
const name = kebabCase(Component.name);
return (
<>
<div id={`${name}-data`} data-props={JSON.stringify(props)} />
<script type="module" defer src={`${options.path}/${name}.js`} />
</>
);
}

// biome-ignore lint/suspicious/noExplicitAny: <explanation>
export function mount(Component: (props?: any) => JSX.Element) {
if (typeof document !== "undefined") {
const name = kebabCase(Component.name);
const dataElement = document.getElementById(`${name}-data`);
if (!dataElement) {
throw new Error(
`unable to mount client component ${name}, data element not found. make sure you have a <div id="${name}-data"></div> in your html`,
);
}
const dataJson = dataElement.dataset.props;
const data = JSON.parse(dataJson ?? "{}");
console.info(`found data for client component ${name} in DOM`, data);
const targetElement = document.getElementById(name);
if (!targetElement) {
throw new Error(
`unable to render client component ${name}, target element not found. make sure you have a <div id="${name}"></div> in your html`,
);
}
renderHono(<Component {...data} />, targetElement);
}
}
4 changes: 2 additions & 2 deletions src/entity.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { bunSqlite } from "./bun";
import { sqlite } from "./bun";
import { rollback, store } from "./entity";

type Database = {
Expand All @@ -20,7 +20,7 @@ type Database = {
};

describe("entity crud operations", async () => {
const { database, migrate } = bunSqlite<Database>();
const { database, migrate } = sqlite<Database>();

await migrate(
({ schema }) =>
Expand Down
2 changes: 1 addition & 1 deletion src/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function isSchedule(schedule: unknown): schedule is Schedule {
);
}

export function defineSchedule(opts: {
export function schedule(opts: {
name: string;
cron: string;
run: () => Promise<void>;
Expand Down
3 changes: 1 addition & 2 deletions src/plainstack.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export { store } from "./entity";
export { migrate, rollback } from "./database";
export { test, dev, prod } from "./env";
export { session } from "./middleware/session";
export { job, queue, perform, work } from "./job";
export { schedule, job, queue, perform, work } from "./job";
export { form } from "./form";
export { type DB } from "./db";
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"paths": {
"plainstack": ["./src/plainstack.ts"],
"plainstack/session": ["./src/middleware/session.ts"],
"plainstack/bun": ["./src/bun.ts"]
"plainstack/bun": ["./src/bun.ts"],
"plainstack/client": ["./src/client.tsx"]
},

// bundler mode
Expand Down
3 changes: 2 additions & 1 deletion tsup.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { defineConfig } from "tsup";

export default defineConfig({
target: "node22",
target: "esnext",
format: ["esm"],
entry: [
"src/plainstack.ts",
"src/bun.ts",
"src/middleware/session.ts",
"src/db.ts",
"src/client.tsx",
],
external: ["bun:sqlite"],
dts: true,
Expand Down

0 comments on commit fab1f49

Please sign in to comment.