From a5f23e704ac55c62686c34ed804ef2f3aaec2cd2 Mon Sep 17 00:00:00 2001 From: Ross Robino Date: Wed, 18 Sep 2024 06:52:18 -0400 Subject: [PATCH] 0.13 (#23) * test * test * test * test * test * better error handling * rm error * test * redeploy * test * test * perf * update deps * preview static html files * improve static file serving * rm isr * deploy * test * isr * doc/format * rm isr * styles * docs --- .changeset/pretty-meals-buy.md | 66 +- .changeset/swift-llamas-jam.md | 5 + apps/cloudflare/src/+client.ts | 1 - apps/cloudflare/src/+server.ts | 9 - apps/cloudflare/src/{ => client}/+page.html | 5 +- apps/cloudflare/src/{ => client}/style.css | 0 apps/cloudflare/src/env.d.ts | 2 + apps/cloudflare/src/global.d.ts | 7 - apps/cloudflare/src/server/+app.ts | 16 + apps/cloudflare/src/static/+page.html | 12 - apps/cloudflare/tsconfig.json | 3 - apps/docs/package.json | 2 +- .../src/{+client.ts => client/+script.ts} | 2 +- .../+client.ts => client/docs/+script.ts} | 1 - .../src/{style.css => client/tailwind.css} | 0 apps/docs/src/content/_preview.md | 33 - apps/docs/src/content/migrate.md | 68 -- apps/docs/src/content/tutorial.md | 197 --- apps/docs/src/{global.d.ts => env.d.ts} | 3 +- apps/docs/src/generated/globals.md | 564 --------- .../docs/src/{+server.tsx => server/+app.tsx} | 34 +- .../docs/src/{ => server}/components/Edit.tsx | 0 .../docs/src/{ => server}/components/Hero.tsx | 0 .../src/{ => server}/components/Layout.tsx | 0 apps/docs/src/{ => server}/components/Nav.tsx | 14 +- apps/docs/src/{ => server}/components/svg.tsx | 19 + apps/docs/src/server/content/_preview.md | 34 + apps/docs/src/{ => server}/content/deploy.md | 21 +- apps/docs/src/server/content/examples.md | 37 + apps/docs/src/server/content/migrate.md | 53 + apps/docs/src/server/content/tutorial.md | 170 +++ .../docs/src/{ => server}/generated/README.md | 0 apps/docs/src/server/generated/globals.md | 344 ++++++ apps/tester/package.json | 5 +- apps/tester/public/public.html | 1 + apps/tester/src/+server.ts | 43 - apps/tester/src/+setup.ts | 13 - apps/tester/src/api/+server.ts | 11 - apps/tester/src/client/+client.ts | 1 - apps/tester/src/{ => client}/+page.html | 33 +- .../src/{+client.ts => client/+script.ts} | 3 +- apps/tester/src/{ => client}/react/+page.html | 4 +- apps/tester/src/{ => client}/react/App.tsx | 4 +- .../+client.tsx => client/react/react.tsx} | 0 apps/tester/src/client/style.css | 10 + apps/tester/src/dynamic/+page.html | 13 - apps/tester/src/dynamic/+server.js | 16 - apps/tester/src/env.d.ts | 2 + apps/tester/src/global.d.ts | 7 - apps/tester/src/half-static/+page.html | 15 - apps/tester/src/half-static/+server.ts | 18 - apps/tester/src/htmx/+page.html | 22 - apps/tester/src/htmx/partials/+client.js | 3 - apps/tester/src/htmx/partials/+server.tsx | 15 - .../src/htmx/partials/non-entry-import.ts | 1 - .../tester/src/htmx/static-partial/+page.html | 2 - apps/tester/src/mw/index.ts | 84 -- apps/tester/src/react/+server.tsx | 24 - apps/tester/src/server/+app.tsx | 60 + apps/tester/src/static-page/+page.html | 13 - apps/tester/src/static-prerender/+page.html | 19 - apps/tester/src/static-prerender/+server.ts | 48 - .../src/static-prerender/content/post-1.md | 3 - .../src/static-prerender/content/post-2.md | 3 - apps/tester/src/style.css | 10 - apps/tester/src/svelte/+client.ts | 10 - apps/tester/src/svelte/+page.html | 9 - apps/tester/src/svelte/+server.ts | 13 - apps/tester/src/svelte/App.svelte | 23 - apps/tester/svelte.config.js | 7 - apps/tester/tsconfig.json | 1 - apps/tester/vite.config.ts | 7 - package-lock.json | 1078 ++++++++++------- package.json | 8 +- packages/create-domco/package.json | 4 +- .../create-domco/src/dependencies/index.ts | 7 +- packages/create-domco/src/index.ts | 6 +- .../create-domco/src/template-files/app.ts | 29 + .../src/template-files/deno-json.ts | 5 +- .../{global-types.ts => env-types.ts} | 9 +- .../src/template-files/gitignore.ts | 2 + .../src/template-files/package-json.ts | 3 +- .../src/template-files/page-html.ts | 9 +- .../src/template-files/prettier.ts | 6 +- .../src/template-files/style-css.ts | 7 +- .../src/template-files/tailwind.ts | 2 +- .../src/template-files/tsconfig-json.ts | 1 - packages/domco/package.json | 29 +- .../domco/src/adapter/cloudflare/index.ts | 29 +- packages/domco/src/adapter/deno/index.ts | 27 +- packages/domco/src/adapter/vercel/index.ts | 135 ++- packages/domco/src/app/dev/index.ts | 143 --- packages/domco/src/app/index.ts | 49 - packages/domco/src/app/mw/index.ts | 15 - packages/domco/src/app/util/index.ts | 173 --- packages/domco/src/constants/index.ts | 23 +- packages/domco/src/index.ts | 2 +- .../request-listener => listener}/index.ts | 8 +- packages/domco/src/node/serve-static/index.ts | 90 -- packages/domco/src/plugin/adapter/index.ts | 67 +- packages/domco/src/plugin/config/index.ts | 113 +- .../src/plugin/configure-server/index.ts | 112 +- packages/domco/src/plugin/entry/index.ts | 31 - packages/domco/src/plugin/html/index.ts | 255 ---- packages/domco/src/plugin/index.ts | 24 +- packages/domco/src/plugin/lifecycle/index.ts | 277 ++++- packages/domco/src/plugin/manifest/index.ts | 37 - packages/domco/src/plugin/manifest/types.d.ts | 4 - packages/domco/src/plugin/page/index.ts | 90 ++ packages/domco/src/plugin/routes/index.ts | 59 - packages/domco/src/plugin/routes/types.d.ts | 4 - packages/domco/src/plugin/script/index.ts | 156 +++ packages/domco/src/types/env.d.ts | 25 + packages/domco/src/types/global.d.ts | 7 - packages/domco/src/types/helper/index.ts | 2 - packages/domco/src/types/index.ts | 129 ++ packages/domco/src/types/private/index.ts | 18 - packages/domco/src/types/public/index.ts | 287 ----- packages/domco/src/util/code-size/index.ts | 12 +- .../domco/src/util/create-routes/index.ts | 79 -- packages/domco/src/util/fs/index.ts | 33 +- packages/domco/src/util/perf/index.ts | 13 + 122 files changed, 2542 insertions(+), 3489 deletions(-) create mode 100644 .changeset/swift-llamas-jam.md delete mode 100644 apps/cloudflare/src/+client.ts delete mode 100644 apps/cloudflare/src/+server.ts rename apps/cloudflare/src/{ => client}/+page.html (81%) rename apps/cloudflare/src/{ => client}/style.css (100%) create mode 100644 apps/cloudflare/src/env.d.ts delete mode 100644 apps/cloudflare/src/global.d.ts create mode 100644 apps/cloudflare/src/server/+app.ts delete mode 100644 apps/cloudflare/src/static/+page.html rename apps/docs/src/{+client.ts => client/+script.ts} (93%) rename apps/docs/src/{lib/docs/+client.ts => client/docs/+script.ts} (99%) rename apps/docs/src/{style.css => client/tailwind.css} (100%) delete mode 100644 apps/docs/src/content/_preview.md delete mode 100644 apps/docs/src/content/migrate.md delete mode 100644 apps/docs/src/content/tutorial.md rename apps/docs/src/{global.d.ts => env.d.ts} (72%) delete mode 100644 apps/docs/src/generated/globals.md rename apps/docs/src/{+server.tsx => server/+app.tsx} (66%) rename apps/docs/src/{ => server}/components/Edit.tsx (100%) rename apps/docs/src/{ => server}/components/Hero.tsx (100%) rename apps/docs/src/{ => server}/components/Layout.tsx (100%) rename apps/docs/src/{ => server}/components/Nav.tsx (96%) rename apps/docs/src/{ => server}/components/svg.tsx (84%) create mode 100644 apps/docs/src/server/content/_preview.md rename apps/docs/src/{ => server}/content/deploy.md (75%) create mode 100644 apps/docs/src/server/content/examples.md create mode 100644 apps/docs/src/server/content/migrate.md create mode 100644 apps/docs/src/server/content/tutorial.md rename apps/docs/src/{ => server}/generated/README.md (100%) create mode 100644 apps/docs/src/server/generated/globals.md create mode 100644 apps/tester/public/public.html delete mode 100644 apps/tester/src/+server.ts delete mode 100644 apps/tester/src/+setup.ts delete mode 100644 apps/tester/src/api/+server.ts delete mode 100644 apps/tester/src/client/+client.ts rename apps/tester/src/{ => client}/+page.html (52%) rename apps/tester/src/{+client.ts => client/+script.ts} (72%) rename apps/tester/src/{ => client}/react/+page.html (63%) rename apps/tester/src/{ => client}/react/App.tsx (65%) rename apps/tester/src/{react/+client.tsx => client/react/react.tsx} (100%) create mode 100644 apps/tester/src/client/style.css delete mode 100644 apps/tester/src/dynamic/+page.html delete mode 100644 apps/tester/src/dynamic/+server.js create mode 100644 apps/tester/src/env.d.ts delete mode 100644 apps/tester/src/global.d.ts delete mode 100644 apps/tester/src/half-static/+page.html delete mode 100644 apps/tester/src/half-static/+server.ts delete mode 100644 apps/tester/src/htmx/+page.html delete mode 100644 apps/tester/src/htmx/partials/+client.js delete mode 100644 apps/tester/src/htmx/partials/+server.tsx delete mode 100644 apps/tester/src/htmx/partials/non-entry-import.ts delete mode 100644 apps/tester/src/htmx/static-partial/+page.html delete mode 100644 apps/tester/src/mw/index.ts delete mode 100644 apps/tester/src/react/+server.tsx create mode 100644 apps/tester/src/server/+app.tsx delete mode 100644 apps/tester/src/static-page/+page.html delete mode 100644 apps/tester/src/static-prerender/+page.html delete mode 100644 apps/tester/src/static-prerender/+server.ts delete mode 100644 apps/tester/src/static-prerender/content/post-1.md delete mode 100644 apps/tester/src/static-prerender/content/post-2.md delete mode 100644 apps/tester/src/style.css delete mode 100644 apps/tester/src/svelte/+client.ts delete mode 100644 apps/tester/src/svelte/+page.html delete mode 100644 apps/tester/src/svelte/+server.ts delete mode 100644 apps/tester/src/svelte/App.svelte delete mode 100644 apps/tester/svelte.config.js create mode 100644 packages/create-domco/src/template-files/app.ts rename packages/create-domco/src/template-files/{global-types.ts => env-types.ts} (53%) delete mode 100644 packages/domco/src/app/dev/index.ts delete mode 100644 packages/domco/src/app/index.ts delete mode 100644 packages/domco/src/app/mw/index.ts delete mode 100644 packages/domco/src/app/util/index.ts rename packages/domco/src/{node/request-listener => listener}/index.ts (97%) delete mode 100644 packages/domco/src/node/serve-static/index.ts delete mode 100644 packages/domco/src/plugin/entry/index.ts delete mode 100644 packages/domco/src/plugin/html/index.ts delete mode 100644 packages/domco/src/plugin/manifest/index.ts delete mode 100644 packages/domco/src/plugin/manifest/types.d.ts create mode 100644 packages/domco/src/plugin/page/index.ts delete mode 100644 packages/domco/src/plugin/routes/index.ts delete mode 100644 packages/domco/src/plugin/routes/types.d.ts create mode 100644 packages/domco/src/plugin/script/index.ts create mode 100644 packages/domco/src/types/env.d.ts delete mode 100644 packages/domco/src/types/global.d.ts create mode 100644 packages/domco/src/types/index.ts delete mode 100644 packages/domco/src/types/private/index.ts delete mode 100644 packages/domco/src/types/public/index.ts delete mode 100644 packages/domco/src/util/create-routes/index.ts create mode 100644 packages/domco/src/util/perf/index.ts diff --git a/.changeset/pretty-meals-buy.md b/.changeset/pretty-meals-buy.md index 2470265..ff506e4 100644 --- a/.changeset/pretty-meals-buy.md +++ b/.changeset/pretty-meals-buy.md @@ -2,4 +2,68 @@ "domco": minor --- -rm default immutable headers - leave to adapters instead +Server framework agnostic + +This project had a lot of overlap with HonoX, HonoX should be the default if you are wanting all of the features Hono provides like client components. This update removes the dependency on Hono and making the library framework agnostic. Hono can still be easily used with domco (see below). + +This makes domco have no dependencies other than Vite. You can now build your app with vanilla JS without any external libraries. You can now use any server framework like Hono that provides a function that handles a web `Request` and returns a `Response`. This update also simplifies domco's API and refactors much of the codebase to make it smaller and builds faster. + +## Overview of Changes + +- `+server` renamed to `+app` +- `+client` renamed to `+script` +- Instead of exporting the `app` as the default export, you now must export `app.fetch` as a named `handler` export. +- Removes `page`, `client`, and `server` context variables. +- `page` is replaced with the `client:page` virtual module. +- `script` is replaced with the `client:script` virtual module. +- The `server` context variable is removed. This is better handled by the user - perhaps with libraries like `ofetch`. +- The `tags` imported from `client:script` are now just strings, so you'll need to pass them through `hono/html` - `raw` function to pass them into a JSX template if you were using them directly in Hono. +- Multiple `+server` entry points are removed in favor of just one `src/server/+app` entry. Note this is located within `src/server/` now instead of directly in `src/`. +- Removes `+setup` - since domco no longer mounts routes, user can control the entire lifecycle through the `handler`. +- Removes default immutable headers - leave to adapters instead. +- Import `handler` from `dist/server/app.js` instead of the `createApp` export. +- Script entry points are no longer automatically injected into pages in the same directory. +- Static pages must be prerendered, nothing from `src/client/` is included in the app if not imported into the server entry. +- d.ts changes - instead of adding the context variable map, you now just need to add types for the virtual modules from `domco/env`. + +```ts +/// +/// +``` + +## Examples + +### Vanilla + +```ts +import { html } from "client:page"; +import type { Handler } from "domco"; + +export const handler: Handler = (req) => { + const { pathname } = new URL(req.url); + + if (pathname === "/") { + return new Response(html, { + headers: { + "Content-Type": "text/html", + }, + }); + } + + return new Response("Not found", { status: 404 }); +}; +``` + +### Hono - Migrate from 0.12 + +```ts +// src/server/+app.ts +import { html } from "client:page"; +import { Hono } from "hono"; + +const app = new Hono(); + +app.use((c) => c.html(html)); + +export const handler = app.fetch; +``` diff --git a/.changeset/swift-llamas-jam.md b/.changeset/swift-llamas-jam.md new file mode 100644 index 0000000..d604c05 --- /dev/null +++ b/.changeset/swift-llamas-jam.md @@ -0,0 +1,5 @@ +--- +"create-domco": minor +--- + +Updates template to 0.13 diff --git a/apps/cloudflare/src/+client.ts b/apps/cloudflare/src/+client.ts deleted file mode 100644 index 6b2b3db..0000000 --- a/apps/cloudflare/src/+client.ts +++ /dev/null @@ -1 +0,0 @@ -console.log("hello world"); diff --git a/apps/cloudflare/src/+server.ts b/apps/cloudflare/src/+server.ts deleted file mode 100644 index 635368d..0000000 --- a/apps/cloudflare/src/+server.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Hono } from "hono"; - -export const prerender = true; - -const app = new Hono(); - -app.get("/", (c) => c.html(c.var.page())); - -export default app; diff --git a/apps/cloudflare/src/+page.html b/apps/cloudflare/src/client/+page.html similarity index 81% rename from apps/cloudflare/src/+page.html rename to apps/cloudflare/src/client/+page.html index bd9c7ab..67b3b8e 100644 --- a/apps/cloudflare/src/+page.html +++ b/apps/cloudflare/src/client/+page.html @@ -4,7 +4,7 @@ - + Cloudflare Tester @@ -12,9 +12,6 @@

Hello Cloudflare

-

- Static -

diff --git a/apps/cloudflare/src/style.css b/apps/cloudflare/src/client/style.css similarity index 100% rename from apps/cloudflare/src/style.css rename to apps/cloudflare/src/client/style.css diff --git a/apps/cloudflare/src/env.d.ts b/apps/cloudflare/src/env.d.ts new file mode 100644 index 0000000..b1d9caa --- /dev/null +++ b/apps/cloudflare/src/env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/apps/cloudflare/src/global.d.ts b/apps/cloudflare/src/global.d.ts deleted file mode 100644 index 65d5a21..0000000 --- a/apps/cloudflare/src/global.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// -import type { DomcoContextVariableMap } from "domco"; -import "hono"; - -declare module "hono" { - interface ContextVariableMap extends DomcoContextVariableMap {} -} diff --git a/apps/cloudflare/src/server/+app.ts b/apps/cloudflare/src/server/+app.ts new file mode 100644 index 0000000..05b04bf --- /dev/null +++ b/apps/cloudflare/src/server/+app.ts @@ -0,0 +1,16 @@ +import { html } from "client:page"; +import type { Handler } from "domco"; + +export const handler: Handler = (req) => { + const { pathname } = new URL(req.url); + + if (pathname === "/") { + return new Response(html, { + headers: { + "Content-Type": "text/html", + }, + }); + } + + return new Response("Not found", { status: 404 }); +}; diff --git a/apps/cloudflare/src/static/+page.html b/apps/cloudflare/src/static/+page.html deleted file mode 100644 index ac133d3..0000000 --- a/apps/cloudflare/src/static/+page.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Static Page - - -

Static Page

- Home - - diff --git a/apps/cloudflare/tsconfig.json b/apps/cloudflare/tsconfig.json index 6649a36..ee49bd0 100644 --- a/apps/cloudflare/tsconfig.json +++ b/apps/cloudflare/tsconfig.json @@ -1,9 +1,6 @@ { "extends": "@robino/tsconfig/bundler.json", "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "hono/jsx", - "paths": { "@": ["./src"], "@/*": ["./src/*"] diff --git a/apps/docs/package.json b/apps/docs/package.json index e117e04..6d80a06 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -13,7 +13,7 @@ "autoprefixer": "^10.4.20", "domco": "*", "drab": "^5.4.2", - "tailwindcss": "^3.4.10", + "tailwindcss": "^3.4.12", "uico": "^0.3.1" } } diff --git a/apps/docs/src/+client.ts b/apps/docs/src/client/+script.ts similarity index 93% rename from apps/docs/src/+client.ts rename to apps/docs/src/client/+script.ts index 8e453f5..8f543a7 100644 --- a/apps/docs/src/+client.ts +++ b/apps/docs/src/client/+script.ts @@ -1,4 +1,4 @@ -import "@/style.css"; +import "@/client/tailwind.css"; import "drab/details/define"; import "drab/dialog/define"; import "drab/prefetch/define"; diff --git a/apps/docs/src/lib/docs/+client.ts b/apps/docs/src/client/docs/+script.ts similarity index 99% rename from apps/docs/src/lib/docs/+client.ts rename to apps/docs/src/client/docs/+script.ts index 0f7a6e0..f6cf62e 100644 --- a/apps/docs/src/lib/docs/+client.ts +++ b/apps/docs/src/client/docs/+script.ts @@ -16,7 +16,6 @@ headings.forEach((heading) => { } else { link.classList.add("text-muted-foreground"); const last = tableOfContents.querySelector(".level-2:last-child"); - console.log(last); let nestedUl = last?.querySelector("ul"); if (!nestedUl) { diff --git a/apps/docs/src/style.css b/apps/docs/src/client/tailwind.css similarity index 100% rename from apps/docs/src/style.css rename to apps/docs/src/client/tailwind.css diff --git a/apps/docs/src/content/_preview.md b/apps/docs/src/content/_preview.md deleted file mode 100644 index 39e11fa..0000000 --- a/apps/docs/src/content/_preview.md +++ /dev/null @@ -1,33 +0,0 @@ -```ts {7} -import { Hono } from "hono"; - -const app = new Hono(); - -app.get("/", (c) => - c.html( - c.var.page(), // your Vite app - ), -); - -export default app; -``` - -
-
-
-
- -

- Construct Web Applications with - - Vite - - and - - Hono - -

- -domco turns your [Vite](https://vitejs.dev) project into a [Hono](https://hono.dev) application. You can take advantage of Vite's build pipeline, plugins, and HMR in a full-stack context using Hono as your web server. - -This project is inspired by other projects including [HonoX](https://github.com/honojs/honox), [Vinxi](https://vinxi.vercel.app/), and [SvelteKit](https://kit.svelte.dev). domco aims to be a minimal layer that connects Vite and Hono, it has no other dependencies. diff --git a/apps/docs/src/content/migrate.md b/apps/docs/src/content/migrate.md deleted file mode 100644 index d2c103c..0000000 --- a/apps/docs/src/content/migrate.md +++ /dev/null @@ -1,68 +0,0 @@ -# Migrate - -This section will show you how to migrate an existing Vite single page application to become a Hono application. It will use the React template created when running `npm create vite`. - -## Client - -- Run `npm i -D hono domco` in your terminal to install hono and domco as dependencies. -- Add `domco` to your `plugins` array in your `vite.config`. - - - -```ts {3,9} -// vite.config.ts -import react from "@vitejs/plugin-react"; -import { domco } from "domco"; -import { defineConfig } from "vite"; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - domco(), // add to plugins - react(), - ], -}); -``` - -- Move `index.html` into `src/` and rename it to `+page.html`. -- Change the `src` attribute of the `script` tag linking to `/src/main.tsx` to `/main.tsx` (alternatively remove the tag entirely and rename `main.tsx` to `+client.tsx`). -- Run `npm run dev` to serve your application. - -You have successfully migrated your app to domco. - -## Server - -To add an API route, follow these instructions. - -- Add types to `/src/vite.env.d.ts`. - -```ts {3-8} -// /src/vite.env.d.ts -/// -import type { DomcoContextVariableMap } from "domco"; -import "hono"; - -declare module "hono" { - interface ContextVariableMap extends DomcoContextVariableMap {} -} -``` - -- Create a `+server.ts` file anywhere within `src/`. In this example, we can make `/src/+server.ts` and serve the app from an endpoint. - -```ts -// /src/+server.ts -import { Hono } from "hono"; - -const app = new Hono(); - -app.get("/", (c) => c.html(c.var.page())); - -app.get("/api", (c) => c.html("hello world")); - -export default app; -``` - -- `/` is now an API route serving your React SPA application. -- Navigate to `/api` to see the `"hello world"` response from your API. - -You now have a full-stack application. diff --git a/apps/docs/src/content/tutorial.md b/apps/docs/src/content/tutorial.md deleted file mode 100644 index d133223..0000000 --- a/apps/docs/src/content/tutorial.md +++ /dev/null @@ -1,197 +0,0 @@ -# Tutorial - -The following documentation covers the basics of creating a site and all of the features domco provides in addition to Vite and Hono. See the [Vite](https://vitejs.dev/) or [Hono](https://hono.dev) documentation for more information and configuration options. - -## Create a new project - -To get started, you'll need to have [Node](https://nodejs.org), [Bun](https://bun.sh/), or [Deno](https://deno.com) or installed on your computer. Then run the `create-domco` script to create a new project. If you already have an existing Vite project check out the [migration instructions](/migrate). - -### Node - -```bash -npm create domco@latest -``` - -### Bun - -```bash -bun create domco@latest -``` - -### Deno - -```bash -deno run -A npm:create-domco@latest -``` - -## +page - -Your project is located in `src/`---this serves as the root directory of your project. `src/+page.html` can be accessed at `https://example.com`, while `src/nested/+page.html` can be accessed at `https://example.com/nested`. - -To create another page, add another `+page.html` file in another directory within `src/`. - -domco configures Vite to process each `+page.html` as a separate entry point automatically. Everything linked in these pages will be bundled and included in the output upon running `vite build`. - -```txt {5} -. -└── src/ - ├── +page.html - └── nested/ - └── +page.html -``` - -Now you can navigate to `/nested` with an anchor tag. - -```html -Nested -``` - -## +client - -Each `+client.(js,ts,jsx,tsx)` file within `src/` will be processed as an entry point by Vite. Client-side scripts can be used in pages or on the server _without_ a page. - -domco will automatically inject a tag for any `+client` file that is located next to a `+page.html` into the page so you don't need to link it. - -```txt {3} -. -└── src/ - ├── +client.ts - └── +page.html -``` - -If you would like to like to add another script, you can import it into `+client`, or add a script tag to your HTML page. - -```html - - -``` - -Now that this script is included in an entry point `+page.html`, the script, and anything that is imported, will be bundled and included in the final build. - -## +server - -Turn any path in `src` into a Hono app by adding a `+server.(js,ts,jsx,tsx)` file. - -```txt {4} -. -└── src/ - ├── +page.html - └── +server.ts -``` - -You have now converted this route path from a static page into a Hono application based at that path. - -```ts -// src/+server.ts -import { Hono } from "hono"; - -const app = new Hono(); - -app.get("/", (c) => c.html("hello world")); - -// you can also create another route from here -// this route can be accessed at https://example.com/another-route -app.get("/another-route", (c) => c.html("another route")); - -export default app; -``` - -### Page context - -domco sets a `page` context variable for you that will return the transformed HTML of any `+page.html` requested. - -```ts -app.get("/", (c) => { - // ./+page.html - const page = c.var.page(); - - // src/nested/+page.html - const anotherPage = c.var.page("/nested"); - - return c.html(page); -}; -``` - -### Client context - -You can also easily get the tags for any `+client` file on the server as well. These script tags (including all imports) can be accessed using the `client` context variable within your Hono application. They can be included in an HTML string, or inside of JSX. - -```ts {3,9} -app.get("/", (c) => { - // gets `./+client.(js,ts,jsx,tsx)` - const tags = c.var.client(); - - // gets `src/route/path/+client.(js,ts,jsx,tsx)` - const differentTags = c.var.client("/route/path"); - - return c.html(html` - ${tags} -

Partial with client side script

- `); -}); -``` - -domco reads the manifest generated by the client build and includes the hashed version of these file names in production. - -### Server context - -domco also sets a `server` context variable to make it easier to request local routes (it's just `fetch` with the `origin` set.) - -```ts -// dev: fetch("http://localhost:5173/route/path") -// prod: fetch("https://example.com/route/path") -const res = await c.var.server("/route/path"); -``` - -### Prerender - -Export a `prerender` variable to prerender pages. - -```ts -// src/posts/+server.ts -import type { Prerender } from "domco"; - -// prerender current route -export const prerender: Prerender = true; - -// prerender multiple paths relative to the current route. -export const prerender: Prerender = ["/", "/post-1", "/post-2"]; -``` - -### Route ordering - -Routes are sorted and applied from most specific to least. - -``` -. -└── src/ - ├── +server.ts - 3rd - └── nested/ - ├── +server.ts - 2nd - └── another/ - └── +server.ts - 1st -``` - -So if you write a handler within `src/nested/+server.ts` to handle requests for `"/"`, and also one inside `src/+server.ts` to handle `"/nested"`, the handler within `src/nested/+server.ts` will be executed. - -### +setup - -You can also create a `src/+setup.(js,ts,jsx,tsx)` file, this app will be added to your application before other routes. This is an escape hatch, for example if you need some middleware to apply to all routes in your application and you don't want to import it into each `+server` module. - -## Styles - -Styles can be linked to an HTML page using the `link` tag within the `head` tag, or by importing a stylesheet into a client side script. - -Notice that the `href` for these links are relative to the root directory---this tag will link `src/style.css`. - -```html - - - - -``` - -```js -// +client.js -import "/style.css"; -``` diff --git a/apps/docs/src/global.d.ts b/apps/docs/src/env.d.ts similarity index 72% rename from apps/docs/src/global.d.ts rename to apps/docs/src/env.d.ts index f875d6b..9d0379d 100644 --- a/apps/docs/src/global.d.ts +++ b/apps/docs/src/env.d.ts @@ -1,10 +1,9 @@ /// -import type { DomcoContextVariableMap } from "domco"; +/// import "hono"; import type { HtmlEscapedString } from "hono/utils/html"; declare module "hono" { - interface ContextVariableMap extends DomcoContextVariableMap {} interface ContextRenderer { ( props: { title: string; client?: HtmlEscapedString[] }, diff --git a/apps/docs/src/generated/globals.md b/apps/docs/src/generated/globals.md deleted file mode 100644 index e9d492b..0000000 --- a/apps/docs/src/generated/globals.md +++ /dev/null @@ -1,564 +0,0 @@ -## Type Aliases - - - -### Adapter - -> **Adapter**: `object` - -#### Type declaration - - - -##### devMiddleware? - -> `optional` **devMiddleware**: [`CreateAppOptions`](globals.md#createappoptions)\[`"middleware"`\] - -Middleware to apply in `dev` mode. -For production middleware, export it from the adapter module, -and then import into the entry point. - - - -##### entry - -> **entry**: [`AdapterEntry`](globals.md#adapterentry) - -Entry point for the server application. - - - -##### message - -> **message**: `string` - -Message to log when the build is complete. - - - -##### name - -> **name**: `string` - -The name of the adapter. - - - -##### noExternal? - -> `optional` **noExternal**: `SSROptions`\[`"noExternal"`\] - -Passed into Vite `config.ssr.noExternal`. - - - -##### previewMiddleware? - -> `optional` **previewMiddleware**: [`CreateAppOptions`](globals.md#createappoptions)\[`"middleware"`\] - -Middleware to apply in `preview` mode. -For production middleware, export it from the adapter module, -and then import into the entry point. - - - -##### run()? - -> `optional` **run**: () => `any` - -The script to run after Vite build is complete. - -###### Returns - -`any` - - - -##### target? - -> `optional` **target**: `SSRTarget` - -Passed into Vite `config.ssr.target`. - -#### Defined in - -[types/public/index.ts:44](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/types/public/index.ts#L44) - ---- - - - -### AdapterBuilder()\ - -> **AdapterBuilder**\<`AdapterOptions`\>: (`AdapterOptions`?) => `MaybePromise`\<[`Adapter`](globals.md#adapter)\> - -#### Type Parameters - -• **AdapterOptions** = `never` - -#### Parameters - -• **AdapterOptions?**: `AdapterOptions` - -#### Returns - -`MaybePromise`\<[`Adapter`](globals.md#adapter)\> - -#### Defined in - -[types/public/index.ts:78](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/types/public/index.ts#L78) - ---- - - - -### AdapterEntry() - -> **AdapterEntry**: (`AdapterEntryOptions`) => `object` - -#### Parameters - -• **AdapterEntryOptions** - -• **AdapterEntryOptions.appId**: `string` - -The app entrypoint to import `createApp` from. - -#### Returns - -`object` - -##### code - -> **code**: `string` - -Code for the entrypoint. - -##### id - -> **id**: `string` - -The name of the entrypoint without extension. - -###### Example - -```ts -"main"; -``` - -#### Defined in - -[types/public/index.ts:29](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/types/public/index.ts#L29) - ---- - - - -### Client() - -> **Client**: (`routePath`?) => `HtmlEscapedString` - -#### Parameters - -• **routePath?**: `string` - -#### Returns - -`HtmlEscapedString` - -#### Defined in - -[types/public/index.ts:141](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/types/public/index.ts#L141) - ---- - - - -### CreateAppMiddleware - -> **CreateAppMiddleware**: `object` - -#### Type declaration - - - -##### handler - -> **handler**: `MiddlewareHandler` - -The middleware to apply. - - - -##### path - -> **path**: `string` - -Path to apply the middleware to. - -#### Defined in - -[types/public/index.ts:7](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/types/public/index.ts#L7) - ---- - - - -### CreateAppOptions - -> **CreateAppOptions**: `object` - -#### Type declaration - - - -##### devServer? - -> `optional` **devServer**: `ViteDevServer` - -Only used in `dev` to call the server. - - - -##### honoOptions? - -> `optional` **honoOptions**: `HonoOptions`\<`Env`\> - -Passthrough options to the Hono app. - - - -##### middleware? - -> `optional` **middleware**: [`CreateAppMiddleware`](globals.md#createappmiddleware)[] - -Middleware to be applied before any routes. Useful for adapters that need to -inject middleware. - -#### Defined in - -[types/public/index.ts:15](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/types/public/index.ts#L15) - ---- - - - -### DomcoConfig - -> **DomcoConfig**: `object` - -domco Config - -Use if you want to create a separate object for your domco config. -Pass the config into the `domco` vite plugin. - -#### Type declaration - - - -##### adapter? - -> `optional` **adapter**: `ReturnType`\<[`AdapterBuilder`](globals.md#adapterbuilderadapteroptions)\> - -domco adapter. - -Defaults to `undefined` - creates a `app` build only. - -###### Default - -```ts -undefined; -``` - -###### Example - -```js -import { adapter } from `"domco/adapter/...";` -``` - -#### Example - -```ts -// vite.config.ts -import { domco, type DomcoConfig } from "domco"; -import { defineConfig } from "vite"; - -const config: DomcoConfig = { - // options... -}; - -export default defineConfig({ - plugins: [domco(config)], -}); -``` - -#### Defined in - -[types/public/index.ts:104](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/types/public/index.ts#L104) - ---- - - - -### DomcoContextVariableMap - -> **DomcoContextVariableMap**: `object` - -Extend Hono's variable map with domco's. - -[Hono reference](https://hono.dev/docs/api/context#contextvariablemap) - -#### Type declaration - - - -##### client - -> **client**: [`Client`](globals.md#client) - -The script tags for any `+client.(js,ts,jsx,tsx)` within `src` can be accessed through the `client` context variable. -This is useful if you are wanting to include a client side script but are not using a `page` file for your HTML, since -the names of these tags will be different after your client application has been built. - -For example, if you are using JSX to render the markup for your application you can utilize the `client` variable to include -client side scripts. - -Also, the link tags for any `css` imported into the script will be included. - -In development, the tags will link directly to the script. - -In production, domco will read `dist/client/.vite/manifest.json` and use the hashed file names generated from the build. - -###### Param - -If `routePath` is left `undefined`, the current route's `./+client.(js,ts,jsx,tsx)` will be returned. -Otherwise, you can use an alternative `routePath` to get a different route's tags. - -###### Example - -```tsx -// HTML example -// src/.../+server.(js,ts,jsx,tsx) - -import { Hono } from "hono"; -import { html } from "hono/html"; - -const app = new Hono(); - -app.get("/", (c) => { - // gets `./+client.(js,ts,jsx,tsx)` - const tags = c.var.client(); - - // gets `src/route/path/+client.(js,ts,jsx,tsx)` - const differentTags = c.var.client("/route/path") - - return c.html(html` - ${tags} -

Partial with client side script

- `); -}); - -export default app; - -// JSX example - -app.get("/", (c) => { - return c.html( - <> - {c.var.client()} -

Partial with client side script

- - `); -}); -``` - - - -##### page - -> **page**: [`Page`](globals.md#page-1) - -Any `+page.html` within `src` can be accessed through the `page` context variable. -This provides the HTML after processing by Vite. - -###### Param - -If `routePath` is left `undefined`, the current route's `./+page.html` will be returned. -Otherwise, you can use an alternative `routePath` to get a different page. - -###### Example - -```ts -// src/.../+server.(js,ts,jsx,tsx) -import { Hono } from "hono"; - -app.get("/", (c) => { - // gets `./+page.html` - const page = c.var.page(); - - // gets `src/route/path/+page.html` - const differentPage = c.var.page("/route/path"); - - return c.html(page); -}); - -export default app; -``` - - - -##### server - -> **server**: [`Server`](globals.md#server-1) - -[`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) with the origin set, pass in the local path instead of the entire `url`. - -###### Param - -the local path to request - -###### Param - -[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) - -###### Example - -```ts -// src/.../+server.(js,ts,jsx,tsx) -import { Hono } from "hono"; - -const app = new Hono(); - -app.get("/", (c) => { - // dev: fetch("http://localhost:5173/route/path") - // prod: fetch("https://example.com/route/path") - const res = await c.var.server("/route/path") - - // ... -}); - -export default app; -``` - -#### Example - -```ts -// src/global.d.ts -/// -import type { DomcoContextVariableMap } from "domco"; -import "hono"; - -declare module "hono" { - interface ContextVariableMap extends DomcoContextVariableMap {} -} -``` - -#### Defined in - -[types/public/index.ts:166](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/types/public/index.ts#L166) - ---- - - - -### Page() - -> **Page**: (`routePath`?) => `string` - -#### Parameters - -• **routePath?**: `string` - -#### Returns - -`string` - -#### Defined in - -[types/public/index.ts:139](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/types/public/index.ts#L139) - ---- - - - -### Prerender - -> **Prerender**: `string`[] \| `true` - -Paths to prerender relative to the current route. - -#### Example - -```ts -// src/posts/+server.ts -import type { Prerender } from "domco"; - -// prerender current route -export const prerender: Prerender = true; - -// prerender multiple paths relative to the current route. -export const prerender: Prerender = ["/", "/post-1", "/post-2"]; -``` - -#### Defined in - -[types/public/index.ts:137](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/types/public/index.ts#L137) - ---- - - - -### Server() - -> **Server**: (`pathname`, `init`?) => `MaybePromise`\<`Response`\> - -#### Parameters - -• **pathname**: `string` - -• **init?**: `RequestInit` - -#### Returns - -`MaybePromise`\<`Response`\> - -#### Defined in - -[types/public/index.ts:143](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/types/public/index.ts#L143) - -## Functions - - - -### domco() - -> **domco**(`domcoConfig`): `Promise`\<`Plugin`\<`any`\>[]\> - -Creates domco Vite plugin, add to your `plugins` array within your `vite.config` -to start using domco. - -#### Parameters - -• **domcoConfig**: [`DomcoConfig`](globals.md#domcoconfig) = `{}` - -#### Returns - -`Promise`\<`Plugin`\<`any`\>[]\> - -The domco Vite plugin. - -#### Example - -```ts -// vite.config.ts -import { domco } from "domco"; -import { defineConfig } from "vite"; - -export default defineConfig({ - plugins: [domco()], -}); -``` - -#### Defined in - -[plugin/index.ts:31](https://github.com/rossrobino/domco/blob/b818d1141c9ab3d2c6ee8351170a98e520e95923/packages/domco/src/plugin/index.ts#L31) diff --git a/apps/docs/src/+server.tsx b/apps/docs/src/server/+app.tsx similarity index 66% rename from apps/docs/src/+server.tsx rename to apps/docs/src/server/+app.tsx index b682c31..f4aeb01 100644 --- a/apps/docs/src/+server.tsx +++ b/apps/docs/src/server/+app.tsx @@ -1,9 +1,11 @@ -import { Edit } from "./components/Edit"; -import { Hero } from "@/components/Hero"; -import { Layout } from "@/components/Layout"; -import preview from "@/content/_preview.md?raw"; -import apiReference from "@/generated/globals.md?raw"; +import { Edit } from "@/server/components/Edit"; +import { Hero } from "@/server/components/Hero"; +import { Layout } from "@/server/components/Layout"; +import preview from "@/server/content/_preview.md?raw"; +import apiReference from "@/server/generated/globals.md?raw"; import { processMarkdown } from "@robino/md"; +import { tags as rootTags } from "client:script"; +import { tags as docTags } from "client:script/docs"; import type { Prerender } from "domco"; import { Hono } from "hono"; import { etag } from "hono/etag"; @@ -17,7 +19,7 @@ app.use(etag()); app.use(async (c, next) => { c.setRenderer(({ title, client }, content) => { - const tags = [c.var.client()]; + const tags = [raw(rootTags)]; if (client) { tags.push(...client); } @@ -30,9 +32,9 @@ app.use(async (c, next) => { await next(); }); -app.get("/", async (c) => { - const previewHtml = raw(processMarkdown({ md: preview }).html); +const previewHtml = raw(processMarkdown({ md: preview }).html); +app.get("/", async (c) => { return c.render( { title: "domco" }, <> @@ -47,7 +49,7 @@ app.get("/", async (c) => { ); }); -const content = import.meta.glob("/content/*.md", { +const content = import.meta.glob("/server/content/*.md", { query: "?raw", import: "default", eager: true, @@ -67,7 +69,7 @@ for (const [fileName, md] of Object.entries(content)) { return c.render( { title: slug.charAt(0).toUpperCase() + slug.slice(1), - client: [c.var.client("/lib/docs")], + client: [raw(docTags)], }, <>
{html}
@@ -78,13 +80,13 @@ for (const [fileName, md] of Object.entries(content)) { } } -app.get("/api-reference", async (c) => { - const apiReferenceHtml = raw( - processMarkdown({ md: apiReference.replaceAll("globals.md#", "#") }).html, - ); +const apiReferenceHtml = raw( + processMarkdown({ md: apiReference.replaceAll("globals.md#", "#") }).html, +); +app.get("/api-reference", async (c) => { return c.render( - { title: "API Reference", client: [c.var.client("/lib/docs")] }, + { title: "API Reference", client: [raw(docTags)] }, <>

API Reference

@@ -95,4 +97,4 @@ app.get("/api-reference", async (c) => { ); }); -export default app; +export const handler = app.fetch; diff --git a/apps/docs/src/components/Edit.tsx b/apps/docs/src/server/components/Edit.tsx similarity index 100% rename from apps/docs/src/components/Edit.tsx rename to apps/docs/src/server/components/Edit.tsx diff --git a/apps/docs/src/components/Hero.tsx b/apps/docs/src/server/components/Hero.tsx similarity index 100% rename from apps/docs/src/components/Hero.tsx rename to apps/docs/src/server/components/Hero.tsx diff --git a/apps/docs/src/components/Layout.tsx b/apps/docs/src/server/components/Layout.tsx similarity index 100% rename from apps/docs/src/components/Layout.tsx rename to apps/docs/src/server/components/Layout.tsx diff --git a/apps/docs/src/components/Nav.tsx b/apps/docs/src/server/components/Nav.tsx similarity index 96% rename from apps/docs/src/components/Nav.tsx rename to apps/docs/src/server/components/Nav.tsx index da8c8e4..a4e51fd 100644 --- a/apps/docs/src/components/Nav.tsx +++ b/apps/docs/src/server/components/Nav.tsx @@ -1,4 +1,12 @@ -import { BookSvg, EarthSvg, FuncSvg, HomeSvg, PlugSvg, XSvg } from "./svg"; +import { + BookSvg, + EarthSvg, + FuncSvg, + HomeSvg, + PenSvg, + PlugSvg, + XSvg, +} from "./svg"; import type { FC } from "hono/jsx"; export const Nav: FC = () => { @@ -99,6 +107,10 @@ export const InternalLinks: FC = () => { title: "Deploy", Icon: EarthSvg, }, + { + title: "Examples", + Icon: PenSvg, + }, { title: "API Reference", Icon: FuncSvg, diff --git a/apps/docs/src/components/svg.tsx b/apps/docs/src/server/components/svg.tsx similarity index 84% rename from apps/docs/src/components/svg.tsx rename to apps/docs/src/server/components/svg.tsx index ad40008..ff46c15 100644 --- a/apps/docs/src/components/svg.tsx +++ b/apps/docs/src/server/components/svg.tsx @@ -120,3 +120,22 @@ export const HomeSvg: FC = () => { ); }; + +export const PenSvg: FC = () => { + return ( + + + + + ); +}; diff --git a/apps/docs/src/server/content/_preview.md b/apps/docs/src/server/content/_preview.md new file mode 100644 index 0000000..581fa4e --- /dev/null +++ b/apps/docs/src/server/content/_preview.md @@ -0,0 +1,34 @@ +```ts {1,5} +import { html } from "client:page"; + +export const handler = async (req: Request) => { + return new Response( + html, // Your Vite app. + { + headers: { "Content-Type": "text/html" }, + }, + ); +}; +``` + +## Create Full-Stack Applications with Vite + +domco turns your [Vite](https://vitejs.dev) project into a full-stack application. You can leverage Vite's build pipeline, plugins, and HMR within a full-stack context using Hono as your web server. + +This project draws inspiration from [HonoX](https://github.com/honojs/honox), [Vinxi](https://vinxi.vercel.app/), and [SvelteKit](https://kit.svelte.dev). + +### Build with Web APIs + +Server-side JavaScript runtimes are standardizing on the Web [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) APIs. This makes it possible to write your app once and deploy it to a variety of different platforms using [adapters](/deploy#adapters). + +### Add a Framework, or don't + +One of the main goals of domco is to be able to create full-stack JavaScript applications using vanilla JavaScript. Without domco, it's challenging to achieve the same developer experience as other frameworks that are based around a UI library. By default, domco only bundles only the code you write, making it efficient and straightforward. + +If you need a front-end framework, you can use any Vite plugin as you would in a traditional Vite application. + +On the server, domco is compatible with any server-side JavaScript framework that provides a web request handler taking a `Request` argument and returning a `Response`. Check out the [examples](/examples) to see how to use popular server frameworks with domco. + +### Minimal Dependencies + +domco is lightweight, relying solely on Vite as its dependency. This results in quick installation times, fast build and development processes, and a reduced risk of supply chain attacks. diff --git a/apps/docs/src/content/deploy.md b/apps/docs/src/server/content/deploy.md similarity index 75% rename from apps/docs/src/content/deploy.md rename to apps/docs/src/server/content/deploy.md index 12df022..0256d08 100644 --- a/apps/docs/src/content/deploy.md +++ b/apps/docs/src/server/content/deploy.md @@ -17,24 +17,23 @@ By default domco will generate a `app.js` module and static assets for your appl ## Example -If you are not using an [adapter](#adapters), you can import `createApp` from the `app.js` module and configure your app to use in one of [Hono's supported environments](https://hono.dev/docs/getting-started/basic). +If you are not using an [adapter](#adapters), you can import `handler` from the `app.js` module and configure your app to use in another environment. -The `client/` directory holds client assets. JS and CSS assets with hashed file names will be output to `dist/client/_immutable/`, you can serve this path with immutable cache headers. Other assets are processed and included in `dist/client/` directly. +The `dist/client/` directory holds client assets. JS and CSS assets with hashed file names will be output to `dist/client/_immutable/`, you can serve this path with immutable cache headers. Other assets like HTML files are processed and included in `dist/client/` directly. -Here's an example of how to serve your app using the result of your build with [`@hono/node-server`](https://github.com/honojs/node-server). +Here's an example of how to serve your app using the result of your build using `node:http`. ```ts // server.js // import from build output -import { createApp } from "./dist/server/app.js"; -import { serve } from "@hono/node-server"; -import { serveStatic } from "@hono/node-server/serve-static"; +import { handler } from "./dist/server/app.js"; +// converts web to node +import { nodeListener } from "domco/listener"; +import { createServer } from "node:http"; -const app = createApp({ - middleware: [{ path: "/*", handler: serveStatic({ root: "./dist/client" }) }], -}); +const server = createServer(nodeListener(handler)); -serve(app); +server.listen(3000); ``` Run this module to start your server. @@ -82,7 +81,7 @@ The [Deno](https://deno.com) adapter outputs your app to run on [Deno Deploy](ht - Function runs on Deno. - Static files are served with [`@std/http/file-server`](https://jsr.io/@std/http). -![A screenshot of the Deno Deploy Project Configuration UI. Set the Framework Preset field to "None", set the build command to "deno run -A npm:vite build", and the entrypoint to "dist/server/main.js".](/_vercel/image?url=/images/deno/build-settings.png&w=1280&q=100) +![A screenshot of the Deno Deploy Project Configuration UI. Set the Framework Preset field to "None", set the build command to "deno run -A npm:vite build", and the entry point to "dist/server/main.js".](/_vercel/image?url=/images/deno/build-settings.png&w=1280&q=100) ### Vercel diff --git a/apps/docs/src/server/content/examples.md b/apps/docs/src/server/content/examples.md new file mode 100644 index 0000000..362b166 --- /dev/null +++ b/apps/docs/src/server/content/examples.md @@ -0,0 +1,37 @@ +# Examples + +## Server Frameworks + +Here are some examples of how to use a few popular server-side frameworks with domco. + +### Hono + +[Hono](https://hono.dev/) is a fast, lightweight server framework built on Web Standards with support for any JavaScript runtime. + +```ts +// src/server/+app.ts +import { html } from "client:page"; +import { Hono } from "hono"; + +const app = new Hono(); + +app.get("/", (c) => c.html(html)); + +export const handler = app.fetch; +``` + +### h3 + +[h3](https://h3.unjs.io/) is a server framework built for high performance and portability running in any JavaScript runtime. + +```ts +// src/server/+app.ts +import { html } from "client:page"; +import { createApp, eventHandler, toWebHandler } from "h3"; + +const app = createApp(); + +app.use(eventHandler(() => html)); + +export const handler = toWebHandler(app); +``` diff --git a/apps/docs/src/server/content/migrate.md b/apps/docs/src/server/content/migrate.md new file mode 100644 index 0000000..f1f634e --- /dev/null +++ b/apps/docs/src/server/content/migrate.md @@ -0,0 +1,53 @@ +# Migrate + +This section will show you how to migrate an existing Vite single page application to become a Hono application. It will use the React template created when running `npm create vite`. + +## Client + +- Run `npm i -D domco` in your terminal to install domco as a dependency. +- Add `domco` to your `plugins` array in your `vite.config`. + + + +```ts {3,9} +// vite.config.ts +import react from "@vitejs/plugin-react"; +import { domco } from "domco"; +import { defineConfig } from "vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + domco(), // add to plugins + react(), + ], +}); +``` + +- Move `index.html` into `src/client/` and rename it to `+page.html`. +- Move `main.tsx` into `src/client/` and change the `src` attribute of the `script` tag in `+page.html` linking to `/src/main.tsx` to `/client/main.tsx`. +- Add types to `/src/vite.env.d.ts`. + +```ts {3} +// /src/vite.env.d.ts +/// +/// +``` + +- Create a `src/server/+app.ts` file and serve your page from the endpoint. + +```ts +// /src/server/+app.ts +import { html } from "client:page"; + +export const handler = async (req: Request) => { + return new Response( + html, // Your Vite app. + { + headers: { "Content-Type": "text/html" }, + }, + ); +}; +``` + +- `handler` is now an API route serving your React SPA application. diff --git a/apps/docs/src/server/content/tutorial.md b/apps/docs/src/server/content/tutorial.md new file mode 100644 index 0000000..00e63c0 --- /dev/null +++ b/apps/docs/src/server/content/tutorial.md @@ -0,0 +1,170 @@ +# Tutorial + +The following documentation covers the basics of creating a site and all of the features domco provides in addition to Vite and Hono. See the [Vite](https://vitejs.dev/) or [Hono](https://hono.dev) documentation for more information and configuration options. + +## Create a new project + +To get started, you'll need to have [Node](https://nodejs.org), [Bun](https://bun.sh/), or [Deno](https://deno.com) or installed on your computer. Then run the `create-domco` script to create a new project. If you already have an existing client-side Vite project check out the [migration instructions](/migrate). + +### Node + +```bash +npm create domco@latest +``` + +### Bun + +```bash +bun create domco@latest +``` + +### Deno + +```bash +deno run -A npm:create-domco@latest +``` + +## Entry Points + +domco identifies the entry points of your application by file name. These entry points are prefixed with `+` so they are easily identifiable. + +### +app + +The `app` entry point is located in within `src/server/`, this is the server entry point for your application. + +```txt {4} +. +└── src/ + └── server/ + └── +app.(js,ts,jsx,tsx) +``` + +`+app` modules export a `handler` function that takes in a `Request`, and returns a `Response`. + +```ts +// src/server/+app.ts +export const handler = async (req: Request) => { + return new Response("Hello world"); +}; +``` + +From here, it's up to you. For example, you could route different requests to different responses, based on the `req.url`. + +```ts +// src/server/+app.ts +export const handler = async (req: Request) => { + const { pathname } = new URL(req.url); + + if (pathname === "/") { + return new Response("Hello"); + } else if (pathname === "/world") { + return new Response("World"); + } + + return new Response("Not found", { status: 404 }); +}; +``` + +Or add a framework like [Hono](/examples#hono) to do your routing and more! + +### +page + +To create a page, add `+page.html` file in a directory within `src/client/`. + +domco configures Vite to process each `+page.html` as a separate entry point automatically. Everything linked in these pages will be bundled and included in the output upon running `vite build`. You can serve the transformed contents of a page via the [`client:page`](#client%3Apage) virtual module. + +```txt {4} +. +└── src/ + ├── client/ + │ └── +page.html + └── server/ + └── +app.ts +``` + +### +script + +Each `+script.(js,ts,jsx,tsx)` file within `src/client/` will be processed as an entry point by Vite. Client-side scripts can be used in pages via a `script` tag, or on the server _without_ a page by using the [`client:script`](#client%3Ascript) virtual module. + +```txt {4} +. +└── src/ + ├── client/ + │ └── +script.ts + └── server/ + └── +app.ts +``` + +## Virtual Modules + +domco provides a doorway to the client via virtual modules. These allow you to not have to worry about getting the hashed filenames for client assets after the build is complete. You can easily serve a `+page` or include the tags for a `+script` in a response. + +### client:page + +You can import the transformed HTML of any `+page.html` from this module or one of it's sub-paths. + +```ts {2,8} +// returns transformed content of `src/client/+page.html` +import { html } from "client:page"; +// `src/client/other/+page.html` +import { html as otherHtml } from "client:page/other"; + +export const handler = async (req: Request) => { + return new Response( + html, // Your Vite app. + { + headers: { "Content-Type": "text/html" }, + }, + ); +}; +``` + +### client:script + +You can also easily get the tags for any `+script` file on the server as well. These script tags (including all imports) can be accessed via the `client:script` virtual module. They can be included in an HTML string, or inside of JSX. + +```ts {2,13} +// returns transformed content of `src/client/+script.ts` +import { tags } from "client:script"; +// `src/client/other/+script.ts` +import { tags as otherTags } from "client:script/other"; + +export const handler = async (req: Request) => { + return new Response( + ` + + + + + ${tags} + Document + + ... + `, + { + headers: { "Content-Type": "text/html" }, + }, + ); +}; +``` + +In development, domco links the scripts to the source. In production, domco reads the manifest generated by the client build and includes the hashed version of these file names and their imports in production. + +## Prerender + +Export a `prerender` variable to prerender routes that respond with HTML. + +```ts +// src/server/+app.ts +export const prerender = ["/", "/post-1", "/post-2"]; +``` + +After the Vite build is complete, domco will import your production app and request the routes provided. The responses will be written into `dist/client/(prerender-path)/index.html` files upon build. + +If you are using an [adapter](/deploy#adapters), these static files will be served in front of your request handler. So when an `index.html` file is found for the route, it is served directly without hitting your handler. + +## That's It! + +domco has a minimal API surface area and tries to get out of your way during development. + +Next, learn how to [deploy](/deploy) your application. diff --git a/apps/docs/src/generated/README.md b/apps/docs/src/server/generated/README.md similarity index 100% rename from apps/docs/src/generated/README.md rename to apps/docs/src/server/generated/README.md diff --git a/apps/docs/src/server/generated/globals.md b/apps/docs/src/server/generated/globals.md new file mode 100644 index 0000000..7e82c61 --- /dev/null +++ b/apps/docs/src/server/generated/globals.md @@ -0,0 +1,344 @@ +## Type Aliases + + + +### Adapter + +> **Adapter**: `object` + +A domco adapter that configures the build to a target production environment. + +#### Type declaration + + + +##### devMiddleware? + +> `optional` **devMiddleware**: [`AdapterMiddleware`](globals.md#adaptermiddleware)[] + +Middleware to apply in `dev` mode. + + + +##### entry + +> **entry**: [`AdapterEntry`](globals.md#adapterentry) + +Entry point for the server application. + + + +##### message + +> **message**: `string` + +Message to log when the build is complete. + + + +##### name + +> **name**: `string` + +The name of the adapter. + + + +##### noExternal? + +> `optional` **noExternal**: `SSROptions`\[`"noExternal"`\] + +Passed into Vite `config.ssr.noExternal`. + + + +##### previewMiddleware? + +> `optional` **previewMiddleware**: [`AdapterMiddleware`](globals.md#adaptermiddleware)[] + +Middleware to apply in `preview` mode. + + + +##### run()? + +> `optional` **run**: () => `any` + +The script to run after Vite build is complete. + +###### Returns + +`any` + + + +##### target? + +> `optional` **target**: `SSRTarget` + +Passed into Vite `config.ssr.target`. + +#### Defined in + +[types/index.ts:58](https://github.com/rossrobino/domco/blob/992b91cd321929967ee8f34c5da10c9c02ff5567/packages/domco/src/types/index.ts#L58) + +--- + + + +### AdapterBuilder()\ + +> **AdapterBuilder**\<`AdapterOptions`\>: (`AdapterOptions`?) => `MaybePromise`\<[`Adapter`](globals.md#adapter)\> + +Use this type to create your own adapter. +Pass any options for the adapter in as a generic. + +#### Type Parameters + +• **AdapterOptions** = `never` + +#### Parameters + +• **AdapterOptions?**: `AdapterOptions` + +#### Returns + +`MaybePromise`\<[`Adapter`](globals.md#adapter)\> + +#### Defined in + +[types/index.ts:88](https://github.com/rossrobino/domco/blob/992b91cd321929967ee8f34c5da10c9c02ff5567/packages/domco/src/types/index.ts#L88) + +--- + + + +### AdapterEntry() + +> **AdapterEntry**: (`AdapterEntryOptions`) => `object` + +A function that returns an additional entry point to include in the SSR build. + +#### Parameters + +• **AdapterEntryOptions** + +• **AdapterEntryOptions.appId**: `string` + +The app entry point to import `handler` from. + +#### Returns + +`object` + +##### code + +> **code**: `string` + +Code for the entry point. + +##### id + +> **id**: `string` + +The name of the entry point without extension. + +###### Example + +```ts +"main"; +``` + +#### Defined in + +[types/index.ts:42](https://github.com/rossrobino/domco/blob/992b91cd321929967ee8f34c5da10c9c02ff5567/packages/domco/src/types/index.ts#L42) + +--- + + + +### AdapterMiddleware + +> **AdapterMiddleware**: `Connect.NextHandleFunction` + +Middleware used in the Vite server for dev and preview. + +#### Defined in + +[types/index.ts:39](https://github.com/rossrobino/domco/blob/992b91cd321929967ee8f34c5da10c9c02ff5567/packages/domco/src/types/index.ts#L39) + +--- + + + +### AppModule + +> **AppModule**: `object` + +Exports from the SSR `app` entry point. + +#### Type declaration + + + +##### handler + +> **handler**: [`Handler`](globals.md#handler-1) + + + +##### prerender + +> **prerender**: [`Prerender`](globals.md#prerender-1) + +#### Defined in + +[types/index.ts:5](https://github.com/rossrobino/domco/blob/992b91cd321929967ee8f34c5da10c9c02ff5567/packages/domco/src/types/index.ts#L5) + +--- + + + +### DomcoConfig + +> **DomcoConfig**: `object` + +domco Config + +Use if you want to create a separate object for your domco config. +Pass the config into the `domco` vite plugin. + +#### Type declaration + + + +##### adapter? + +> `optional` **adapter**: `ReturnType`\<[`AdapterBuilder`](globals.md#adapterbuilderadapteroptions)\> + +domco adapter. + +Defaults to `undefined` - creates a `app` build only. + +###### Default + +```ts +undefined; +``` + +###### Example + +```js +import { adapter } from `"domco/adapter/...";` +``` + +#### Example + +```ts +// vite.config.ts +import { domco, type DomcoConfig } from "domco"; +import { defineConfig } from "vite"; + +const config: DomcoConfig = { + // options... +}; + +export default defineConfig({ + plugins: [domco(config)], +}); +``` + +#### Defined in + +[types/index.ts:114](https://github.com/rossrobino/domco/blob/992b91cd321929967ee8f34c5da10c9c02ff5567/packages/domco/src/types/index.ts#L114) + +--- + + + +### Handler() + +> **Handler**: (`req`) => `MaybePromise`\<`Response`\> + +Request handler, takes a web request and returns a web response. + +```ts +// src/server/+app.ts +import type { Handler } from "domco"; + +export const handler: Handler = async (req) => { + return new Response("Hello world"); +}; +``` + +#### Parameters + +• **req**: `Request` + +#### Returns + +`MaybePromise`\<`Response`\> + +#### Defined in + +[types/index.ts:22](https://github.com/rossrobino/domco/blob/992b91cd321929967ee8f34c5da10c9c02ff5567/packages/domco/src/types/index.ts#L22) + +--- + + + +### Prerender + +> **Prerender**: `string`[] + +Paths to prerender at build time. + +#### Example + +```ts +// src/server/+app.ts +import type { Prerender } from "domco"; + +export const prerender: Prerender = ["/", "/post-1", "/post-2"]; +``` + +#### Defined in + +[types/index.ts:36](https://github.com/rossrobino/domco/blob/992b91cd321929967ee8f34c5da10c9c02ff5567/packages/domco/src/types/index.ts#L36) + +## Functions + + + +### domco() + +> **domco**(`domcoConfig`): `Promise`\<`Plugin`\<`any`\>[]\> + +Creates domco Vite plugin, add to your `plugins` array within your `vite.config` +to start using domco. + +#### Parameters + +• **domcoConfig**: [`DomcoConfig`](globals.md#domcoconfig) = `{}` + +#### Returns + +`Promise`\<`Plugin`\<`any`\>[]\> + +The domco Vite plugin. + +#### Example + +```ts +// vite.config.ts +import { domco } from "domco"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [domco()], +}); +``` + +#### Defined in + +[plugin/index.ts:29](https://github.com/rossrobino/domco/blob/992b91cd321929967ee8f34c5da10c9c02ff5567/packages/domco/src/plugin/index.ts#L29) diff --git a/apps/tester/package.json b/apps/tester/package.json index bcfc82f..0381b3f 100644 --- a/apps/tester/package.json +++ b/apps/tester/package.json @@ -10,13 +10,12 @@ "preview": "vite preview" }, "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.1.2", - "@types/react": "^18.3.5", + "@types/react": "^18.3.7", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", "domco": "*", "react": "^18.3.1", "react-dom": "^18.3.1", - "svelte": "4.2.19" + "uico": "^0.3.1" } } diff --git a/apps/tester/public/public.html b/apps/tester/public/public.html new file mode 100644 index 0000000..a48cf0d --- /dev/null +++ b/apps/tester/public/public.html @@ -0,0 +1 @@ +public diff --git a/apps/tester/src/+server.ts b/apps/tester/src/+server.ts deleted file mode 100644 index 1625b19..0000000 --- a/apps/tester/src/+server.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { inject } from "@/mw"; -import { Injector } from "domco/injector"; -import { Hono } from "hono"; -import { validator } from "hono/validator"; - -export const app = new Hono(); - -app - .use( - "/", - inject({ - comment: [ - { text: "server", children: [{ tag: "p", children: "hello" }] }, - ], - }), - ) - .post( - "/", - validator("form", (value, c) => { - const { test } = value; - if (!test || typeof test !== "string") { - return c.html( - new Injector(c.var.page()) - .comment([{ text: "result", children: "Please enter some text" }]) - .title("Invalid").html, - ); - } - return { - test, - }; - }), - (c) => { - const { test } = c.req.valid("form"); - return c.html( - new Injector(c.var.page()) - .comment([{ text: "result", children: "Success: " + test }]) - .title("Success").html, - ); - }, - ) - .get("/", inject({ title: "Home", handler: true })); - -export default app; diff --git a/apps/tester/src/+setup.ts b/apps/tester/src/+setup.ts deleted file mode 100644 index 3fbe3c9..0000000 --- a/apps/tester/src/+setup.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Hono } from "hono"; -// import { etag } from "hono/etag"; -import { logger } from "hono/logger"; - -export const prerender = true; - -const setup = new Hono(); - -// if (import.meta.env.PROD) setup.use(etag()); - -if (import.meta.env.DEV) setup.use(logger()); - -export default setup; diff --git a/apps/tester/src/api/+server.ts b/apps/tester/src/api/+server.ts deleted file mode 100644 index 6dba864..0000000 --- a/apps/tester/src/api/+server.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Hono } from "hono"; - -const app = new Hono(); - -app.get("/", async (c) => { - const res = await c.var.server("/static-page"); - - return c.json({ staticHtml: await res.text() }); -}); - -export default app; diff --git a/apps/tester/src/client/+client.ts b/apps/tester/src/client/+client.ts deleted file mode 100644 index af3eba3..0000000 --- a/apps/tester/src/client/+client.ts +++ /dev/null @@ -1 +0,0 @@ -console.log("ONLY SCRIPT"); diff --git a/apps/tester/src/+page.html b/apps/tester/src/client/+page.html similarity index 52% rename from apps/tester/src/+page.html rename to apps/tester/src/client/+page.html index 1d3b430..ed0e512 100644 --- a/apps/tester/src/+page.html +++ b/apps/tester/src/client/+page.html @@ -4,38 +4,35 @@ - + + + domco tester - -
domco
+ +
+

home

+
-

Home

+

Tester

+

Routes

-

Example Output

- -

- -

- -

Post Method

+

Post

- + +
diff --git a/apps/tester/src/+client.ts b/apps/tester/src/client/+script.ts similarity index 72% rename from apps/tester/src/+client.ts rename to apps/tester/src/client/+script.ts index 4c32b58..36f9e39 100644 --- a/apps/tester/src/+client.ts +++ b/apps/tester/src/client/+script.ts @@ -1,8 +1,7 @@ -import "@/style.css"; import { version } from "domco/version"; console.log(version); const p = document.createElement("p"); -p.textContent = "client"; +p.textContent = "client js script"; document.body.append(p); diff --git a/apps/tester/src/react/+page.html b/apps/tester/src/client/react/+page.html similarity index 63% rename from apps/tester/src/react/+page.html rename to apps/tester/src/client/react/+page.html index 7a08bcf..a91a8be 100644 --- a/apps/tester/src/react/+page.html +++ b/apps/tester/src/client/react/+page.html @@ -3,9 +3,11 @@ + + React - +

React

__ROOT__
diff --git a/apps/tester/src/react/App.tsx b/apps/tester/src/client/react/App.tsx similarity index 65% rename from apps/tester/src/react/App.tsx rename to apps/tester/src/client/react/App.tsx index 85f8a49..9469789 100644 --- a/apps/tester/src/react/App.tsx +++ b/apps/tester/src/client/react/App.tsx @@ -4,7 +4,9 @@ export default function App() { const [count, setCount] = useState(0); return ( <> - +

+ +

Count: {count}

); diff --git a/apps/tester/src/react/+client.tsx b/apps/tester/src/client/react/react.tsx similarity index 100% rename from apps/tester/src/react/+client.tsx rename to apps/tester/src/client/react/react.tsx diff --git a/apps/tester/src/client/style.css b/apps/tester/src/client/style.css new file mode 100644 index 0000000..2bb3eae --- /dev/null +++ b/apps/tester/src/client/style.css @@ -0,0 +1,10 @@ +@import "uico"; + +body { + padding: 2rem; + font-family: system-ui, sans-serif; +} + +input { + margin-bottom: 1rem; +} diff --git a/apps/tester/src/dynamic/+page.html b/apps/tester/src/dynamic/+page.html deleted file mode 100644 index 04ccb07..0000000 --- a/apps/tester/src/dynamic/+page.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Dynamic - - -

Dynamic

- -

__PARAMS__

- - diff --git a/apps/tester/src/dynamic/+server.js b/apps/tester/src/dynamic/+server.js deleted file mode 100644 index c17382b..0000000 --- a/apps/tester/src/dynamic/+server.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Hono } from "hono"; - -const app = new Hono(); - -app.get("/:param", (c) => { - return c.html( - c.var - .page() - .replaceAll( - "__PARAMS__", - `time: ${Date.now()}\n${JSON.stringify(c.req.param())}`, - ), - ); -}); - -export default app; diff --git a/apps/tester/src/env.d.ts b/apps/tester/src/env.d.ts new file mode 100644 index 0000000..b1d9caa --- /dev/null +++ b/apps/tester/src/env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/apps/tester/src/global.d.ts b/apps/tester/src/global.d.ts deleted file mode 100644 index 65d5a21..0000000 --- a/apps/tester/src/global.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// -import type { DomcoContextVariableMap } from "domco"; -import "hono"; - -declare module "hono" { - interface ContextVariableMap extends DomcoContextVariableMap {} -} diff --git a/apps/tester/src/half-static/+page.html b/apps/tester/src/half-static/+page.html deleted file mode 100644 index 466dd5f..0000000 --- a/apps/tester/src/half-static/+page.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - Half Static - - -

Half Static

- -

__PARAMS__

- -

Rendered at __TIME__

- - diff --git a/apps/tester/src/half-static/+server.ts b/apps/tester/src/half-static/+server.ts deleted file mode 100644 index e5760d2..0000000 --- a/apps/tester/src/half-static/+server.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Prerender } from "domco"; -import { Hono } from "hono"; - -export const prerender: Prerender = ["/hola", "/hello"]; - -const app = new Hono(); - -app.get("/:param", async (c) => { - const param = c.req.param(); - return c.html( - c.var - .page() - .replace("__PARAMS__", JSON.stringify(param)) - .replace("__TIME__", new Date().toLocaleString()), - ); -}); - -export default app; diff --git a/apps/tester/src/htmx/+page.html b/apps/tester/src/htmx/+page.html deleted file mode 100644 index f8c26ea..0000000 --- a/apps/tester/src/htmx/+page.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - htmx - - - -

htmx

- -

- -

- - diff --git a/apps/tester/src/htmx/partials/+client.js b/apps/tester/src/htmx/partials/+client.js deleted file mode 100644 index 4693573..0000000 --- a/apps/tester/src/htmx/partials/+client.js +++ /dev/null @@ -1,3 +0,0 @@ -import "./non-entry-import"; - -console.log("partial"); diff --git a/apps/tester/src/htmx/partials/+server.tsx b/apps/tester/src/htmx/partials/+server.tsx deleted file mode 100644 index bf147a5..0000000 --- a/apps/tester/src/htmx/partials/+server.tsx +++ /dev/null @@ -1,15 +0,0 @@ -/** @jsxImportSource hono/jsx */ -import { Hono } from "hono"; - -const app = new Hono(); - -app.get("/rendered-at", async (c) => { - return c.html( -
- {c.var.client()} -

Server rendered at {new Date().toLocaleString()}

-
, - ); -}); - -export default app; diff --git a/apps/tester/src/htmx/partials/non-entry-import.ts b/apps/tester/src/htmx/partials/non-entry-import.ts deleted file mode 100644 index 4805ac9..0000000 --- a/apps/tester/src/htmx/partials/non-entry-import.ts +++ /dev/null @@ -1 +0,0 @@ -console.log("non entry import"); diff --git a/apps/tester/src/htmx/static-partial/+page.html b/apps/tester/src/htmx/static-partial/+page.html deleted file mode 100644 index 6c95f54..0000000 --- a/apps/tester/src/htmx/static-partial/+page.html +++ /dev/null @@ -1,2 +0,0 @@ -

static partial

- diff --git a/apps/tester/src/mw/index.ts b/apps/tester/src/mw/index.ts deleted file mode 100644 index ab4eb64..0000000 --- a/apps/tester/src/mw/index.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - Injector, - type CommentDescriptor, - type TagDescriptor, -} from "domco/injector"; -import { createMiddleware } from "hono/factory"; - -/** - * Hono middleware to inject tags into an HTML page. - * - * First looks for `options.html`, then uses `c.var.page()`. - * - * @param options Inject options. - * @returns Inject Hono middleware. - */ -export const inject = (options: { - /** - * Set or replace the contents of the `title` tag. - */ - title?: string; - - /** - * Inject tags into the head. - */ - head?: TagDescriptor[]; - - headPrepend?: TagDescriptor[]; - - /** - * Inject tags into the body. - */ - body?: TagDescriptor[]; - - bodyPrepend?: TagDescriptor[]; - - /** - * Replace comments with tags. - * - * Set `tag` equal to text within comment. - */ - comment?: CommentDescriptor[]; - - /** - * @default c.var.page - */ - html?: string; - - /** - * Should return the final HTML as the response. - * - * @default false - */ - handler?: boolean; -}) => { - return createMiddleware(async (c, next) => { - const { - title, - head, - headPrepend, - body, - bodyPrepend, - comment, - html, - handler, - } = options; - - const newPage = new Injector(html ?? c.var.page()) - .title(title) - .head(head) - .head(headPrepend, true) - .body(body) - .body(bodyPrepend, true) - .comment(comment) - .toString(); - - if (handler) { - return c.html(newPage); - } - - c.set("page", () => newPage); - - await next(); - }); -}; diff --git a/apps/tester/src/react/+server.tsx b/apps/tester/src/react/+server.tsx deleted file mode 100644 index 2b4bf2d..0000000 --- a/apps/tester/src/react/+server.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import App from "./App"; -import type { Prerender } from "domco"; -import { Hono } from "hono"; -import React from "react"; -import { renderToString } from "react-dom/server"; - -export const prerender: Prerender = true; - -const app = new Hono(); - -app.get("/", async (c) => { - return c.html( - c.var.page().replace( - "__ROOT__", - renderToString( - - - , - ), - ), - ); -}); - -export default app; diff --git a/apps/tester/src/server/+app.tsx b/apps/tester/src/server/+app.tsx new file mode 100644 index 0000000..1ba9c5b --- /dev/null +++ b/apps/tester/src/server/+app.tsx @@ -0,0 +1,60 @@ +import App from "@/client/react/App"; +import { html } from "client:page"; +import { html as reactHtml } from "client:page/react"; +import type { Handler, Prerender } from "domco"; +import { Injector } from "domco/injector"; +import { Hono } from "hono"; +import React from "react"; +import { renderToString } from "react-dom/server"; + +export const prerender: Prerender = ["/static-page", "/half-static/static"]; + +export const app = new Hono(); + +app.all("/", async (c) => { + if (c.req.method === "POST") { + const formData = await c.req.formData(); + + const userInput = formData.get("test"); + + if (typeof userInput === "string" && userInput.length) { + return c.html( + new Injector(html).comment([{ text: "result", children: "success" }]) + .html, + ); + } else { + return c.html( + new Injector(html).comment([{ text: "result", children: "invalid" }]) + .html, + ); + } + } + + return c.html(html + new Date().toUTCString()); +}); + +app.get("/static-page", (c) => + c.html(`

Static

` + new Date().toUTCString()), +); + +app.get("/dynamic", (c) => c.html(new Date().toUTCString())); + +app.get("/half-static/static", (c) => c.html(new Date().toUTCString())); +app.get("/half-static/dynamic", (c) => c.html(new Date().toUTCString())); + +app.get("/api", (c) => c.json({ hello: "world" })); + +app.get("/react", (c) => { + return c.html( + reactHtml.replace( + "__ROOT__", + renderToString( + + + , + ), + ), + ); +}); + +export const handler: Handler = app.fetch; diff --git a/apps/tester/src/static-page/+page.html b/apps/tester/src/static-page/+page.html deleted file mode 100644 index 83eafc4..0000000 --- a/apps/tester/src/static-page/+page.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Static Page - - -

Static Page Only

- -

Not prerendered with a server file.

- - diff --git a/apps/tester/src/static-prerender/+page.html b/apps/tester/src/static-prerender/+page.html deleted file mode 100644 index 8d1f567..0000000 --- a/apps/tester/src/static-prerender/+page.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - Static Prerender - - -

- - - -
- - diff --git a/apps/tester/src/static-prerender/+server.ts b/apps/tester/src/static-prerender/+server.ts deleted file mode 100644 index 97e9a6a..0000000 --- a/apps/tester/src/static-prerender/+server.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { processMarkdown } from "@robino/md"; -import type { Prerender } from "domco"; -import { Hono } from "hono"; -import { createMiddleware } from "hono/factory"; - -const content = import.meta.glob("./content/*.md", { - query: "?raw", - import: "default", - eager: true, -}); - -export const prerender: Prerender = ["/", "/post-1"]; - -const app = new Hono(); - -const ssr = createMiddleware<{ Variables: { ssr: string } }>( - async (c, next) => { - c.set( - "ssr", - c.var - .page() - .replace( - "", - `rendered at: ${new Date().toLocaleString()}`, - ), - ); - await next(); - }, -); - -app.get("/", ssr, async (c) => { - return c.html(c.var.ssr); -}); - -app.get("/:slug", ssr, async (c) => { - const slug = c.req.param("slug"); - const md = content[`./content/${slug}.md`]; - - if (typeof md === "string") { - return c.html( - c.var.page().replace("", processMarkdown({ md }).html), - ); - } - - return c.notFound(); -}); - -export default app; diff --git a/apps/tester/src/static-prerender/content/post-1.md b/apps/tester/src/static-prerender/content/post-1.md deleted file mode 100644 index e16c040..0000000 --- a/apps/tester/src/static-prerender/content/post-1.md +++ /dev/null @@ -1,3 +0,0 @@ -# post 1 - -content diff --git a/apps/tester/src/static-prerender/content/post-2.md b/apps/tester/src/static-prerender/content/post-2.md deleted file mode 100644 index 4896a8d..0000000 --- a/apps/tester/src/static-prerender/content/post-2.md +++ /dev/null @@ -1,3 +0,0 @@ -# post 2 - -content diff --git a/apps/tester/src/style.css b/apps/tester/src/style.css deleted file mode 100644 index b35f90a..0000000 --- a/apps/tester/src/style.css +++ /dev/null @@ -1,10 +0,0 @@ -body { - font-family: system-ui, sans-serif; -} - -a { - color: blue; - &:visited { - color: blueviolet; - } -} diff --git a/apps/tester/src/svelte/+client.ts b/apps/tester/src/svelte/+client.ts deleted file mode 100644 index 99842cb..0000000 --- a/apps/tester/src/svelte/+client.ts +++ /dev/null @@ -1,10 +0,0 @@ -// @ts-ignore - no type for SSR -import App from "./App.svelte"; -import "@/style.css"; - -const app = new App({ - target: document.getElementById("root")!, - hydrate: true, -}); - -export default app; diff --git a/apps/tester/src/svelte/+page.html b/apps/tester/src/svelte/+page.html deleted file mode 100644 index 398f2f7..0000000 --- a/apps/tester/src/svelte/+page.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - Svelte - - -
__ROOT__
- - diff --git a/apps/tester/src/svelte/+server.ts b/apps/tester/src/svelte/+server.ts deleted file mode 100644 index c6812f4..0000000 --- a/apps/tester/src/svelte/+server.ts +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-ignore - no type for SSR -import App from "./App.svelte"; -import { Hono } from "hono"; - -const app = new Hono(); - -app.get("/", async (c) => { - // @ts-ignore - no type for SSR - const result = App.render({ data: {} }); - return c.html(c.var.page().replace("__ROOT__", result.html)); -}); - -export default app; diff --git a/apps/tester/src/svelte/App.svelte b/apps/tester/src/svelte/App.svelte deleted file mode 100644 index ad8f458..0000000 --- a/apps/tester/src/svelte/App.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - -svelte - -

Svelte

- - - -

{count}

- - diff --git a/apps/tester/svelte.config.js b/apps/tester/svelte.config.js deleted file mode 100644 index 69d7904..0000000 --- a/apps/tester/svelte.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; - -export default { - // Consult https://svelte.dev/docs#compile-time-svelte-preprocess - // for more information about preprocessors - preprocess: vitePreprocess(), -}; diff --git a/apps/tester/tsconfig.json b/apps/tester/tsconfig.json index ef0d9a6..7109ee4 100644 --- a/apps/tester/tsconfig.json +++ b/apps/tester/tsconfig.json @@ -2,7 +2,6 @@ "extends": "@robino/tsconfig/bundler.json", "compilerOptions": { "jsx": "react-jsx", - "paths": { "@": ["./src"], "@/*": ["./src/*"] diff --git a/apps/tester/vite.config.ts b/apps/tester/vite.config.ts index ff16090..da12b8c 100644 --- a/apps/tester/vite.config.ts +++ b/apps/tester/vite.config.ts @@ -1,4 +1,3 @@ -import { svelte } from "@sveltejs/vite-plugin-svelte"; import react from "@vitejs/plugin-react"; import { domco } from "domco"; import { adapter } from "domco/adapter/vercel"; @@ -11,14 +10,8 @@ export default defineConfig({ config: { runtime: "nodejs20.x", }, - isr: { expiration: 60 }, }), }), react(), - svelte({ - compilerOptions: { - hydratable: true, - }, - }), ], }); diff --git a/package-lock.json b/package-lock.json index 4b73915..ff6a844 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,12 @@ "@robino/md": "^0.2.0", "@robino/prettier": "^0.1.1", "@robino/tsconfig": "^0.2.1", - "hono": "^4.5.11", + "hono": "^4.6.2", "prettier": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.6", - "turbo": "^2.1.1", - "typescript": "^5.5.4", - "vite": "^5.4.3" + "turbo": "^2.1.2", + "typescript": "^5.6.2", + "vite": "^5.4.6" } }, "apps/cloudflare": { @@ -36,7 +36,7 @@ "autoprefixer": "^10.4.20", "domco": "*", "drab": "^5.4.2", - "tailwindcss": "^3.4.10", + "tailwindcss": "^3.4.12", "uico": "^0.3.1" } }, @@ -44,14 +44,13 @@ "name": "domco-tester", "version": "0.0.0", "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.1.2", - "@types/react": "^18.3.5", + "@types/react": "^18.3.7", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", "domco": "*", "react": "^18.3.1", "react-dom": "^18.3.1", - "svelte": "4.2.19" + "uico": "^0.3.1" } }, "node_modules/@alloc/quick-lru": { @@ -1988,6 +1987,29 @@ "@types/hast": "^3.0.4" } }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.17.7.tgz", + "integrity": "sha512-wwSf7lKPsm+hiYQdX+1WfOXujtnUG6fnN4rCmExxa4vo+OTmvZ9B1eKauilvol/LHUPrQgW12G3gzem7pY5ckw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.17.7", + "@shikijs/vscode-textmate": "^9.2.2", + "oniguruma-to-js": "0.4.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.17.7.tgz", + "integrity": "sha512-pvSYGnVeEIconU28NEzBXqSQC/GILbuNbAHwMoSfdTBrobKAsV1vq2K4cAgiaW1TJceLV9QMGGh18hi7cCzbVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.17.7", + "@shikijs/vscode-textmate": "^9.2.2" + } + }, "node_modules/@shikijs/markdown-it": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@shikijs/markdown-it/-/markdown-it-1.16.1.tgz", @@ -2010,53 +2032,23 @@ "shiki": "1.16.1" } }, - "node_modules/@shikijs/vscode-textmate": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.2.0.tgz", - "integrity": "sha512-5FinaOp6Vdh/dl4/yaOTh0ZeKch+rYS8DUb38V3GMKYVkdqzxw53lViRKUYkVILRiVQT7dcPC7VvAKOR73zVtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.2.tgz", - "integrity": "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==", + "node_modules/@shikijs/types": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.17.7.tgz", + "integrity": "sha512-+qA4UyhWLH2q4EFd+0z4K7GpERDU+c+CN2XYD3sC+zjvAr5iuwD1nToXZMt1YODshjkEGEDV86G7j66bKjqDdg==", "dev": true, "license": "MIT", "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0", - "debug": "^4.3.4", - "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.10", - "svelte-hmr": "^0.16.0", - "vitefu": "^0.2.5" - }, - "engines": { - "node": "^18.0.0 || >=20" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.0" + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4" } }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz", - "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==", + "node_modules/@shikijs/vscode-textmate": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.2.2.tgz", + "integrity": "sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==", "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.0.0 || >=20" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.0" - } + "license": "MIT" }, "node_modules/@trivago/prettier-plugin-sort-imports": { "version": "4.3.0", @@ -2193,6 +2185,16 @@ "@types/mdurl": "^2" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", @@ -2219,9 +2221,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.5", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz", - "integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.7.tgz", + "integrity": "sha512-KUnDCJF5+AiZd8owLIeVHqmW9yM4sqmDVf2JRJiBMFkGvkoZ4/WyV2lL4zVsoinmRS/W3FeEdZLEWFRofnT2FQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2260,6 +2262,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@vitejs/plugin-react": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", @@ -2281,14 +2290,14 @@ } }, "node_modules/@vitest/expect": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", - "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.1.tgz", + "integrity": "sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.0.5", - "@vitest/utils": "2.0.5", + "@vitest/spy": "2.1.1", + "@vitest/utils": "2.1.1", "chai": "^5.1.1", "tinyrainbow": "^1.2.0" }, @@ -2296,10 +2305,38 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/mocker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.1.tgz", + "integrity": "sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "^2.1.0-beta.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.11" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/spy": "2.1.1", + "msw": "^2.3.5", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, "node_modules/@vitest/pretty-format": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", - "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.1.tgz", + "integrity": "sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2310,13 +2347,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz", - "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.1.tgz", + "integrity": "sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.0.5", + "@vitest/utils": "2.1.1", "pathe": "^1.1.2" }, "funding": { @@ -2324,14 +2361,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", - "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.1.tgz", + "integrity": "sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.5", - "magic-string": "^0.30.10", + "@vitest/pretty-format": "2.1.1", + "magic-string": "^0.30.11", "pathe": "^1.1.2" }, "funding": { @@ -2339,9 +2376,9 @@ } }, "node_modules/@vitest/spy": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", - "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.1.tgz", + "integrity": "sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==", "dev": true, "license": "MIT", "dependencies": { @@ -2352,14 +2389,13 @@ } }, "node_modules/@vitest/utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", - "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.1.tgz", + "integrity": "sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.5", - "estree-walker": "^3.0.3", + "@vitest/pretty-format": "2.1.1", "loupe": "^3.1.1", "tinyrainbow": "^1.2.0" }, @@ -2371,8 +2407,9 @@ "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2451,16 +2488,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2519,16 +2546,6 @@ "postcss": "^8.1.0" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2667,6 +2684,17 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", @@ -2699,6 +2727,28 @@ "node": ">=4" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -2770,20 +2820,6 @@ "node": ">=8" } }, - "node_modules/code-red": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", - "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1", - "acorn": "^8.10.0", - "estree-walker": "^3.0.3", - "periscopic": "^3.1.0" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2801,6 +2837,17 @@ "dev": true, "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2850,20 +2897,6 @@ "postcss": "^8.0.9" } }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2912,16 +2945,6 @@ "node": ">=6" } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2942,6 +2965,20 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -3145,30 +3182,6 @@ "@types/estree": "^1.0.0" } }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, "node_modules/extendable-error": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", @@ -3348,19 +3361,6 @@ "node": "*" } }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -3456,13 +3456,63 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", + "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hono": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.5.11.tgz", - "integrity": "sha512-62FcjLPtjAFwISVBUshryl+vbHOjg8rE4uIK/dxyR8GpLztunZpwFmfEvmJCUI7xoGh/Sr3CGCDPCmYxVw7wUQ==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.2.tgz", + "integrity": "sha512-v+39817TgAhetmHUEli8O0uHDmxp2Up3DnhS4oUZXOl5IQ9np9tYtldd42e5zgdLVS0wsOoXQNZ6mx+BGmEvCA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=16.9.0" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/human-id": { @@ -3472,16 +3522,6 @@ "dev": true, "license": "MIT" }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3577,29 +3617,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-subdir": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", @@ -3719,16 +3736,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -3756,13 +3763,6 @@ "uc.micro": "^2.0.0" } }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, - "license": "MIT" - }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -3869,12 +3869,27 @@ "markdown-it": "*" } }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", "dev": true, - "license": "CC0-1.0" + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, "node_modules/mdurl": { "version": "2.0.0", @@ -3883,13 +3898,6 @@ "dev": true, "license": "MIT" }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3900,6 +3908,100 @@ "node": ">= 8" } }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -3914,19 +4016,6 @@ "node": ">=8.6" } }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -4027,35 +4116,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4076,20 +4136,17 @@ "node": ">= 6" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/oniguruma-to-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz", + "integrity": "sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" + "regex": "^4.3.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/antfu" } }, "node_modules/os-tmpdir": { @@ -4263,18 +4320,6 @@ "node": ">= 14.16" } }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -4629,6 +4674,17 @@ } } }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -4784,6 +4840,13 @@ "dev": true, "license": "MIT" }, + "node_modules/regex": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.2.tgz", + "integrity": "sha512-kK/AA3A9K6q2js89+VMymcboLOlF5lZRCYJv3gzszXFHBr6kO6qLGzbm+UIugBEV8SMMKCTR59txoY6ctRHYVw==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -5022,6 +5085,17 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spawndamnit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-2.0.0.tgz", @@ -5191,6 +5265,21 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -5241,19 +5330,6 @@ "node": ">=4" } }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -5303,49 +5379,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svelte": { - "version": "4.2.19", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz", - "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.1", - "@jridgewell/sourcemap-codec": "^1.4.15", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/estree": "^1.0.1", - "acorn": "^8.9.0", - "aria-query": "^5.3.0", - "axobject-query": "^4.0.0", - "code-red": "^1.0.3", - "css-tree": "^2.3.1", - "estree-walker": "^3.0.3", - "is-reference": "^3.0.1", - "locate-character": "^3.0.0", - "magic-string": "^0.30.4", - "periscopic": "^3.1.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/svelte-hmr": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz", - "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^12.20 || ^14.13.1 || >= 16" - }, - "peerDependencies": { - "svelte": "^3.19.0 || ^4.0.0" - } - }, "node_modules/tailwindcss": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz", - "integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz", + "integrity": "sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==", "dev": true, "license": "MIT", "dependencies": { @@ -5465,6 +5502,13 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", + "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinypool": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", @@ -5486,9 +5530,9 @@ } }, "node_modules/tinyspy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", - "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "license": "MIT", "engines": { @@ -5531,6 +5575,17 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -5539,27 +5594,27 @@ "license": "Apache-2.0" }, "node_modules/turbo": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.1.1.tgz", - "integrity": "sha512-u9gUDkmR9dFS8b5kAYqIETK4OnzsS4l2ragJ0+soSMHh6VEeNHjTfSjk1tKxCqLyziCrPogadxP680J+v6yGHw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.1.2.tgz", + "integrity": "sha512-Jb0rbU4iHEVQ18An/YfakdIv9rKnd3zUfSE117EngrfWXFHo3RndVH96US3GsT8VHpwTncPePDBT2t06PaFLrw==", "dev": true, "license": "MIT", "bin": { "turbo": "bin/turbo" }, "optionalDependencies": { - "turbo-darwin-64": "2.1.1", - "turbo-darwin-arm64": "2.1.1", - "turbo-linux-64": "2.1.1", - "turbo-linux-arm64": "2.1.1", - "turbo-windows-64": "2.1.1", - "turbo-windows-arm64": "2.1.1" + "turbo-darwin-64": "2.1.2", + "turbo-darwin-arm64": "2.1.2", + "turbo-linux-64": "2.1.2", + "turbo-linux-arm64": "2.1.2", + "turbo-windows-64": "2.1.2", + "turbo-windows-arm64": "2.1.2" } }, "node_modules/turbo-darwin-64": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.1.1.tgz", - "integrity": "sha512-aYNuJpZlCoi0Htd79fl/2DywpewGKijdXeOfg9KzNuPVKzSMYlAXuAlNGh0MKjiOcyqxQGL7Mq9LFhwA0VpDpQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.1.2.tgz", + "integrity": "sha512-3TEBxHWh99h2yIzkuIigMEOXt/ItYQp0aPiJjPd1xN4oDcsKK5AxiFKPH9pdtfIBzYsY59kQhZiFj0ELnSP7Bw==", "cpu": [ "x64" ], @@ -5571,9 +5626,9 @@ ] }, "node_modules/turbo-darwin-arm64": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.1.1.tgz", - "integrity": "sha512-tifJKD8yHY48rHXPMcM8o1jI/Jk2KCaXiNjTKvvy9Zsim61BZksNVLelIbrRoCGwAN6PUBZO2lGU5iL/TQJ5Pw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.1.2.tgz", + "integrity": "sha512-he0miWNq2WxJzsH82jS2Z4MXpnkzn9SH8a79iPXiJkq25QREImucscM4RPasXm8wARp91pyysJMq6aasD45CeA==", "cpu": [ "arm64" ], @@ -5585,9 +5640,9 @@ ] }, "node_modules/turbo-linux-64": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.1.1.tgz", - "integrity": "sha512-Js6d/bSQe9DuV9c7ITXYpsU/ADzFHABdz1UIHa7Oqjj9VOEbFeA9WpAn0c+mdJrVD+IXJFbbDZUjN7VYssmtcg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.1.2.tgz", + "integrity": "sha512-fKUBcc0rK8Vdqv5a/E3CSpMBLG1bzwv+Q0Q83F8fG2ZfNCNKGbcEYABdonNZkkx141Rj03cZQFCgxu3MVEGU+A==", "cpu": [ "x64" ], @@ -5599,9 +5654,9 @@ ] }, "node_modules/turbo-linux-arm64": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.1.1.tgz", - "integrity": "sha512-LidzTCq0yvQ+N8w8Qub9FmhQ/mmEIeoqFi7DSupekEV2EjvE9jw/zYc9Pk67X+g7dHVfgOnvVzmrjChdxpFePw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.1.2.tgz", + "integrity": "sha512-sV8Bpmm0WiuxgbhxymcC7wSsuxfBBieI98GegSwbr/bs1ANAgzCg93urIrdKdQ3/b31zZxQwcaP4FBF1wx1Qdg==", "cpu": [ "arm64" ], @@ -5613,9 +5668,9 @@ ] }, "node_modules/turbo-windows-64": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.1.1.tgz", - "integrity": "sha512-GKc9ZywKwy4xLDhwXd6H07yzl0TB52HjXMrFLyHGhCVnf/w0oq4sLJv2sjbvuarPjsyx4xnCBJ3m3oyL2XmFtA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.1.2.tgz", + "integrity": "sha512-wcmIJZI9ORT9ykHGliFE6kWRQrlH930QGSjSgWC8uFChFFuOyUlvC7ttcxuSvU9VqC7NF4C+GVAcFJQ8lTjN7g==", "cpu": [ "x64" ], @@ -5627,9 +5682,9 @@ ] }, "node_modules/turbo-windows-arm64": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.1.1.tgz", - "integrity": "sha512-oFKkMj11KKUv3xSK9/fhAEQTxLUp1Ol1EOktwc32+SFtEU0uls7kosAz0b+qe8k3pJGEMFdDPdqoEjyJidbxtQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.1.2.tgz", + "integrity": "sha512-zdnXjrhk7YO6CP+Q5wPueEvOCLH4lDa6C4rrwiakcWcPgcQGbVozJlo4uaQ6awo8HLWQEvOwu84RkWTdLAc/Hw==", "cpu": [ "arm64" ], @@ -5641,17 +5696,17 @@ ] }, "node_modules/typedoc": { - "version": "0.26.6", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.6.tgz", - "integrity": "sha512-SfEU3SH3wHNaxhFPjaZE2kNl/NFtLNW5c1oHsg7mti7GjmUj1Roq6osBQeMd+F4kL0BoRBBr8gQAuqBlfFu8LA==", + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.7.tgz", + "integrity": "sha512-gUeI/Wk99vjXXMi8kanwzyhmeFEGv1LTdTQsiyIsmSYsBebvFxhbcyAx7Zjo4cMbpLGxM4Uz3jVIjksu/I2v6Q==", "dev": true, "license": "Apache-2.0", "dependencies": { "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", - "shiki": "^1.9.1", - "yaml": "^2.4.5" + "shiki": "^1.16.2", + "yaml": "^2.5.1" }, "bin": { "typedoc": "bin/typedoc" @@ -5660,13 +5715,13 @@ "node": ">= 18" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x" } }, "node_modules/typedoc-plugin-markdown": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.6.tgz", - "integrity": "sha512-k33o2lZSGpL3GjH28eW+RsujzCYFP0L5GNqpK+wa4CBcMOxpj8WV7SydNRLS6eSa2UvaPvNVJTaAZ6Tm+8GXoA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.7.tgz", + "integrity": "sha512-bLsQdweSm48P9j6kGqQ3/4GCH5zu2EnURSkkxqirNc+uVFE9YK825ogDw+WbNkRHIV6eZK/1U43gT7YfglyYOg==", "dev": true, "license": "MIT", "engines": { @@ -5676,10 +5731,40 @@ "typedoc": "0.26.x" } }, + "node_modules/typedoc/node_modules/@shikijs/core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.17.7.tgz", + "integrity": "sha512-ZnIDxFu/yvje3Q8owSHaEHd+bu/jdWhHAaJ17ggjXofHx5rc4bhpCSW+OjC6smUBi5s5dd023jWtZ1gzMu/yrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.17.7", + "@shikijs/engine-oniguruma": "1.17.7", + "@shikijs/types": "1.17.7", + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.2" + } + }, + "node_modules/typedoc/node_modules/shiki": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.17.7.tgz", + "integrity": "sha512-Zf6hNtWhFyF4XP5OOsXkBTEx9JFPiN0TQx4wSe+Vqeuczewgk2vT4IZhF4gka55uelm052BD5BaHavNqUNZd+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "1.17.7", + "@shikijs/engine-javascript": "1.17.7", + "@shikijs/engine-oniguruma": "1.17.7", + "@shikijs/types": "1.17.7", + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5711,6 +5796,79 @@ "devOptional": true, "license": "MIT" }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -5759,10 +5917,40 @@ "dev": true, "license": "MIT" }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz", - "integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -5819,16 +6007,15 @@ } }, "node_modules/vite-node": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", - "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.1.tgz", + "integrity": "sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.5", + "debug": "^4.3.6", "pathe": "^1.1.2", - "tinyrainbow": "^1.2.0", "vite": "^5.0.0" }, "bin": { @@ -5841,46 +6028,31 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vitefu": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", - "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, "node_modules/vitest": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz", - "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.1.tgz", + "integrity": "sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@vitest/expect": "2.0.5", - "@vitest/pretty-format": "^2.0.5", - "@vitest/runner": "2.0.5", - "@vitest/snapshot": "2.0.5", - "@vitest/spy": "2.0.5", - "@vitest/utils": "2.0.5", + "@vitest/expect": "2.1.1", + "@vitest/mocker": "2.1.1", + "@vitest/pretty-format": "^2.1.1", + "@vitest/runner": "2.1.1", + "@vitest/snapshot": "2.1.1", + "@vitest/spy": "2.1.1", + "@vitest/utils": "2.1.1", "chai": "^5.1.1", - "debug": "^4.3.5", - "execa": "^8.0.1", - "magic-string": "^0.30.10", + "debug": "^4.3.6", + "magic-string": "^0.30.11", "pathe": "^1.1.2", "std-env": "^3.7.0", - "tinybench": "^2.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.0", "tinypool": "^1.0.0", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.0.5", + "vite-node": "2.1.1", "why-is-node-running": "^2.3.0" }, "bin": { @@ -5895,8 +6067,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.0.5", - "@vitest/ui": "2.0.5", + "@vitest/browser": "2.1.1", + "@vitest/ui": "2.1.1", "happy-dom": "*", "jsdom": "*" }, @@ -6102,9 +6274,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", - "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", "dev": true, "license": "ISC", "bin": { @@ -6124,8 +6296,19 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "packages/create-domco": { - "version": "0.1.13", + "version": "0.1.14", "license": "MIT", "dependencies": { "@clack/prompts": "^0.7.0", @@ -6139,18 +6322,17 @@ } }, "packages/domco": { - "version": "0.11.0", + "version": "0.12.0", "license": "MIT", "devDependencies": { - "typedoc": "^0.26.6", - "typedoc-plugin-markdown": "^4.2.6", - "vitest": "^2.0.5" + "typedoc": "^0.26.7", + "typedoc-plugin-markdown": "^4.2.7", + "vitest": "^2.1.1" }, "engines": { "node": ">=20" }, "peerDependencies": { - "hono": "^4.5.0", "vite": "^5.4.0" } }, diff --git a/package.json b/package.json index b5511bc..63af2ed 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,11 @@ "@robino/md": "^0.2.0", "@robino/prettier": "^0.1.1", "@robino/tsconfig": "^0.2.1", - "hono": "^4.5.11", + "hono": "^4.6.2", "prettier": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.6", - "turbo": "^2.1.1", - "typescript": "^5.5.4", - "vite": "^5.4.3" + "turbo": "^2.1.2", + "typescript": "^5.6.2", + "vite": "^5.4.6" } } diff --git a/packages/create-domco/package.json b/packages/create-domco/package.json index d07d702..4191fc3 100644 --- a/packages/create-domco/package.json +++ b/packages/create-domco/package.json @@ -13,11 +13,11 @@ ], "scripts": { "test:clean": "rm -rf ./create-domco-test", - "test": "build:js && npm run test:clean && echo 'Create your project in `create-domco-test`\n\n' && node ./dist/bin.js", + "test": "npm run build && echo 'Create your project in `create-domco-test`\n\n' && node ./dist/bin.js", "dev": "tsc --watch", "build:clean": "rm -rf ./dist", "build:js": "tsc", - "build": "npm run test:clean && npm run build:clean && npm run build:js" + "build": "npm run test:clean & npm run build:clean & wait && npm run build:js" }, "author": { "name": "Ross Robino", diff --git a/packages/create-domco/src/dependencies/index.ts b/packages/create-domco/src/dependencies/index.ts index 5824f61..36e9aaf 100644 --- a/packages/create-domco/src/dependencies/index.ts +++ b/packages/create-domco/src/dependencies/index.ts @@ -1,10 +1,9 @@ export const dependencies = { - domco: "0.12.0", - hono: "4.6.1", + domco: "0.13.0", autoprefixer: "10.4.20", prettier: "3.3.3", prettierTailwind: "0.6.6", tailwind: "3.4.11", - typescript: "5.6.0", - vite: "5.4.3", + typescript: "5.6.2", + vite: "5.4.6", } as const; diff --git a/packages/create-domco/src/index.ts b/packages/create-domco/src/index.ts index 731e7b3..67fbebb 100644 --- a/packages/create-domco/src/index.ts +++ b/packages/create-domco/src/index.ts @@ -1,7 +1,8 @@ +import app from "./template-files/app.js"; import denoJson from "./template-files/deno-json.js"; +import envTypes from "./template-files/env-types.js"; import favicon from "./template-files/favicon.js"; import gitignore from "./template-files/gitignore.js"; -import globalTypes from "./template-files/global-types.js"; import packageJson from "./template-files/package-json.js"; import pageHtml from "./template-files/page-html.js"; import prettier from "./template-files/prettier.js"; @@ -110,10 +111,11 @@ export const createDomco = async () => { const getAllTemplateFiles: GetTemplateFile = (options) => { const getTemplateFileFunctions = [ + app, denoJson, favicon, gitignore, - globalTypes, + envTypes, packageJson, pageHtml, prettier, diff --git a/packages/create-domco/src/template-files/app.ts b/packages/create-domco/src/template-files/app.ts new file mode 100644 index 0000000..2013c4d --- /dev/null +++ b/packages/create-domco/src/template-files/app.ts @@ -0,0 +1,29 @@ +import type { GetTemplateFile } from "../index.js"; + +const getTemplateFiles: GetTemplateFile = ({ lang }) => { + const isTs = lang === "ts"; + return [ + { + name: `src/server/+app.${lang}`, + contents: `${isTs ? `import type { Handler } from "domco";` : `/** @import { Handler } from "domco" */`} +import { html } from "client:page"; +${isTs ? "" : "\n/** @type {Handler} */"} +export const handler${isTs ? ": Handler" : ""} = (req) => { + const { pathname } = new URL(req.url); + + if (pathname === "/") { + return new Response(html, { + headers: { + "Content-Type": "text/html", + }, + }); + } + + return new Response("Not found", { status: 404 }); +}; +`, + }, + ]; +}; + +export default getTemplateFiles; diff --git a/packages/create-domco/src/template-files/deno-json.ts b/packages/create-domco/src/template-files/deno-json.ts index 21dfc31..548a99f 100644 --- a/packages/create-domco/src/template-files/deno-json.ts +++ b/packages/create-domco/src/template-files/deno-json.ts @@ -19,8 +19,7 @@ const getTemplateFiles: GetTemplateFile = ({ pm, prettier, tailwind }) => { }, "nodeModulesDir": true, "imports": { - "domco": "npm:domco@^${dependencies.domco}", - "hono": "npm:hono@^${dependencies.hono}",${prettier ? `\n\t\t"prettier": "npm:prettier@^${dependencies.prettier}",` : ""}${ + "domco": "npm:domco@^${dependencies.domco}",${prettier ? `\n\t\t"prettier": "npm:prettier@^${dependencies.prettier}",` : ""}${ prettier && tailwind ? `\n\t\t"prettier-plugin-tailwindcss": "npm:prettier-plugin-tailwindcss@^${dependencies.prettierTailwind}",` : "" @@ -33,8 +32,6 @@ const getTemplateFiles: GetTemplateFile = ({ pm, prettier, tailwind }) => { }, "compilerOptions": { "checkJs": true, - "jsx": "react-jsx", - "jsxImportSource": "hono/jsx", "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, diff --git a/packages/create-domco/src/template-files/global-types.ts b/packages/create-domco/src/template-files/env-types.ts similarity index 53% rename from packages/create-domco/src/template-files/global-types.ts rename to packages/create-domco/src/template-files/env-types.ts index fd0e408..2447889 100644 --- a/packages/create-domco/src/template-files/global-types.ts +++ b/packages/create-domco/src/template-files/env-types.ts @@ -3,14 +3,9 @@ import type { GetTemplateFile } from "../index.js"; const getTemplateFiles: GetTemplateFile = () => { return [ { - name: "src/global.d.ts", + name: "src/env.d.ts", contents: `/// -import type { DomcoContextVariableMap } from "domco"; -import "hono"; - -declare module "hono" { - interface ContextVariableMap extends DomcoContextVariableMap {} -} +/// `, }, ]; diff --git a/packages/create-domco/src/template-files/gitignore.ts b/packages/create-domco/src/template-files/gitignore.ts index 7b48f9f..057038f 100644 --- a/packages/create-domco/src/template-files/gitignore.ts +++ b/packages/create-domco/src/template-files/gitignore.ts @@ -13,6 +13,8 @@ pnpm-debug.log* lerna-debug.log* node_modules dist +.env +.env.* *.local .vscode/* !.vscode/extensions.json diff --git a/packages/create-domco/src/template-files/package-json.ts b/packages/create-domco/src/template-files/package-json.ts index 622fc49..f619227 100644 --- a/packages/create-domco/src/template-files/package-json.ts +++ b/packages/create-domco/src/template-files/package-json.ts @@ -26,8 +26,7 @@ const getTemplateFiles: GetTemplateFile = ({ } }, "devDependencies": { - "domco": "^${dependencies.domco}", - "hono": "^${dependencies.hono}",${prettier ? `\n\t\t"prettier": "^${dependencies.prettier}",` : ""}${ + "domco": "^${dependencies.domco}",${prettier ? `\n\t\t"prettier": "^${dependencies.prettier}",` : ""}${ prettier && tailwind ? `\n\t\t"prettier-plugin-tailwindcss": "^${dependencies.prettierTailwind}",` : "" diff --git a/packages/create-domco/src/template-files/page-html.ts b/packages/create-domco/src/template-files/page-html.ts index 6b5348d..0e9c316 100644 --- a/packages/create-domco/src/template-files/page-html.ts +++ b/packages/create-domco/src/template-files/page-html.ts @@ -2,27 +2,26 @@ import type { GetTemplateFile } from "../index.js"; import { faviconFileName } from "./favicon.js"; import { styleFileName } from "./style-css.js"; -const getTemplateFiles: GetTemplateFile = () => { +const getTemplateFiles: GetTemplateFile = ({ tailwind }) => { return [ { - name: "src/+page.html", + name: "src/client/+page.html", contents: ` - + Title
-

Hello World

+ Hello World
diff --git a/packages/create-domco/src/template-files/prettier.ts b/packages/create-domco/src/template-files/prettier.ts index e7ed2a0..905e0da 100644 --- a/packages/create-domco/src/template-files/prettier.ts +++ b/packages/create-domco/src/template-files/prettier.ts @@ -6,7 +6,9 @@ const getTemplateFiles: GetTemplateFile = ({ prettier, tailwind }) => { return [ { name: "prettier.config.js", - contents: `/** @type {import("prettier").Config} */ + contents: `/** @import { Config } from "prettier" */ + +/** @type {Config} */ export default { useTabs: true,${ tailwind ? `\n\tplugins: ["prettier-plugin-tailwindcss"],\n` : "" @@ -14,7 +16,7 @@ export default { }, { name: ".prettierignore", - contents: `.DS_Store\nnode_modules\n/dist\n.env\n.env.*\npackage-lock.json\npnpm-lock.yaml\nyarn.lock\nbun.lockb\n.vercel\n.cloudflare\n`, + contents: `.DS_Store\nnode_modules\n/dist\npackage-lock.json\npnpm-lock.yaml\nyarn.lock\nbun.lockb\n.vercel\n.cloudflare\n`, }, ]; }; diff --git a/packages/create-domco/src/template-files/style-css.ts b/packages/create-domco/src/template-files/style-css.ts index a982bc9..a30331f 100644 --- a/packages/create-domco/src/template-files/style-css.ts +++ b/packages/create-domco/src/template-files/style-css.ts @@ -1,11 +1,14 @@ import type { GetTemplateFile } from "../index.js"; -export const styleFileName = "style.css"; +export const styleFileName = { + base: "client/style.css", + tailwind: "client/tailwind.css", +} as const; const getTemplateFiles: GetTemplateFile = ({ tailwind }) => { return [ { - name: `src/${styleFileName}`, + name: `src/${tailwind ? styleFileName.tailwind : styleFileName.base}`, contents: tailwind ? `@tailwind base; @tailwind components; diff --git a/packages/create-domco/src/template-files/tailwind.ts b/packages/create-domco/src/template-files/tailwind.ts index 6e94174..3d7c1d4 100644 --- a/packages/create-domco/src/template-files/tailwind.ts +++ b/packages/create-domco/src/template-files/tailwind.ts @@ -9,7 +9,7 @@ const getTemplateFiles: GetTemplateFile = ({ lang, tailwind }) => { contents: `${ lang === "ts" ? `import type { Config } from "tailwindcss";\n` - : `/** @type {import("tailwindcss").Config} */` + : `/** @import { Config } from "tailwindcss" */\n\n/** @type {Config} */` } export default { content: ["./src/**/*.{html,js,ts,jsx,tsx}"], diff --git a/packages/create-domco/src/template-files/tsconfig-json.ts b/packages/create-domco/src/template-files/tsconfig-json.ts index ebb5a21..3785919 100644 --- a/packages/create-domco/src/template-files/tsconfig-json.ts +++ b/packages/create-domco/src/template-files/tsconfig-json.ts @@ -21,7 +21,6 @@ const getTemplateFiles: GetTemplateFile = ({ pm }) => { "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", - "jsxImportSource": "hono/jsx", /* Linting */ "strict": true, diff --git a/packages/domco/package.json b/packages/domco/package.json index 723a3c9..667ecc0 100644 --- a/packages/domco/package.json +++ b/packages/domco/package.json @@ -41,21 +41,19 @@ "types": "./dist/adapter/vercel/index.d.ts", "default": "./dist/adapter/vercel/index.js" }, - "./app": { - "types": "./dist/app/index.d.ts", - "default": "./dist/app/index.js" - }, - "./app/dev": { - "types": "./dist/app/dev/index.d.ts", - "default": "./dist/app/dev/index.js" + "./env": { + "types": "./dist/types/env.d.ts" }, "./injector": { "types": "./dist/injector/index.d.ts", "default": "./dist/injector/index.js" }, - "./node/request-listener": { - "types": "./dist/node/request-listener/index.d.ts", - "default": "./dist/node/request-listener/index.js" + "./listener": { + "types": "./dist/listener/index.d.ts", + "default": "./dist/listener/index.js" + }, + "./types": { + "types": "./dist/types/index.d.ts" }, "./version": { "types": "./dist/version/index.d.ts", @@ -73,16 +71,15 @@ "test:dev": "vitest", "build:clean": "rm -rf ./dist", "build:js": "tsc", - "build": "node scripts/version && npm run build:clean && npm run build:js", - "doc": "typedoc src/index.ts --out ../../apps/docs/src/generated --plugin typedoc-plugin-markdown --outputFileStrategy modules --hidePageHeader --hidePageTitle --useHTMLAnchors" + "build": "node scripts/version && npm run build:clean && npm run build:js && cp src/types/env.d.ts dist/types/env.d.ts", + "doc": "typedoc src/index.ts --out ../../apps/docs/src/server/generated --plugin typedoc-plugin-markdown --outputFileStrategy modules --hidePageHeader --hidePageTitle --useHTMLAnchors" }, "devDependencies": { - "typedoc": "^0.26.6", - "typedoc-plugin-markdown": "^4.2.6", - "vitest": "^2.0.5" + "typedoc": "^0.26.7", + "typedoc-plugin-markdown": "^4.2.7", + "vitest": "^2.1.1" }, "peerDependencies": { - "hono": "^4.5.0", "vite": "^5.4.0" } } diff --git a/packages/domco/src/adapter/cloudflare/index.ts b/packages/domco/src/adapter/cloudflare/index.ts index 251e48c..039884c 100644 --- a/packages/domco/src/adapter/cloudflare/index.ts +++ b/packages/domco/src/adapter/cloudflare/index.ts @@ -1,5 +1,5 @@ import { dirNames, headers } from "../../constants/index.js"; -import type { AdapterBuilder } from "../../types/public/index.js"; +import type { AdapterBuilder } from "../../types/index.js"; import { clearDir, copyClient, @@ -38,17 +38,15 @@ export const adapter: AdapterBuilder = async () => { target: "webworker", noExternal: true, message: - "created Cloudflare Pages build .cloudflare/\n\ninstall wrangler and run `wrangler pages dev .cloudflare` to preview your build\nhttps://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler#installation\nhttps://developers.cloudflare.com/workers/wrangler/commands/#pages", + "created Cloudflare Pages build .cloudflare/\ninstall wrangler and run `wrangler pages dev .cloudflare` to preview your build\nhttps://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler#installation\nhttps://developers.cloudflare.com/workers/wrangler/commands/#pages", entry: ({ appId }) => { return { id: "_worker", code: ` - import { createApp } from "${appId}"; + import { handler } from "${appId}"; - const app = createApp(); - - export default app; + export default { fetch: handler }; `, }; }, @@ -65,6 +63,8 @@ export const adapter: AdapterBuilder = async () => { /** * Adds paths to exclude from when the worker gets called. + * Need to exclude all static files, since these will be served in + * front of the function. * * @param dir directory to walk */ @@ -79,20 +79,23 @@ export const adapter: AdapterBuilder = async () => { const staticFiles = await fs.readdir(dir, { withFileTypes: true }); + const subDirPromises: Promise[] = []; + for (const file of staticFiles) { const filePath = path.join(dir, file.name); let relativePath = toPosix(`/${path.relative(base, filePath)}`); if ( + // already added via wildcard relativePath.startsWith(`/${dirNames.out.client.immutable}`) || + // manifest relativePath.startsWith("/.vite") ) { - // already added via wildcard continue; } if (file.isDirectory()) { - await addExclusions(filePath); + subDirPromises.push(addExclusions(filePath)); continue; } @@ -109,17 +112,17 @@ export const adapter: AdapterBuilder = async () => { routes.exclude.push(relativePath); } - }; - - await addExclusions(); - await clearDir(outDir); + await Promise.all(subDirPromises); + }; - await fs.mkdir(outDir, { recursive: true }); + await Promise.all([addExclusions(), clearDir(outDir)]); await Promise.all([ + // copy output into .cloudflare copyClient(outDir), copyServer(outDir), + fs.writeFile( path.join(outDir, "_routes.json"), JSON.stringify(routes, null, "\t"), diff --git a/packages/domco/src/adapter/deno/index.ts b/packages/domco/src/adapter/deno/index.ts index 2bc4419..0b9c418 100644 --- a/packages/domco/src/adapter/deno/index.ts +++ b/packages/domco/src/adapter/deno/index.ts @@ -1,5 +1,5 @@ import { dirNames } from "../../constants/index.js"; -import type { AdapterBuilder } from "../../types/public/index.js"; +import type { AdapterBuilder } from "../../types/index.js"; /** * Creates a Deno Deploy build. @@ -34,8 +34,14 @@ export const adapter: AdapterBuilder = async () => { entry: ({ appId }) => { return { id: "main", + + /** + * Need to first serve static, deno's file server will redirect if there + * is a directory with the pathname, so that needs to be tried first before + * falling back to the handler. + */ code: ` - import { createApp } from "${appId}"; + import { handler } from "${appId}"; import { serveDir } from "https://jsr.io/@std/http/1.0.6/file_server.ts"; const getStatic = async (req) => { @@ -45,8 +51,8 @@ export const adapter: AdapterBuilder = async () => { }); }; - const serveStatic = async (c, next) => { - const res = await getStatic(c.req.raw); + const denoHandler = async (req) => { + const res = await getStatic(req); if (res.ok) return res; @@ -58,19 +64,10 @@ export const adapter: AdapterBuilder = async () => { } } - await next(); + return handler(req); }; - const app = createApp({ - middleware: [ - { - path: "/*", - handler: serveStatic, - }, - ], - }); - - Deno.serve(app.fetch); + Deno.serve(denoHandler); `, }; }, diff --git a/packages/domco/src/adapter/vercel/index.ts b/packages/domco/src/adapter/vercel/index.ts index 047a917..e96d6ef 100644 --- a/packages/domco/src/adapter/vercel/index.ts +++ b/packages/domco/src/adapter/vercel/index.ts @@ -2,8 +2,8 @@ import { dirNames, headers } from "../../constants/index.js"; import type { AdapterBuilder, AdapterEntry, - CreateAppMiddleware, -} from "../../types/public/index.js"; + AdapterMiddleware, +} from "../../types/index.js"; import { clearDir, copyClient, copyServer } from "../../util/fs/index.js"; import { version } from "../../version/index.js"; import type { @@ -12,41 +12,55 @@ import type { RequiredOptions, VercelAdapterOptions, } from "./types.js"; -import { createMiddleware } from "hono/factory"; -import type { HonoOptions } from "hono/hono-base"; import fs from "node:fs/promises"; import path from "node:path"; -/** This function is required for ISR. */ -export const getPath: HonoOptions<{}>["getPath"] = (req) => { +const entryId = "main"; + +/** Use when runtime is set to node. */ +const nodeEntry: AdapterEntry = ({ appId }) => { + return { + id: entryId, + code: ` +import { handler } from "${appId}"; +import { nodeListener } from "domco/listener"; + +export default nodeListener(handler); +`, + }; +}; + +/** + * This function is required for ISR. + * + * Gets the `__pathname` query param and sets the url pathname + * to it. See `allowQuery` in config. + */ +export const getUrl = (req: Request) => { const url = new URL(req.url); const params = new URLSearchParams(url.search); const pathnameParam = "__pathname"; const pathname = `/${params.get(pathnameParam) ?? ""}`; - if (pathname) { - params.delete(pathnameParam); - return `${pathname}${params.toString() ? `?${params}` : ""}`; - } + params.delete(pathnameParam); + url.pathname = pathname; - return req.url; + return url; }; -const entryId = "main"; - -/** Use when runtime is set to node. */ -const nodeEntry: AdapterEntry = ({ appId }) => { +/** Use when runtime is set to node and ISR. */ +const isrEntry: AdapterEntry = ({ appId }) => { return { id: entryId, code: ` -import { createApp } from "${appId}"; -import { createRequestListener } from "domco/node/request-listener"; -import { getPath } from "domco/adapter/vercel"; +import { handler } from "${appId}"; +import { nodeListener } from "domco/listener"; +import { getUrl } from "domco/adapter/vercel"; -const app = createApp({ honoOptions: { getPath } }); +const isrHandler = async (req) => handler(new Request(getUrl(req))); -export default createRequestListener(app.fetch); +export default nodeListener(isrHandler); `, }; }; @@ -56,12 +70,9 @@ const edgeEntry: AdapterEntry = ({ appId }) => { return { id: entryId, code: ` -import { createApp } from "${appId}"; -import { handle } from "hono/vercel"; - -const app = createApp(); +import { handler } from "${appId}"; -export default handle(app); +export default handler; `, }; }; @@ -122,32 +133,41 @@ export const adapter: AdapterBuilder = ( resolvedOptions.isr = options?.isr; resolvedOptions.images = options?.images; + let entry = nodeEntry; + if (isEdge) entry = edgeEntry; + else if (options?.isr) entry = isrEntry; + /** * This is applied in `dev` and `preview` so users can see the src images. */ - const imageMiddleware: CreateAppMiddleware = { - path: "/*", - handler: createMiddleware(async (c, next) => { - if (resolvedOptions.images) { - if (c.req.path.startsWith("/_vercel/image")) { - const { url, w, q } = c.req.query(); - - if (!url) - throw new Error(`Add a \`url\` query param to ${c.req.url}`); - if (!w) throw new Error(`Add a \`w\` query param to ${c.req.url}`); - if (!q) throw new Error(`Add a \`q\` query param to ${c.req.url}`); - - if (!resolvedOptions.images.sizes.includes(parseInt(w))) { - throw new Error( + const imageMiddleware: AdapterMiddleware = (req, res, next) => { + if (resolvedOptions.images) { + if (req.url?.startsWith("/_vercel/image")) { + const query = new URLSearchParams(req.url.split("?")[1]); + + const url = query.get("url"); + const w = query.get("w"); + const q = query.get("q"); + + if (!url) + return next(new Error(`Add a \`url\` query param to ${req.url}`)); + if (!w) return next(new Error(`Add a \`w\` query param to ${req.url}`)); + if (!q) return next(new Error(`Add a \`q\` query param to ${req.url}`)); + + if (!resolvedOptions.images.sizes.includes(parseInt(w))) { + return next( + new Error( `\`${w}\` is not an included image size. Add \`${w}\` to \`sizes\` in your adapter config to support this width.`, - ); - } - - return c.redirect(url); + ), + ); } + + res.writeHead(302, { Location: url }); + return res.end(); } - await next(); - }), + } + + return next(); }; return { @@ -155,7 +175,7 @@ export const adapter: AdapterBuilder = ( target: isEdge ? "webworker" : "node", noExternal: true, message: `created ${resolvedOptions.config.runtime} build .vercel/`, - entry: isEdge ? edgeEntry : nodeEntry, + entry, devMiddleware: [imageMiddleware], previewMiddleware: [imageMiddleware], @@ -204,7 +224,7 @@ export const adapter: AdapterBuilder = ( await fs.mkdir(fnDir, { recursive: true }); - await Promise.all([ + const tasks = [ copyClient(path.join(outDir, "static")), copyServer(fnDir), @@ -226,20 +246,23 @@ export const adapter: AdapterBuilder = ( path.join(outDir, "functions", `${fnName}.func`, "package.json"), JSON.stringify({ type: "module" }, null, "\t"), ), - ]); + ]; if (resolvedOptions.isr) { - // TODO add prerender fallback - // write prerender-config - await fs.writeFile( - path.join(outDir, "functions", `${fnName}.prerender-config.json`), - JSON.stringify( - Object.assign(defaultIsr, resolvedOptions.isr), - null, - "\t", + tasks.push( + // write prerender-config + fs.writeFile( + path.join(outDir, "functions", `${fnName}.prerender-config.json`), + JSON.stringify( + Object.assign(defaultIsr, resolvedOptions.isr), + null, + "\t", + ), ), ); } + + await Promise.all(tasks); }, }; }; diff --git a/packages/domco/src/app/dev/index.ts b/packages/domco/src/app/dev/index.ts deleted file mode 100644 index f31948c..0000000 --- a/packages/domco/src/app/dev/index.ts +++ /dev/null @@ -1,143 +0,0 @@ -import type { Routes } from "../../types/private/index.js"; -import type { CreateAppOptions } from "../../types/public/index.js"; -import { createRoutes } from "../../util/create-routes/index.js"; -import { standardMiddleware } from "../mw/index.js"; -import { addMiddleware, addRoutes, addSetup } from "../util/index.js"; -import { Hono } from "hono"; -import fs from "node:fs/promises"; -import path from "node:path"; -import process from "node:process"; -import type { ViteDevServer } from "vite"; - -/** - * Creates a app for development use in the `configureServer` Vite hook. - * - * @param options - * @returns Hono app instance. - */ -export const createAppDev = ( - options: CreateAppOptions = {}, -) => { - const { devServer, honoOptions, middleware } = options; - - const rootApp = new Hono(honoOptions); - - addMiddleware(rootApp, middleware); - - rootApp.all("/*", async (c) => { - // this has to be called each request for HMR - const routes = await getRoutesDev({ - devServer, - }); - - // start fresh each time for dev for HMR - const app = new Hono(honoOptions); - - app.onError((err, c) => { - console.log(); - console.error(err); - return c.html(errorTemplate(err)); - }); - - app.notFound((c) => { - return c.html( - errorTemplate({ - message: "404 - Not Found", - stack: `No route matched for ${c.req.url}\n\nMake sure there is a route registered for this path.`, - name: "404 - Not Found", - }), - 404, - ); - }); - - addMiddleware(app, standardMiddleware); - - addSetup(app, routes); - - addRoutes({ app, routes }); - - app.use(async (c, next) => { - // fallback to the html in dev, vite does not automatically serve the fallback - // can't use serve static here on `src` because it will not apply the transformations - const html = routes[c.req.path]?.page; - if (html) return c.html(html); - - await next(); - }); - - return app.fetch(c.req.raw); - }); - - return rootApp; -}; - -/** - * Gets the routes in development using the pre-built file names and the Vite - * module runner. - * - * @returns Loaded route modules, keys are `routePath`s. - */ -const getRoutesDev = async (options: { devServer?: ViteDevServer }) => { - const { devServer } = options; - - const merged = await createRoutes(); - - const loadedRoutes: Routes = {}; - - for (const routePath in merged) { - // convert filepaths into modules and html strings - loadedRoutes[routePath] = {}; - - loadedRoutes[routePath].client = merged[routePath]?.client; - - if (merged[routePath]?.server) { - const handlerPath = path.join(process.cwd(), merged[routePath].server); - - loadedRoutes[routePath].server = - await devServer?.ssrLoadModule(handlerPath); - } - - if (merged[routePath]?.page) { - const html = await fs.readFile( - path.join(process.cwd(), merged[routePath].page), - "utf-8", - ); - loadedRoutes[routePath].page = await devServer?.transformIndexHtml( - routePath, - html, - ); - } - } - - return loadedRoutes; -}; - -const errorTemplate = (err: Error) => /* html */ ` - - - - - ${err.name} - - - -

${err.name}

-
${err.stack}
- - -`; diff --git a/packages/domco/src/app/index.ts b/packages/domco/src/app/index.ts deleted file mode 100644 index 75d66b7..0000000 --- a/packages/domco/src/app/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { CreateAppOptions } from "../types/public/index.js"; -import { standardMiddleware } from "./mw/index.js"; -import { addMiddleware, addRoutes, addSetup } from "./util/index.js"; -import { manifest } from "domco:manifest"; -import { routes } from "domco:routes"; -import { Hono } from "hono"; - -/** - * Creates your production Hono app instance. You can import `createApp` from - * `./dist/server/app.js` after your build is complete. - * - * This export must be used within a Vite build context. It will not work to - * directly import from `"domco/app"`, instead import from your build. - * - * Below is an example of how to import your app after build is complete to make a - * Node server. You can adapt this to different [environments of your choice](https://hono.dev/docs/getting-started/basic). - * - * @param options - * @returns Hono app instance. - * - * @example - * - * ```js - * // example using Node.js and `@hono/node-server` - * import { serve } from "@hono/node-server"; - * import { serveStatic } from "@hono/node-server/serve-static"; - * import { createApp } from "./dist/server/app.js"; - * - * const app = createApp({ - * middleware: [{ path: "/*", handler: serveStatic({ root: "./dist/client" }) }], - * }); - * - * serve(app); - * ``` - */ -export const createApp = ( - options: CreateAppOptions = {}, -) => { - const app = new Hono(options.honoOptions); - - addMiddleware(app, options.middleware); - addMiddleware(app, standardMiddleware); - - addSetup(app, routes); - - addRoutes({ app, routes, manifest }); - - return app; -}; diff --git a/packages/domco/src/app/mw/index.ts b/packages/domco/src/app/mw/index.ts deleted file mode 100644 index c3da29f..0000000 --- a/packages/domco/src/app/mw/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { CreateAppOptions } from "../../types/public/index.js"; -import { setServer } from "../util/index.js"; -import { trimTrailingSlash } from "hono/trailing-slash"; - -/** Middleware used in `dev` and `prod`. */ -export const standardMiddleware: CreateAppOptions["middleware"] = [ - { - path: "/*", - handler: setServer, - }, - { - path: "/*", - handler: trimTrailingSlash(), - }, -]; diff --git a/packages/domco/src/app/util/index.ts b/packages/domco/src/app/util/index.ts deleted file mode 100644 index 5b0b2c2..0000000 --- a/packages/domco/src/app/util/index.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { setup } from "../../constants/index.js"; -import { type TagDescriptor, serializeTags } from "../../injector/index.js"; -import type { Routes } from "../../types/private/index.js"; -import type { - Client, - CreateAppOptions, - DomcoContextVariableMap, - Page, - Server, -} from "../../types/public/index.js"; -import type { Hono } from "hono"; -import { createMiddleware } from "hono/factory"; -import { raw } from "hono/html"; -import type { Manifest } from "vite"; - -export const addMiddleware = ( - app: Hono, - middleware: CreateAppOptions["middleware"], -) => { - if (middleware) { - for (const mw of middleware) { - app.use(mw.path, mw.handler); - } - } -}; - -/** - * Adds routes based on route modules to the base app. - * - * @param options - */ -export const addRoutes = (options: { - /** app to add routes to. */ - app: Hono; - - /** `Routes` to add. */ - routes: Routes; - - manifest?: Manifest; -}) => { - const { app, routes, manifest } = options; - - const routeApps: { routeId: string; routeApp: Hono }[] = []; - - for (const routeId in routes) { - const route = routes[routeId]!; - - if (route.server?.default) { - routeApps.push({ routeId, routeApp: route.server.default }); - } - } - - routeApps.sort((a, b) => b.routeId.localeCompare(a.routeId)); - - // avoids setting multiple times since the same route can be declared twice - const varsSet = new Set(); - - for (const { routeId, routeApp } of routeApps) { - if (routeId !== setup) { - // set middleware by route - - for (const route of routeApp.routes) { - let routePath = `${routeId === "/" ? "" : routeId}${route.path === "/" ? "" : route.path}`; - if (routePath === "") routePath = "/"; - - if (!varsSet.has(routePath)) { - varsSet.add(routePath); - - const page: Page = (routePath) => { - if (!routePath) routePath = routeId; - - return routes[routePath]?.page ?? ""; - }; - - const client: Client = (routePath) => { - if (!routePath) routePath = routeId; - - const entryPath = routes[routePath]?.client; - - if (!entryPath) - throw Error(`No client script found for ${routePath}`); - - const tags: TagDescriptor[] = []; - - if (manifest) { - tags.push(...getTagsForEntry({ manifest, entryPath })); - } else { - tags.push({ - tag: "script", - attrs: { type: "module", src: routes[routePath]?.client ?? "" }, - }); - } - - const result = serializeTags(tags); - - return raw(result); - }; - - app.use(routePath, setVariables({ page, client })); - } - } - - app.route(routeId, routeApp); - } - } -}; - -const getTagsForEntry = (options: { - manifest: Manifest; - entryPath: string; - cssOnly?: boolean; -}) => { - const { manifest, entryPath, cssOnly } = options; - - const chunk = manifest[entryPath]; - - if (!chunk) return []; - - const tags: TagDescriptor[] = []; - - if (!cssOnly) { - // push the entry file name - // not needed for `imports`, they are already linked in the code - tags.push({ - tag: "script", - attrs: { type: "module", src: `/${chunk.file}` }, - }); - } - - if (chunk.css) { - // need to also do this for `imports` since css does not actually link in the code - for (const cssFile of chunk.css) { - tags.push({ - tag: "link", - attrs: { rel: "stylesheet", href: `/${cssFile}` }, - }); - } - } - - if (chunk.imports) { - // recursively call on imports - for (const imp of chunk.imports) { - tags.push( - ...getTagsForEntry({ manifest, entryPath: imp, cssOnly: true }), - ); - } - } - - return tags; -}; - -export const setVariables = (variables: Partial) => - createMiddleware(async (c, next) => { - for (const [variable, value] of Object.entries(variables)) { - c.set(variable, value); - } - await next(); - }); - -export const addSetup = (app: Hono, routes: Routes) => { - if (routes[setup]?.server?.default) { - app.route("/", routes[setup].server.default); - } -}; - -export const setServer = createMiddleware((appC, next) => { - const server: Server = (routePath: string, init?: RequestInit) => { - const req = new Request(new URL(appC.req.url).origin + routePath, init); - return fetch(req); - }; - - return setVariables({ server })(appC, next); -}); diff --git a/packages/domco/src/constants/index.ts b/packages/domco/src/constants/index.ts index 84acd2e..320224b 100644 --- a/packages/domco/src/constants/index.ts +++ b/packages/domco/src/constants/index.ts @@ -1,15 +1,12 @@ export const fileNames = { + /** app file name without extension */ + app: "+app", + /** page file name with extension */ page: "+page.html", - /** server file name without extension */ - server: "+server", - - /** client file name without extension */ - client: "+client", - - /** setup file name without extension */ - setup: "+setup", + /** script file name without extension */ + script: "+script", /** output files */ out: { @@ -34,7 +31,7 @@ export const dirNames = { }, ssr: "server", }, - src: "src", + src: { base: "src", client: "client", server: "server" }, public: "public", } as const; @@ -42,4 +39,10 @@ export const headers = { cacheControl: { immutable: "public, immutable, max-age=31536000" }, } as const; -export const setup = "setup"; +export const ids = { + /** App entry point ID. */ + app: `/${dirNames.src.server}/${fileNames.app}`, + + /** Adapter entry ID for the entry point provided by the adapter. */ + adapter: "domco:adapter", +} as const; diff --git a/packages/domco/src/index.ts b/packages/domco/src/index.ts index 9c78c3d..34ab23c 100644 --- a/packages/domco/src/index.ts +++ b/packages/domco/src/index.ts @@ -1,3 +1,3 @@ -export type * from "./types/public/index.js"; +export type * from "./types/index.js"; export { domco } from "./plugin/index.js"; diff --git a/packages/domco/src/node/request-listener/index.ts b/packages/domco/src/listener/index.ts similarity index 97% rename from packages/domco/src/node/request-listener/index.ts rename to packages/domco/src/listener/index.ts index 1135bf3..eee76a8 100644 --- a/packages/domco/src/node/request-listener/index.ts +++ b/packages/domco/src/listener/index.ts @@ -2,7 +2,7 @@ * Adapted from https://github.com/mjackson/remix-the-web/blob/main/packages/node-fetch-server * to use as middleware: https://github.com/mjackson/remix-the-web/issues/13 */ -import type { MaybePromise } from "../../types/helper/index.js"; +import type { MaybePromise } from "../types/helper/index.js"; import type { ReadStream } from "node:fs"; import type { IncomingHttpHeaders, @@ -45,7 +45,7 @@ type FetchHandler = ( client: ClientAddress, ) => MaybePromise; -type RequestListenerOptions = { +type ListenerOptions = { /** * Overrides the host portion of the incoming request URL. By default the request URL host is * derived from the HTTP `Host` header. @@ -79,9 +79,9 @@ type RequestListenerOptions = { * Wraps a fetch handler in a Node.js `http.RequestListener` that can be used with * `http.createServer()` or `https.createServer()`. */ -export const createRequestListener = ( +export const nodeListener = ( handler: FetchHandler, - options?: RequestListenerOptions, + options?: ListenerOptions, ): RequestListener => { const onError = options?.onError ?? defaultErrorHandler; diff --git a/packages/domco/src/node/serve-static/index.ts b/packages/domco/src/node/serve-static/index.ts deleted file mode 100644 index 81c2a1b..0000000 --- a/packages/domco/src/node/serve-static/index.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Used during `preview` only. - * - * Adapted from https://github.com/honojs/node-server/blob/main/src/serve-static.ts - */ -import { dirNames } from "../../constants/index.js"; -import { createStreamBody } from "../request-listener/index.js"; -import { createMiddleware } from "hono/factory"; -import { - getFilePath, - getFilePathWithoutDefaultDocument, -} from "hono/utils/filepath"; -import { getMimeType } from "hono/utils/mime"; -import { createReadStream } from "node:fs"; -import { lstat } from "node:fs/promises"; - -export const serveStatic = createMiddleware(async (c, next) => { - if (c.finalized) { - return next(); - } - - const root = `./${dirNames.out.base}/${dirNames.out.client.base}`; - const filename = decodeURIComponent(c.req.path); - - let path = getFilePathWithoutDefaultDocument({ filename, root }); - - if (!path) return next(); - - path = `./${path}`; - - let stats = await getStats(path); - - if (stats?.isDirectory()) { - path = getFilePath({ filename, root }); - - if (!path) return next(); - - path = `./${path}`; - - stats = await getStats(path); - } - - if (!stats) return next(); - - const mime = getMimeType(path); - - if (mime) { - c.header("Content-Type", mime); - } - - if (c.req.method == "HEAD" || c.req.method == "OPTIONS") { - c.header("Content-Length", stats.size.toString()); - return c.body(null, 200); - } - - const range = c.req.header("range") || ""; - - if (!range) { - c.header("Content-Length", stats.size.toString()); - return c.body(createStreamBody(createReadStream(path)), 200); - } - - // handle range - c.header("Accept-Ranges", "bytes"); - c.header("Date", stats.birthtime.toUTCString()); - - const parts = range.replace(/bytes=/, "").split("-", 2); - const start = parts[0] ? parseInt(parts[0], 10) : 0; - let end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1; - if (stats.size < end - start + 1) { - end = stats.size - 1; - } - - const chunkSize = end - start + 1; - const stream = createReadStream(path, { start, end }); - - c.header("Content-Length", chunkSize.toString()); - c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`); - - return c.body(createStreamBody(stream), 206); -}); - -const getStats = async (path: string) => { - try { - const stats = await lstat(path); - return stats; - } catch { - return null; - } -}; diff --git a/packages/domco/src/plugin/adapter/index.ts b/packages/domco/src/plugin/adapter/index.ts index e1a731f..86879dd 100644 --- a/packages/domco/src/plugin/adapter/index.ts +++ b/packages/domco/src/plugin/adapter/index.ts @@ -1,62 +1,29 @@ -import type { Adapter } from "../../types/public/index.js"; -import { style } from "../../util/style/index.js"; -import { appId } from "../entry/index.js"; -import type { Plugin, ResolvedConfig } from "vite"; - -/** SSR entry ID for the entrypoint provided by the adapter. */ -export const ssrId = "domco:ssr-entry"; - +import { ids } from "../../constants/index.js"; +import type { Adapter } from "../../types/index.js"; +import type { Plugin } from "vite"; + +/** + * Creates a virtual module for the adapter entry point to make + * the app usable in the target environment. + * + * @param adapter + * @returns vite plugin + */ export const adapterPlugin = async (adapter?: Adapter): Promise => { - const ssrResolvedId = "\0" + ssrId; - - let viteConfig: ResolvedConfig; + const resolvedAdapterId = `\0${ids.adapter}`; return { - name: "domco:adapter", - - configResolved(config) { - viteConfig = config; - }, + name: ids.adapter, resolveId(id) { - if (id === ssrId) { - return ssrResolvedId; + if (id === ids.adapter) { + return resolvedAdapterId; } }, load(id) { - if (id === ssrResolvedId && adapter) { - // adapter provided entry point that imports the final app - // and makes it usable in the target environment. - return adapter.entry({ appId }).code; - } - }, - - async closeBundle() { - if (viteConfig.build.ssr) { - if (adapter) { - console.log(style.bold(`adapter - ${adapter.name}`)); - - if (adapter.run) { - await adapter.run(); - } - - console.log(style.dim(style.italic(adapter.message))); - console.log(); - } - - console.log(style.bold("✓ build complete")); - - console.log( - style.dim( - style.italic( - "run `vite preview` to preview your app with Vite and Node.js", - ), - ), - ); - - console.log(); - console.log(); + if (id === resolvedAdapterId && adapter) { + return adapter.entry({ appId: ids.app }).code; } }, }; diff --git a/packages/domco/src/plugin/config/index.ts b/packages/domco/src/plugin/config/index.ts index 4c36083..1ecff83 100644 --- a/packages/domco/src/plugin/config/index.ts +++ b/packages/domco/src/plugin/config/index.ts @@ -1,12 +1,16 @@ -import { dirNames, fileNames } from "../../constants/index.js"; -import type { Adapter, DomcoConfig } from "../../types/public/index.js"; +import { dirNames, fileNames, ids } from "../../constants/index.js"; +import type { Adapter, DomcoConfig } from "../../types/index.js"; import { findFiles, toAllScriptEndings } from "../../util/fs/index.js"; -import { ssrId } from "../adapter/index.js"; -import { appId } from "../entry/index.js"; import path from "node:path"; import process from "node:process"; -import type { Plugin, SSROptions } from "vite"; - +import { type Plugin, createLogger } from "vite"; + +/** + * Dynamically sets the Vite config. + * + * @param domcoConfig + * @returns Vite plugin + */ export const configPlugin = async ( domcoConfig: DomcoConfig, ): Promise => { @@ -15,28 +19,38 @@ export const configPlugin = async ( return { name: "domco:config", async config(_, { isSsrBuild, command }) { - /** If Vite is building. */ const build = command === "build"; + const customLogger = createLogger(); + const loggerInfo = customLogger.info; + customLogger.info = (msg, _options) => { + if (build && msg.includes("../dist")) { + // usually logs output relative to root, correct + return loggerInfo(msg.split("../dist/").join("dist/")); + } + + return loggerInfo(msg); + }; + return { + customLogger, resolve: { alias: [ { find: "@", - replacement: path.resolve(dirNames.src), + replacement: path.resolve(dirNames.src.base), }, ], }, - root: dirNames.src, + root: dirNames.src.base, publicDir: isSsrBuild ? false : path.join(process.cwd(), dirNames.public), appType: "custom", ssr: { target: adapter?.target, - noExternal: getNoExternal({ build, adapter }), + noExternal: build ? adapter?.noExternal : undefined, }, - logLevel: build ? "warn" : "info", build: { manifest: !isSsrBuild, target: "es2022", @@ -68,73 +82,50 @@ export const configPlugin = async ( }; }; -const getNoExternal = (options: { adapter?: Adapter; build: boolean }) => { - const { adapter, build } = options; - - let noExternal: SSROptions["noExternal"] = ["domco"]; - - if (!build) return noExternal; - - if (adapter?.noExternal === true) { - noExternal = true; - } else if (adapter?.noExternal instanceof Array) { - noExternal.push(...adapter.noExternal); - } else if (adapter?.noExternal) { - noExternal.push(adapter.noExternal); - } - - return noExternal; -}; - const serverEntry = (adapter?: Adapter) => { const entry: Record = { - app: appId, + app: ids.app, }; - // only create an adapter entrypoint if there's an adapter + // only create an adapter entry point if there's an adapter if (adapter) { - entry[adapter.entry({ appId }).id] = ssrId; + entry[adapter.entry({ appId: ids.app }).id] = ids.adapter; } return entry; }; const clientEntry = async () => { - const entryPoints = await findFiles({ - dir: dirNames.src, - checkEndings: [fileNames.page], - }); - - // rename key - if (entryPoints["/"]) { - entryPoints.main = entryPoints["/"]; - delete entryPoints["/"]; + const [pages, scripts] = await Promise.all([ + findFiles({ + dir: path.join(dirNames.src.base, dirNames.src.client), + checkEndings: [fileNames.page], + }), + findFiles({ + dir: path.join(dirNames.src.base, dirNames.src.client), + checkEndings: toAllScriptEndings(fileNames.script), + }), + ]); + + // rename keys + if (pages["/"]) { + pages.main = pages["/"]; + delete pages["/"]; } - - // remove leading slash - for (const key in entryPoints) { - const entryPath = entryPoints[key]; - if (entryPath) { - entryPoints[key] = entryPath.slice(1); - } + if (scripts["/"]) { + scripts["/main"] = scripts["/"]; + delete scripts["/"]; } - const jsFiles = await findFiles({ - dir: dirNames.src, - checkEndings: toAllScriptEndings("+client"), - }); - - // rename key - if (jsFiles["/"]) { - jsFiles["/main"] = jsFiles["/"]; - delete jsFiles["/"]; + for (const [key, value] of Object.entries(pages)) { + pages[key] = value.slice(dirNames.src.base.length + 1); // remove "/src" } - const jsEntry: Record = {}; + const scriptsEntry: Record = {}; - for (const [key, value] of Object.entries(jsFiles)) { - jsEntry[`/_client${key}`] = value.slice(1); + for (const [key, value] of Object.entries(scripts)) { + scriptsEntry[`/_script${key}`] = value.slice(dirNames.src.base.length + 1); // remove "/src" } - return Object.assign(entryPoints, jsEntry); + return Object.assign(pages, scriptsEntry); }; diff --git a/packages/domco/src/plugin/configure-server/index.ts b/packages/domco/src/plugin/configure-server/index.ts index 4e40b49..4e4037e 100644 --- a/packages/domco/src/plugin/configure-server/index.ts +++ b/packages/domco/src/plugin/configure-server/index.ts @@ -1,14 +1,18 @@ -import { createAppDev } from "../../app/dev/index.js"; -import type { createApp as createAppType } from "../../app/index.js"; import { dirNames, fileNames } from "../../constants/index.js"; -import { createRequestListener } from "../../node/request-listener/index.js"; -import { serveStatic } from "../../node/serve-static/index.js"; -import type { Adapter, CreateAppOptions } from "../../types/public/index.js"; +import { nodeListener } from "../../listener/index.js"; +import type { Adapter, AppModule } from "../../types/index.js"; +import { findFiles } from "../../util/fs/index.js"; import path from "node:path"; import process from "node:process"; import url from "node:url"; import type { Plugin } from "vite"; +/** + * Configures the dev and preview server for SSR. + * + * @param adapter + * @returns Vite plugin + */ export const configureServerPlugin = (adapter?: Adapter): Plugin => { return { name: "domco:configure-server", @@ -18,7 +22,7 @@ export const configureServerPlugin = (adapter?: Adapter): Plugin => { // inject vite client to client entries, in case the script is added // without adding an html file. This is a easier than injecting a tag // into the html response, but will only work if a script is added. - if (id.includes(fileNames.client)) { + if (id.includes(fileNames.script)) { return { // put it at the end to not mess up line numbers code: `${code}\n\n// dev\nimport "/@vite/client";`, @@ -27,30 +31,25 @@ export const configureServerPlugin = (adapter?: Adapter): Plugin => { }, async configureServer(devServer) { - const sendFullReload = () => devServer.hot.send({ type: "full-reload" }); - - const listener = (filePath: string) => { - if (filePath.endsWith(fileNames.page)) { - sendFullReload(); - } - }; - - devServer.watcher.on("add", listener); - devServer.watcher.on("unlink", listener); - devServer.watcher.on("change", listener); - return async () => { - // POST MIDDLEWARE - const app = createAppDev({ - devServer, - middleware: adapter?.devMiddleware, - }); + for (const mw of adapter?.devMiddleware ?? []) { + devServer.middlewares.use(mw); + } devServer.middlewares.use(async (req, res, next) => { - createRequestListener( + nodeListener( // Copied from https://github.com/honojs/vite-plugins/blob/main/packages/dev-server/src/dev-server.ts async (request) => { - const response = await app.fetch(request); + const { handler } = (await devServer.ssrLoadModule( + path.join( + process.cwd(), + dirNames.src.base, + dirNames.src.server, + fileNames.app, + ), + )) as AppModule; + + const response = await handler(request); if (!(response instanceof Response)) throw response; @@ -80,9 +79,50 @@ export const configureServerPlugin = (adapter?: Adapter): Plugin => { }, async configurePreviewServer(previewServer) { - // import built app from dist - const createApp = ( - await import( + const serveDir = path.join(dirNames.out.base, dirNames.out.client.base); + + const htmlFiles = await findFiles({ + dir: serveDir, + checkEndings: ["index.html"], + }); + + // Rewrites static routes to HTML urls so Vite will serve the static file. + previewServer.middlewares.use(async (req, res, next) => { + let pathName = req.url; + + if (pathName && req.method === "GET") { + let trailingSlash = false; + + if (pathName !== "/" && pathName.endsWith("/")) { + // Remove the trailing slash on the temporary pathName since htmlFiles keys do not have it. + pathName = pathName.slice(0, -1); + trailingSlash = true; + } + + if (pathName in htmlFiles) { + if (trailingSlash) { + // redirect to path without trailing slash + res.writeHead(307, { location: pathName }); + return res.end(); + } + + // Rewrite the url so Vite serves the HTML file. + req.url = htmlFiles[pathName]?.slice(`/${serveDir}`.length); + } + } + + return next(); + }); + + return async () => { + // This must be post middleware or serve static will not work. + + for (const mw of adapter?.previewMiddleware ?? []) { + previewServer.middlewares.use(mw); + } + + // import from dist + const { handler } = (await import( url.pathToFileURL( path.join( process.cwd(), @@ -91,20 +131,10 @@ export const configureServerPlugin = (adapter?: Adapter): Plugin => { fileNames.out.entry.app, ), ).href - ) - ).createApp as typeof createAppType; + )) as AppModule; - const middleware: CreateAppOptions["middleware"] = [ - { path: "/*", handler: serveStatic }, - ]; - - if (adapter?.previewMiddleware) { - middleware.push(...adapter.previewMiddleware); - } - - const app = createApp({ middleware }); - - previewServer.middlewares.use(createRequestListener(app.fetch)); + previewServer.middlewares.use(nodeListener(handler)); + }; }, }; }; diff --git a/packages/domco/src/plugin/entry/index.ts b/packages/domco/src/plugin/entry/index.ts deleted file mode 100644 index 818dc6a..0000000 --- a/packages/domco/src/plugin/entry/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { routesId } from "../routes/index.js"; -import type { Plugin } from "vite"; - -/** ID of the entry point that exports `createApp`. */ -export const appId = "domco:app-entry"; - -export const entryPlugin = (): Plugin => { - const resolvedAppId = "\0" + appId; - - return { - name: "domco:entry", - - resolveId(id) { - if (id === appId) { - return resolvedAppId; - } - }, - - async load(id) { - if (id == resolvedAppId) { - // this entry provides an export of the built app - // user can create a separate module and import createApp - // to build their app if adapters do not suit their needs - return ` - export { createApp } from "domco/app"; - export { routes } from "${routesId}"; - `; - } - }, - }; -}; diff --git a/packages/domco/src/plugin/html/index.ts b/packages/domco/src/plugin/html/index.ts deleted file mode 100644 index e409f0d..0000000 --- a/packages/domco/src/plugin/html/index.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { setup, dirNames, fileNames } from "../../constants/index.js"; -import type { Routes } from "../../types/private/index.js"; -import { codeSize } from "../../util/code-size/index.js"; -import { - fileExists, - findFiles, - toAllScriptEndings, - toPosix, -} from "../../util/fs/index.js"; -import { getMaxLengths } from "../../util/get-max-lengths/index.js"; -import { style } from "../../util/style/index.js"; -import type { Hono } from "hono"; -import fs from "node:fs/promises"; -import path from "node:path"; -import process from "node:process"; -import url from "node:url"; -import type { HtmlTagDescriptor, Plugin } from "vite"; - -type StaticFile = { path: string; kB: string; gzip: string }; - -export const htmlPlugin = (): Plugin => { - let ssr: boolean | undefined; - - return { - name: "domco:html-prerender", - - config(_, { isSsrBuild }) { - ssr = isSsrBuild; - }, - - transformIndexHtml: { - // the modifications need to be applied `order: "pre"` in order for the linked - // assets to be updated afterwards - order: "pre", - async handler(html, ctx) { - let pathname: string; - - const dev = ctx.server?.config.command === "serve"; - - if (dev) { - // during dev, `ctx.path` is the whatever is passed into - // devServer.transformIndexHtml - pathname = ctx.path; - } else { - // during build, `ctx.path` is the path to the HTML relative to root - pathname = path.dirname(ctx.path); - } - - const getClientJsPath = async (pathname: string) => { - const filePath = path.join( - process.cwd(), - dirNames.src, - pathname, - fileNames.client, - ); - - for (const fileName of toAllScriptEndings(filePath)) { - if (await fileExists(fileName)) { - return ( - "/" + - toPosix( - path.relative( - path.join(process.cwd(), dirNames.src), - fileName, - ), - ) - ); - } - } - - return null; - }; - - const injectScript = await getClientJsPath(pathname); - - const tags: HtmlTagDescriptor[] = []; - - if (injectScript) { - tags.push({ - tag: "script", - attrs: { type: "module", src: injectScript }, - }); - } - - return { html, tags }; - }, - }, - - async writeBundle() { - if (ssr) { - const moved = await renameAndRemoveHtml(); - const generated = await generateStatic(); - - const staticFiles = [...moved, ...generated]; - - staticFiles.sort((a, b) => a.path.localeCompare(b.path)); - - if (staticFiles.length) { - console.log(style.bold("static")); - - const maxLengths = getMaxLengths(staticFiles); - - for (const file of staticFiles) { - const filePath = file.path.padEnd((maxLengths.path ?? 0) + 2); - const kB = file.kB.padStart(maxLengths.kB ?? 0) + " kB"; - const gzip = ` │ gzip: ${file.gzip.padStart(maxLengths.gzip ?? 0)} kB`; - - console.log(`${filePath}${style.dim(kB + gzip)}`); - } - - console.log(); - } - } - }, - }; -}; - -/** - * renames pages to `index.html`, deletes pages that have - * endpoints associated to not clash when serving static files - */ -const renameAndRemoveHtml = async () => { - const staticFiles: StaticFile[] = []; - - const pageFiles = await findFiles({ - dir: `${dirNames.out.base}/${dirNames.out.client.base}`, - checkEndings: [fileNames.page], - }); - - const serverFiles = await findFiles({ - dir: dirNames.src, - checkEndings: toAllScriptEndings(fileNames.server), - }); - - for (const [pagePath, filePath] of Object.entries(pageFiles)) { - // don't include pages that have a handler function, - // the html will be included in each route module instead to import - if (!Object.keys(serverFiles).includes(pagePath)) { - const finalPath = path.join( - process.cwd(), - path.dirname(filePath), - "index.html", - ); - - await fs.rename(path.join(process.cwd(), filePath), finalPath); - - const code = await fs.readFile(finalPath, "utf-8"); - - const outDir = path.join(dirNames.out.base, dirNames.out.client.base); - - const { kB, gzip } = codeSize(code); - - staticFiles.push({ - path: toPosix( - `${style.dim(outDir + "/")}${style.green(path.join(pagePath, "index.html").slice(1))}`, - ), - kB, - gzip, - }); - } else { - // delete the pages that are included in `routes` module - await fs.rm(path.join(process.cwd(), filePath), { recursive: true }); - } - } - - return staticFiles; -}; - -/** - * Creates a node server and requests pages for static prerender - * provided by the user. - * - * This server is only used at build time. - */ -const generateStatic = async () => { - const { createApp, routes } = (await import( - /* @vite-ignore */ - url.pathToFileURL( - path.join( - process.cwd(), - dirNames.out.base, - dirNames.out.ssr, - fileNames.out.entry.app, - ), - ).href - )) as { createApp: () => Hono; routes: Routes }; - - const app = createApp(); - - const staticFiles: StaticFile[] = []; - - for (let [routePath, { server: mod }] of Object.entries(routes)) { - if (mod && routePath !== setup) { - const { prerender } = mod; - if (prerender) { - const generate = async (routePath: string) => { - const res = await app.request(toPosix(routePath)); - - if (res.status === 404) { - throw new Error( - `Prerendering failed for path \`${routePath}\` | 404 - not found.`, - ); - } - - const code = await res.text(); - - const outDir = path.join(dirNames.out.base, dirNames.out.client.base); - - const outDirPath = path.join(process.cwd(), `${outDir}${routePath}`); - - await fs.mkdir(outDirPath, { recursive: true }); - await fs.writeFile(`${outDirPath}/index.html`, code, "utf-8"); - - const { kB, gzip } = codeSize(code); - - staticFiles.push({ - path: toPosix( - `${style.dim(outDir + "/")}${style.green( - routePath.slice(1) + - (routePath === "/" || routePath === "" ? "" : "/") + - "index.html", - )}`, - ), - kB, - gzip, - }); - }; - - if (prerender === true) { - await generate(routePath); - } else { - for (let staticPath of prerender) { - if (!staticPath.startsWith("/")) { - throw Error( - `Prerender path \`${staticPath}\` does not start with \`"/"\`.`, - ); - } - - if (routePath === "/") { - routePath = ""; - } - - if (staticPath === "/") { - staticPath = ""; - } - - await generate(`${routePath}${staticPath}`); - } - } - } - } - } - - return staticFiles; -}; diff --git a/packages/domco/src/plugin/index.ts b/packages/domco/src/plugin/index.ts index c862f8b..4f5a4a9 100644 --- a/packages/domco/src/plugin/index.ts +++ b/packages/domco/src/plugin/index.ts @@ -1,12 +1,10 @@ -import type { DomcoConfig } from "../types/public/index.js"; +import type { DomcoConfig } from "../types/index.js"; import { adapterPlugin } from "./adapter/index.js"; import { configPlugin } from "./config/index.js"; import { configureServerPlugin } from "./configure-server/index.js"; -import { entryPlugin } from "./entry/index.js"; -import { htmlPlugin } from "./html/index.js"; import { lifecyclePlugin } from "./lifecycle/index.js"; -import { manifestPlugin } from "./manifest/index.js"; -import { routesPlugin } from "./routes/index.js"; +import { pagePlugin } from "./page/index.js"; +import { scriptPlugin } from "./script/index.js"; import type { Plugin } from "vite"; /** @@ -33,14 +31,12 @@ export const domco = async ( ): Promise => { const adapter = await domcoConfig.adapter; - return [ - await configPlugin(domcoConfig), + return Promise.all([ + configPlugin(domcoConfig), configureServerPlugin(adapter), - htmlPlugin(), - await routesPlugin(), - manifestPlugin(), - entryPlugin(), - await adapterPlugin(adapter), - lifecyclePlugin(), - ]; + pagePlugin(), + scriptPlugin(), + lifecyclePlugin(adapter), + adapterPlugin(adapter), + ]); }; diff --git a/packages/domco/src/plugin/lifecycle/index.ts b/packages/domco/src/plugin/lifecycle/index.ts index f347c0d..2a85906 100644 --- a/packages/domco/src/plugin/lifecycle/index.ts +++ b/packages/domco/src/plugin/lifecycle/index.ts @@ -1,80 +1,267 @@ import { dirNames, fileNames } from "../../constants/index.js"; +import type { Adapter, AppModule, Handler } from "../../types/index.js"; import { codeSize } from "../../util/code-size/index.js"; +import { findFiles, toPosix } from "../../util/fs/index.js"; import { getMaxLengths } from "../../util/get-max-lengths/index.js"; +import { getTime } from "../../util/perf/index.js"; import { style } from "../../util/style/index.js"; import { version } from "../../version/index.js"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { performance } from "node:perf_hooks"; +import url from "node:url"; import { build, type Plugin } from "vite"; -export const lifecyclePlugin = (): Plugin => { +/** + * Runs logs and scripts throughout the build lifecycle. + * + * @param adapter + * @returns vite plugin + */ +export const lifecyclePlugin = (adapter?: Adapter): Plugin => { let ssr: boolean | undefined; + /** If prerender script should be run. */ + let shouldPrerender = false; + return { name: "domco:lifecycle", - async config(_, { isSsrBuild, command }) { + apply: "build", + async config(_config, { isSsrBuild }) { ssr = isSsrBuild; - if (command === "build" && !ssr) { - console.log(); - console.log(style.bold(`domco@${version}`)); - console.log(); + }, + + writeBundle(_options, bundle) { + if (ssr) { + const appChunk = bundle[fileNames.out.entry.app]; + + if (appChunk?.type === "chunk") { + // Only prerender if there's a named export "prerender" from app chunk. + // This prevents having to import the app if it is not needed for prerendering. + shouldPrerender = appChunk.exports.includes("prerender"); + } } }, - writeBundle(_, bundle) { + async closeBundle() { if (!ssr) { - // initiate server build - build({ + console.log(); + + // initiate SSR build + await build({ build: { ssr: true, }, }); + } else { + const tasks: Promise[] = [removeHtml()]; + + if (shouldPrerender) tasks.push(prerender()); + + await Promise.all(tasks); + + console.log(); + + if (adapter) await runAdapter(adapter); + + console.log( + style.italic( + style.dim( + "run `vite preview` to preview your app with Vite and Node.js.", + ), + ), + ); + console.log(); } + }, + }; +}; - const outDir = `${dirNames.out.base}/${ssr ? dirNames.out.ssr : dirNames.out.client.base}`; +const domcoTag = style.cyan(`domco v${version}`); - let info = Object.values(bundle).map((v) => { - const { fileName, code, source } = v as Record; +/** + * Removes all of the `+page.html` files from the build, since the app entry will + * contain what it needs from virtual module imports. + */ +const removeHtml = async () => { + const pageFiles = await findFiles({ + dir: `${dirNames.out.base}/${dirNames.out.client.base}`, + checkEndings: [fileNames.page], + }); - const { kB, gzip } = codeSize(code ?? source); + const promises = []; - const outDirStr = style.dim(outDir + "/"); + for (const filePath of Object.values(pageFiles)) { + promises.push( + fs.rm(path.join(process.cwd(), filePath), { recursive: true }), + ); + } - let fileNameStr: string; - if (fileName.endsWith("js")) fileNameStr = style.cyan(fileName); - else if (fileName.endsWith("css")) - fileNameStr = style.magenta(fileName); - else if (fileName.endsWith("html")) fileNameStr = style.green(fileName); - else fileNameStr = style.red(fileName); + await Promise.all(promises); +}; - return { - path: `${outDirStr}${fileNameStr}`, - kB, - gzip, - }; - }); +/** + * Executes `adapter.run` and logs. + * @param adapter + */ +const runAdapter = async (adapter: Adapter) => { + const adapterStart = performance.now(); - // html is bundled in routes so pages are deleted - info = info.filter((v) => !v.path.includes(fileNames.page)); + console.log( + `${domcoTag} ${style.green(`running ${adapter.name} adapter...`)}`, + ); - if (info.length) { - info.sort((a, b) => a.path.localeCompare(b.path)); + if (adapter.run) { + await adapter.run(); + } - console.log( - style.bold(ssr ? dirNames.out.ssr : dirNames.out.client.base), - ); + console.log(`${style.green("✓")} adapter executed.`); - const maxLengths = getMaxLengths(info); + console.log(style.dim(adapter.message)); - for (const file of info) { - const filePath = file.path.padEnd((maxLengths.path ?? 0) + 2); - const kB = file.kB.padStart(maxLengths.kB ?? 0) + " kB"; - const gzip = ssr - ? "" - : ` │ gzip: ${file.gzip.padStart(maxLengths.gzip ?? 0)} kB`; - console.log(`${filePath}${style.dim(kB + gzip)}`); - } + console.log( + style.green(`✓ ran in ${getTime(adapterStart, performance.now())}`), + ); - console.log(); - } - }, + console.log(); +}; + +type StaticFile = { path: string; kB: string; gzip: string; time: string }; + +/** Prerenders static routes provided by the user's `prerender` export. */ +const prerender = async () => { + console.log(); + + const prerenderStart = performance.now(); + + console.log(`${domcoTag} ${style.green("prerendering static pages...")}`); + + let { handler, prerender } = (await import( + /* @vite-ignore */ + url.pathToFileURL( + path.join( + // process.cwd(), + dirNames.out.base, + dirNames.out.ssr, + fileNames.out.entry.app, + ), + ).href + )) as AppModule; + + console.log( + style.dim( + `imported production app in ${getTime(prerenderStart, performance.now())}`, + ), + ); + + if (!prerender) return; + + const staticFilePromises: Promise[] = []; + + for (const staticPath of prerender) { + if (!staticPath.startsWith("/")) { + throw Error( + `Prerender path \`${staticPath}\` does not start with \`"/"\`.`, + ); + } + + staticFilePromises.push(generateRoute(staticPath, handler)); + } + + // Generate static files in parallel. + const staticResults = await Promise.allSettled(staticFilePromises); + + const staticFiles: StaticFile[] = []; + let failureReasons = ""; + + for (const result of staticResults) { + if (result.status === "fulfilled") { + staticFiles.push(result.value); + } else { + failureReasons += `${result.reason}\n`; + } + } + + if (failureReasons) { + throw new Error( + `The following errors occurred during prerendering:\n\n${failureReasons}`, + ); + } + + if (staticFiles?.length) { + console.log( + `${style.green("✓")} ${staticFiles.length} page${staticFiles.length > 1 ? "s" : ""} generated.`, + ); + + // Sort alphabetically for logs. + staticFiles.sort((a, b) => a.path.localeCompare(b.path)); + + const maxLengths = getMaxLengths(staticFiles); + + for (const file of staticFiles) { + const filePath = file.path.padEnd((maxLengths.path ?? 0) + 2); + const kB = file.kB.padStart(maxLengths.kB ?? 0) + " kB"; + const gzip = ` │ gzip: ${file.gzip.padStart(maxLengths.gzip ?? 0)} kB │ `; + const time = file.time.padStart(maxLengths.time ?? 0); + + console.log( + `${filePath}${style.dim(style.bold(kB))}${style.dim(gzip)}${style.dim(time)}`, + ); + } + + console.log( + style.green( + `✓ prerendered in ${getTime(prerenderStart, performance.now())}`, + ), + ); + } +}; + +/** + * Adding this as a separate function enables generating all of the pages + * in parallel. + * + * @param staticPath path to prerender + * @param handler request handler + * @returns Information about the prerendered files. + */ +const generateRoute = async ( + staticPath: string, + handler: Handler, +): Promise => { + const startTime = performance.now(); + + const res = await handler( + new Request(`http://0.0.0.0:4545${toPosix(staticPath)}`), + ); + + if (res.status === 404) { + throw new Error( + `Prerender failed for path \`${staticPath}\` | 404 - not found.`, + ); + } + + const code = await res.text(); + + const outDir = path.join(dirNames.out.base, dirNames.out.client.base); + + const outDirPath = path.join(process.cwd(), `${outDir}${staticPath}`); + + await fs.mkdir(outDirPath, { recursive: true }); + await fs.writeFile(`${outDirPath}/index.html`, code, "utf-8"); + + const { kB, gzip } = codeSize(code); + + return { + path: toPosix( + `${style.dim(outDir + "/")}${style.green( + staticPath.slice(1) + + (staticPath === "/" || staticPath === "" ? "" : "/") + + "index.html", + )}`, + ), + kB, + gzip, + time: getTime(startTime, performance.now()), }; }; diff --git a/packages/domco/src/plugin/manifest/index.ts b/packages/domco/src/plugin/manifest/index.ts deleted file mode 100644 index 0e51f6a..0000000 --- a/packages/domco/src/plugin/manifest/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { dirNames } from "../../constants/index.js"; -import fs from "node:fs/promises"; -import path from "node:path"; -import process from "node:process"; -import type { Plugin } from "vite"; - -export const manifestPlugin = (): Plugin => { - const manifestId = "domco:manifest"; - const resolvedManifestId = "\0" + manifestId; - - return { - name: manifestId, - apply: "build", - resolveId(id) { - if (id === manifestId) { - return resolvedManifestId; - } - }, - - async load(id) { - if (id === resolvedManifestId) { - const json = await fs.readFile( - path.join( - process.cwd(), - dirNames.out.base, - dirNames.out.client.base, - ".vite", - "manifest.json", - ), - "utf-8", - ); - - return `export const manifest = ${json};`; - } - }, - }; -}; diff --git a/packages/domco/src/plugin/manifest/types.d.ts b/packages/domco/src/plugin/manifest/types.d.ts deleted file mode 100644 index 70d45d1..0000000 --- a/packages/domco/src/plugin/manifest/types.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "domco:manifest" { - const manifest: import("vite").Manifest; - export { manifest }; -} diff --git a/packages/domco/src/plugin/page/index.ts b/packages/domco/src/plugin/page/index.ts new file mode 100644 index 0000000..38efb5c --- /dev/null +++ b/packages/domco/src/plugin/page/index.ts @@ -0,0 +1,90 @@ +import { dirNames, fileNames } from "../../constants/index.js"; +import fs from "node:fs/promises"; +import path from "node:path"; +import type { Plugin, ViteDevServer } from "vite"; + +/** + * Creates the `client:page` virtual module. + * + * @returns Vite plugin + */ +export const pagePlugin = (): Plugin => { + const pageId = "client:page"; + const resolvedPageId = `\0${pageId}`; + + let devServer: ViteDevServer; + + /** Watched page filePaths. */ + const watched = new Set(); + + return { + name: `domco:${pageId}`, + + configureServer(server) { + devServer = server; + }, + + resolveId(id) { + if (id.startsWith(pageId)) { + // Don't return the resolved id here, needs to be the full path. + return `\0${id}`; + } + }, + + async load(id, _options) { + if (id.startsWith(resolvedPageId)) { + const pathName = id.slice(resolvedPageId.length); + + let html: string; + + if (devServer) { + // read from src and transform + const filePath = path.join( + dirNames.src.base, + dirNames.src.client, + pathName, + fileNames.page, + ); + + if (!watched.has(filePath)) { + // add listeners if they are not already there + watched.add(filePath); + + devServer.watcher.on("all", (_event, fp) => { + if (fp.endsWith(filePath)) { + const mod = devServer.moduleGraph.getModuleById(id); + if (mod) devServer.reloadModule(mod); + } + }); + } + + try { + html = await fs.readFile(filePath, "utf-8"); + html = await devServer.transformIndexHtml(pathName, html); + } catch (error) { + html = ""; + + if (error instanceof Error) { + this.warn(error.message); + } else { + this.warn(`Could not read and transform \`${filePath}\``); + } + } + } else { + // read from client output + const filePath = path.join( + dirNames.out.base, + dirNames.out.client.base, + dirNames.src.client, + pathName, + fileNames.page, + ); + + html = await fs.readFile(filePath, "utf-8"); + } + + return `export const html = ${JSON.stringify(html)};`; + } + }, + }; +}; diff --git a/packages/domco/src/plugin/routes/index.ts b/packages/domco/src/plugin/routes/index.ts deleted file mode 100644 index aa0adca..0000000 --- a/packages/domco/src/plugin/routes/index.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { dirNames } from "../../constants/index.js"; -import { createRoutes } from "../../util/create-routes/index.js"; -import { toPosix } from "../../util/fs/index.js"; -import fs from "node:fs/promises"; -import path from "node:path"; -import type { Plugin } from "vite"; - -export const routesId = "domco:routes"; - -export const routesPlugin = async (): Promise => { - const resolvedRoutesId = "\0" + routesId; - - const routes = await createRoutes(); - - return { - name: routesId, - apply: "build", - resolveId(id) { - if (id === routesId) { - return resolvedRoutesId; - } - }, - - async load(id) { - if (id === resolvedRoutesId) { - let imp = ""; - let exp = "\nexport const routes = {\n"; - - let i = 0; - for (let [k, v] of Object.entries(routes)) { - exp += `\t"${k}": {`; - if (v.server) { - imp += `import * as route${i} from ".${toPosix(v.server)}";\n`; - exp += `\n\t\tserver: route${i},`; - i++; - } - if (v.client) { - // remove leading slash to resolve to a key of vite manifest - exp += `\n\t\tclient: "${toPosix(v.client).slice(1)}",`; - } - if (v.page) { - exp += `\n\t\tpage: \`${await fs.readFile( - path.join( - dirNames.out.base, - dirNames.out.client.base, - path.relative(`/${dirNames.src}`, v.page), - ), - )}\`,`; - } - exp += `\n\t},\n`; - } - - exp += "};\n"; - - return imp + exp; - } - }, - }; -}; diff --git a/packages/domco/src/plugin/routes/types.d.ts b/packages/domco/src/plugin/routes/types.d.ts deleted file mode 100644 index 20622f7..0000000 --- a/packages/domco/src/plugin/routes/types.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "domco:routes" { - const routes: import("../../types/private/index.ts").Routes; - export { routes }; -} diff --git a/packages/domco/src/plugin/script/index.ts b/packages/domco/src/plugin/script/index.ts new file mode 100644 index 0000000..4966c1c --- /dev/null +++ b/packages/domco/src/plugin/script/index.ts @@ -0,0 +1,156 @@ +import { dirNames, fileNames } from "../../constants/index.js"; +import { serializeTags, type TagDescriptor } from "../../injector/index.js"; +import { findFiles, toAllScriptEndings, toPosix } from "../../util/fs/index.js"; +import fs from "node:fs/promises"; +import path from "node:path"; +import type { Manifest, ManifestChunk, Plugin } from "vite"; + +/** + * Creates the `client:script` virtual module. + * + * @returns Vite plugin + */ +export const scriptPlugin = (): Plugin => { + const scriptId = "client:script"; + const resolvedScriptId = `\0${scriptId}`; + + let prod: boolean | undefined; + + return { + name: `domco:${scriptId}`, + config(_config, env) { + prod = env.mode === "production"; + }, + + resolveId(id) { + if (id.startsWith(scriptId)) { + // Don't return the resolved id here, needs to be the full path. + return `\0${id}`; + } + }, + + async load(id) { + if (id.startsWith(resolvedScriptId)) { + let pathName = id.slice(resolvedScriptId.length); + + // remove trailing slash + if (pathName.endsWith("/")) pathName = pathName.slice(0, -1); + + if (!pathName) pathName = "/"; + + const tags: TagDescriptor[] = []; + + if (!prod) { + const scriptFiles = await findFiles({ + dir: `${dirNames.src.base}/${dirNames.src.client}`, + checkEndings: toAllScriptEndings(fileNames.script), + }); + + // link the script from src + const src = scriptFiles[pathName]?.slice( + `/${dirNames.src.base}`.length, + ); + + if (!src) this.warn(`No client module found for ${pathName}`); + + tags.push({ + tag: "script", + attrs: { type: "module", src }, + }); + } else { + // read from manifest + const manifest: Manifest = JSON.parse( + await fs.readFile( + path.join( + dirNames.out.base, + dirNames.out.client.base, + ".vite", + "manifest.json", + ), + "utf-8", + ), + ); + + tags.push( + ...getTagsForEntry({ manifest, pathName, error: this.error }), + ); + } + + return `export const tags = ${JSON.stringify(serializeTags(tags))};`; + } + }, + }; +}; + +const getTagsForEntry = (options: { + manifest: Manifest; + + /** + * @example "/react" + */ + pathName: string; + + cssOnly?: boolean; + + error: (message: string) => never; +}) => { + const { manifest, pathName, cssOnly, error } = options; + + let chunk: ManifestChunk | undefined; + + // find chunk + for (const [id, value] of Object.entries(manifest)) { + if (chunk) break; + + const noEnding = toPosix( + path.join(dirNames.src.client, pathName, fileNames.script), + ); + + // id is like this: "client/react/+script.tsx" + // remove leading / , try all endings + for (const idPath of toAllScriptEndings(noEnding)) { + if (chunk) break; + + if (id === idPath) { + chunk = value; + } + } + } + + if (!chunk) { + error(`No tags found in manifest for \`${pathName}\``); + return []; + } + + const tags: TagDescriptor[] = []; + + if (!cssOnly) { + // push the entry file name + // not needed for `imports`, they are already linked in the code + tags.push({ + tag: "script", + attrs: { type: "module", src: `/${chunk.file}` }, + }); + } + + if (chunk.css) { + // need to also do this for `imports` since css does not actually link in the code + for (const cssFile of chunk.css) { + tags.push({ + tag: "link", + attrs: { rel: "stylesheet", href: `/${cssFile}` }, + }); + } + } + + if (chunk.imports) { + // recursively call on imports + for (const imp of chunk.imports) { + tags.push( + ...getTagsForEntry({ manifest, pathName: imp, cssOnly: true, error }), + ); + } + } + + return tags; +}; diff --git a/packages/domco/src/types/env.d.ts b/packages/domco/src/types/env.d.ts new file mode 100644 index 0000000..300a25d --- /dev/null +++ b/packages/domco/src/types/env.d.ts @@ -0,0 +1,25 @@ +declare module "client:page*" { + /** Transformed HTML page. */ + const html: string; + + export { html }; +} + +declare module "client:script*" { + /** + * Import the tags for a client entry point if you want to add client-side JS without adding an HTML page. + * + * **Development** + * + * HTML script tag for the client entry point. + * + * **Production** + * + * HTML `script` tags for the client script and `link` tags for any imported `.css` files. + * domco reads `dist/client/.vite/manifest.json` to find the hashed names after the client + * build has completed. + */ + const tags: string; + + export { tags }; +} diff --git a/packages/domco/src/types/global.d.ts b/packages/domco/src/types/global.d.ts deleted file mode 100644 index 5c1156d..0000000 --- a/packages/domco/src/types/global.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// -import type { DomcoContextVariableMap } from "../types/public/index.ts"; -import "hono"; - -declare module "hono" { - interface ContextVariableMap extends DomcoContextVariableMap {} -} diff --git a/packages/domco/src/types/helper/index.ts b/packages/domco/src/types/helper/index.ts index 09e15f1..cda192b 100644 --- a/packages/domco/src/types/helper/index.ts +++ b/packages/domco/src/types/helper/index.ts @@ -1,4 +1,2 @@ -export type PropertyType = T[K]; - /** Helper type for a type that could be a promise. */ export type MaybePromise = T | Promise; diff --git a/packages/domco/src/types/index.ts b/packages/domco/src/types/index.ts new file mode 100644 index 0000000..e5fd1fc --- /dev/null +++ b/packages/domco/src/types/index.ts @@ -0,0 +1,129 @@ +import type { MaybePromise } from "./helper/index.js"; +import type { SSRTarget, SSROptions, Connect } from "vite"; + +/** Exports from the SSR `app` entry point. */ +export type AppModule = { + handler: Handler; + prerender: Prerender; +}; + +/** + * Request handler, takes a web request and returns a web response. + * + * ```ts + * // src/server/+app.ts + * import type { Handler } from "domco"; + * + * export const handler: Handler = async (req) => { + * return new Response("Hello world"); + * }; + * ``` + */ +export type Handler = (req: Request) => MaybePromise; + +/** + * Paths to prerender at build time. + * + * @example + * + * ```ts + * // src/server/+app.ts + * import type { Prerender } from "domco"; + * + * export const prerender: Prerender = ["/", "/post-1", "/post-2"]; + * ``` + */ +export type Prerender = Array; + +/** Middleware used in the Vite server for dev and preview. */ +export type AdapterMiddleware = Connect.NextHandleFunction; + +/** A function that returns an additional entry point to include in the SSR build. */ +export type AdapterEntry = (AdapterEntryOptions: { + /** The app entry point to import `handler` from. */ + appId: string; +}) => { + /** + * The name of the entry point without extension. + * + * @example "main" + */ + id: string; + + /** Code for the entry point. */ + code: string; +}; + +/** A domco adapter that configures the build to a target production environment. */ +export type Adapter = { + /** The name of the adapter. */ + name: string; + + /** The script to run after Vite build is complete. */ + run?: () => any; + + /** Message to log when the build is complete. */ + message: string; + + /** Entry point for the server application. */ + entry: AdapterEntry; + + /** Passed into Vite `config.ssr.target`. */ + target?: SSRTarget; + + /** Passed into Vite `config.ssr.noExternal`. */ + noExternal?: SSROptions["noExternal"]; + + /** Middleware to apply in `dev` mode. */ + devMiddleware?: AdapterMiddleware[]; + + /** Middleware to apply in `preview` mode. */ + previewMiddleware?: AdapterMiddleware[]; +}; + +/** + * Use this type to create your own adapter. + * Pass any options for the adapter in as a generic. + */ +export type AdapterBuilder = ( + AdapterOptions?: AdapterOptions, +) => MaybePromise; + +/** + * domco Config + * + * Use if you want to create a separate object for your domco config. + * Pass the config into the `domco` vite plugin. + * + * @example + * + * ```ts + * // vite.config.ts + * import { defineConfig } from "vite"; + * import { domco, type DomcoConfig } from "domco"; + * + * const config: DomcoConfig = { + * // options... + * }; + * + * export default defineConfig({ + * plugins: [domco(config)], + * }); + * ``` + */ +export type DomcoConfig = { + /** + * domco adapter. + * + * Defaults to `undefined` - creates a `app` build only. + * + * @default undefined + * + * @example + * + * ```js + * import { adapter } from `"domco/adapter/...";` + * ``` + */ + adapter?: ReturnType; +}; diff --git a/packages/domco/src/types/private/index.ts b/packages/domco/src/types/private/index.ts deleted file mode 100644 index 0f884cb..0000000 --- a/packages/domco/src/types/private/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Prerender } from "../public/index.js"; -import type { Hono } from "hono"; - -/** - * Exports from server modules. - */ -export type ServerModule = { - prerender?: Prerender; - default?: Hono; -}; - -export type Route = { - server?: ServerModule; - page?: string; - client?: string; -}; - -export type Routes = Record; diff --git a/packages/domco/src/types/public/index.ts b/packages/domco/src/types/public/index.ts deleted file mode 100644 index 463ce86..0000000 --- a/packages/domco/src/types/public/index.ts +++ /dev/null @@ -1,287 +0,0 @@ -import type { MaybePromise } from "../helper/index.js"; -import type { Env, MiddlewareHandler } from "hono"; -import type { HonoOptions } from "hono/hono-base"; -import type { HtmlEscapedString } from "hono/utils/html"; -import type { SSRTarget, ViteDevServer, SSROptions } from "vite"; - -export type CreateAppMiddleware = { - /** Path to apply the middleware to. */ - path: string; - - /** The middleware to apply. */ - handler: MiddlewareHandler; -}; - -export type CreateAppOptions = { - /** Only used in `dev` to call the server. */ - devServer?: ViteDevServer; - - /** Passthrough options to the Hono app. */ - honoOptions?: HonoOptions; - - /** - * Middleware to be applied before any routes. Useful for adapters that need to - * inject middleware. - */ - middleware?: CreateAppMiddleware[]; -}; - -export type AdapterEntry = (AdapterEntryOptions: { - /** The app entrypoint to import `createApp` from. */ - appId: string; -}) => { - /** - * The name of the entrypoint without extension. - * - * @example "main" - */ - id: string; - - /** Code for the entrypoint. */ - code: string; -}; - -export type Adapter = { - /** The name of the adapter. */ - name: string; - - /** The script to run after Vite build is complete. */ - run?: () => any; - - /** Message to log when the build is complete. */ - message: string; - - /** Entry point for the server application. */ - entry: AdapterEntry; - - /** Passed into Vite `config.ssr.target`. */ - target?: SSRTarget; - - /** Passed into Vite `config.ssr.noExternal`. */ - noExternal?: SSROptions["noExternal"]; - - /** - * Middleware to apply in `dev` mode. - * For production middleware, export it from the adapter module, - * and then import into the entry point. - */ - devMiddleware?: CreateAppOptions["middleware"]; - - /** - * Middleware to apply in `preview` mode. - * For production middleware, export it from the adapter module, - * and then import into the entry point. - */ - previewMiddleware?: CreateAppOptions["middleware"]; -}; - -export type AdapterBuilder = ( - AdapterOptions?: AdapterOptions, -) => MaybePromise; - -/** - * domco Config - * - * Use if you want to create a separate object for your domco config. - * Pass the config into the `domco` vite plugin. - * - * @example - * - * ```ts - * // vite.config.ts - * import { defineConfig } from "vite"; - * import { domco, type DomcoConfig } from "domco"; - * - * const config: DomcoConfig = { - * // options... - * }; - * - * export default defineConfig({ - * plugins: [domco(config)], - * }); - * ``` - */ -export type DomcoConfig = { - /** - * domco adapter. - * - * Defaults to `undefined` - creates a `app` build only. - * - * @default undefined - * - * @example - * - * ```js - * import { adapter } from `"domco/adapter/...";` - * ``` - */ - adapter?: ReturnType; -}; - -/** - * Paths to prerender relative to the current route. - * - * @example - * - * ```ts - * // src/posts/+server.ts - * import type { Prerender } from "domco"; - * - * // prerender current route - * export const prerender: Prerender = true; - * - * // prerender multiple paths relative to the current route. - * export const prerender: Prerender = ["/", "/post-1", "/post-2"]; - * ``` - */ -export type Prerender = Array | true; - -export type Page = (routePath?: string) => string; - -export type Client = (routePath?: string) => HtmlEscapedString; - -export type Server = ( - pathname: string, - init?: RequestInit, -) => MaybePromise; - -/** - * Extend Hono's variable map with domco's. - * - * [Hono reference](https://hono.dev/docs/api/context#contextvariablemap) - * - * @example - * - * ```ts - * // src/global.d.ts - * /// - * import type { DomcoContextVariableMap } from "domco"; - * import "hono"; - * - * declare module "hono" { - * interface ContextVariableMap extends DomcoContextVariableMap {} - * } - * ``` - */ -export type DomcoContextVariableMap = { - /** - * Any `+page.html` within `src` can be accessed through the `page` context variable. - * This provides the HTML after processing by Vite. - * - * @param routePath - * - * If `routePath` is left `undefined`, the current route's `./+page.html` will be returned. - * Otherwise, you can use an alternative `routePath` to get a different page. - * - * @returns corresponding string of HTML - * - * @example - * - * ```ts - * // src/.../+server.(js,ts,jsx,tsx) - * import { Hono } from "hono"; - * - * app.get("/", (c) => { - * // gets `./+page.html` - * const page = c.var.page(); - * - * // gets `src/route/path/+page.html` - * const differentPage = c.var.page("/route/path") - * - * return c.html(page); - * }); - * - * export default app; - * ``` - */ - page: Page; - - /** - * The script tags for any `+client.(js,ts,jsx,tsx)` within `src` can be accessed through the `client` context variable. - * This is useful if you are wanting to include a client side script but are not using a `page` file for your HTML, since - * the names of these tags will be different after your client application has been built. - * - * For example, if you are using JSX to render the markup for your application you can utilize the `client` variable to include - * client side scripts. - * - * Also, the link tags for any `css` imported into the script will be included. - * - * In development, the tags will link directly to the script. - * - * In production, domco will read `dist/client/.vite/manifest.json` and use the hashed file names generated from the build. - * - * @param routePath - * - * If `routePath` is left `undefined`, the current route's `./+client.(js,ts,jsx,tsx)` will be returned. - * Otherwise, you can use an alternative `routePath` to get a different route's tags. - * - * @returns the raw script tags to use the client side assets in a response. - * - * @example - * - * ```tsx - * // HTML example - * // src/.../+server.(js,ts,jsx,tsx) - * - * import { Hono } from "hono"; - * import { html } from "hono/html"; - * - * const app = new Hono(); - * - * app.get("/", (c) => { - * // gets `./+client.(js,ts,jsx,tsx)` - * const tags = c.var.client(); - * - * // gets `src/route/path/+client.(js,ts,jsx,tsx)` - * const differentTags = c.var.client("/route/path") - * - * return c.html(html` - * ${tags} - *

Partial with client side script

- * `); - * }); - * - * export default app; - * - * - * // JSX example - * - * app.get("/", (c) => { - * return c.html( - * <> - * {c.var.client()} - *

Partial with client side script

- * - * `); - * }); - * ``` - */ - client: Client; - - /** - * [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) with the origin set, pass in the local path instead of the entire `url`. - * - * @param pathname the local path to request - * @param init [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) - * @returns [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) - * @example - * - * ```ts - * // src/.../+server.(js,ts,jsx,tsx) - * import { Hono } from "hono"; - * - * const app = new Hono(); - * - * app.get("/", (c) => { - * // dev: fetch("http://localhost:5173/route/path") - * // prod: fetch("https://example.com/route/path") - * const res = await c.var.server("/route/path") - * - * // ... - * }); - * - * export default app; - * ``` - */ - server: Server; -}; diff --git a/packages/domco/src/util/code-size/index.ts b/packages/domco/src/util/code-size/index.ts index cb53408..8695c31 100644 --- a/packages/domco/src/util/code-size/index.ts +++ b/packages/domco/src/util/code-size/index.ts @@ -1,9 +1,15 @@ import zlib from "zlib"; -export const codeSize = (s: string) => { - const kB = (Buffer.byteLength(s ?? "", "utf-8") / 1000).toFixed(2); +/** + * Gets the size and gzip size of a string. + * + * @param code The raw text. + * @returns The size and gzip size of the file in kB + */ +export const codeSize = (code: string) => { + const kB = (Buffer.byteLength(code ?? "", "utf-8") / 1000).toFixed(2); - const buffer = Buffer.from(s, "utf-8"); + const buffer = Buffer.from(code, "utf-8"); const gzipBuffer = zlib.gzipSync(buffer); const gzip = (gzipBuffer.length / 1000).toFixed(2); diff --git a/packages/domco/src/util/create-routes/index.ts b/packages/domco/src/util/create-routes/index.ts deleted file mode 100644 index db0f4f5..0000000 --- a/packages/domco/src/util/create-routes/index.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { setup, dirNames, fileNames } from "../../constants/index.js"; -import { - fileExists, - findFiles, - toAllScriptEndings, - toPosix, -} from "../fs/index.js"; -import path from "node:path"; -import process from "node:process"; - -/** - * @returns keys are routes and values are RouteModules - */ -export const createRoutes = async () => { - const dir = dirNames.src; - - const serverFiles = await findFiles({ - dir, - checkEndings: toAllScriptEndings(fileNames.server), - }); - - // add setup - for (const fileName of toAllScriptEndings(fileNames.setup)) { - const filePath = path.join(process.cwd(), dirNames.src, fileName); - if (await fileExists(filePath)) { - serverFiles[setup] = toPosix( - "/" + path.relative(process.cwd(), filePath), - ); - break; - } - } - - const clientFiles = await findFiles({ - dir, - checkEndings: toAllScriptEndings(fileNames.client), - }); - - const htmlFiles = await findFiles({ - dir, - checkEndings: [fileNames.page], - }); - - const routes: { - [routeId: string]: { page?: string; server?: string; client?: string }; - } = {}; - - // create routes for both modules and html - // need to also do html in dev to serve the static file since - // vite middleware will not serve index.html without appType="mpa" - for (const routeId in serverFiles) { - routes[routeId] = { - server: serverFiles[routeId], - }; - } - - for (const routeId in htmlFiles) { - const html = htmlFiles[routeId]; - - if (routes[routeId]) { - routes[routeId].page = html; - } else { - routes[routeId] = { page: html }; - } - } - - for (const routeId in clientFiles) { - const client = clientFiles[routeId] - ? `/${toPosix(path.relative("/" + dirNames.src, clientFiles[routeId]))}` - : undefined; - - if (routes[routeId]) { - routes[routeId].client = client; - } else { - routes[routeId] = { client }; - } - } - - return routes; -}; diff --git a/packages/domco/src/util/fs/index.ts b/packages/domco/src/util/fs/index.ts index 90a19d0..cbc09f0 100644 --- a/packages/domco/src/util/fs/index.ts +++ b/packages/domco/src/util/fs/index.ts @@ -43,16 +43,17 @@ export const findFiles = async (options: { withFileTypes: true, }); + const subDirPromises: Promise>[] = []; + for (const file of files) { if (file.isDirectory()) { - // recursively run again - const subDirPaths = await findFiles({ - dir: path.join(dir, file.name), - checkEndings, - root, - }); - - Object.assign(paths, subDirPaths); + subDirPromises.push( + findFiles({ + dir: path.join(dir, file.name), + checkEndings, + root, + }), + ); } else if (checkEnding({ checkEndings, fileName: file.name })) { const relativePath = path.relative(root, dir); @@ -62,6 +63,12 @@ export const findFiles = async (options: { } } + const subDirPaths = await Promise.all(subDirPromises); + + for (const sdp of subDirPaths) { + Object.assign(paths, sdp); + } + return paths; }; @@ -77,7 +84,7 @@ const checkEnding = (options: { checkEndings: string[]; fileName: string }) => { /** * @param filePath - * @returns true if the file exists + * @returns `true` if the file exists */ export const fileExists = async (filePath: PathLike) => { try { @@ -88,6 +95,7 @@ export const fileExists = async (filePath: PathLike) => { } }; +/** Replace all forward slashes with back slashes. */ export const toPosix = (s: string) => s.replaceAll("\\", "/"); /** @@ -109,8 +117,9 @@ export const toAllScriptEndings = (s: string) => { export const clearDir = async (dir: string) => { if (await fileExists(dir)) { await fs.rm(dir, { recursive: true }); - await fs.mkdir(dir, { recursive: true }); } + + await fs.mkdir(dir, { recursive: true }); }; /** @@ -118,7 +127,7 @@ export const clearDir = async (dir: string) => { * @param outDir target directory */ export const copyClient = async (outDir: string) => { - await fs.cp(path.join(dirNames.out.base, dirNames.out.client.base), outDir, { + return fs.cp(path.join(dirNames.out.base, dirNames.out.client.base), outDir, { recursive: true, errorOnExist: false, }); @@ -129,7 +138,7 @@ export const copyClient = async (outDir: string) => { * @param outDir target directory */ export const copyServer = async (outDir: string) => { - await fs.cp(path.join(dirNames.out.base, dirNames.out.ssr), outDir, { + return fs.cp(path.join(dirNames.out.base, dirNames.out.ssr), outDir, { recursive: true, errorOnExist: false, }); diff --git a/packages/domco/src/util/perf/index.ts b/packages/domco/src/util/perf/index.ts new file mode 100644 index 0000000..0efc52d --- /dev/null +++ b/packages/domco/src/util/perf/index.ts @@ -0,0 +1,13 @@ +/** + * @param start time in ms + * @param end time in ms + * @returns formatted string ms/s + */ +export const getTime = (start: number, end: number) => { + const ms = end - start; + if (ms > 999) { + return `${(ms / 1000).toFixed(2)}s`; + } else { + return `${Math.ceil(ms)}ms`; + } +};