diff --git a/README.md b/README.md new file mode 100644 index 0000000..63baa05 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +Generated with [vike.dev/new](https://vike.dev/new) ([version 429](https://www.npmjs.com/package/create-vike/v/0.0.429)) using this command: + +```sh +bun create vike@latest --solid --tailwindcss --authjs --telefunc --fastify --google-analytics --eslint --prettier --biome +``` + +## Contents + +* [`/pages/+config.ts`](#pagesconfigts) +* [Routing](#routing) +* [`/pages/_error/+Page.jsx`](#pages_errorpagejsx) +* [`/pages/+onPageTransitionStart.ts` and `/pages/+onPageTransitionEnd.ts`](#pagesonpagetransitionstartts-and-pagesonpagetransitionendts) +* [SSR](#ssr) +* [HTML Streaming](#html-streaming) + +This app is ready to start. It's powered by [Vike](https://vike.dev) and [SolidJS](https://www.solidjs.com/guides/getting-started). + +### `/pages/+config.ts` + +Such `+` files are [the interface](https://vike.dev/config) between Vike and your code. It defines: + +* A default [`` component](https://vike.dev/Layout) (that wraps your [`` components](https://vike.dev/Page)). +* A default [`title`](https://vike.dev/title). +* Global [`` tags](https://vike.dev/head-tags). + +### Routing + +[Vike's built-in router](https://vike.dev/routing) lets you choose between: + +* [Filesystem Routing](https://vike.dev/filesystem-routing) (the URL of a page is determined based on where its `+Page.jsx` file is located on the filesystem) +* [Route Strings](https://vike.dev/route-string) +* [Route Functions](https://vike.dev/route-function) + +### `/pages/_error/+Page.jsx` + +The [error page](https://vike.dev/error-page) which is rendered when errors occur. + +### `/pages/+onPageTransitionStart.ts` and `/pages/+onPageTransitionEnd.ts` + +The [`onPageTransitionStart()` hook](https://vike.dev/onPageTransitionStart), together with [`onPageTransitionEnd()`](https://vike.dev/onPageTransitionEnd), enables you to implement page transition animations. + +### SSR + +SSR is enabled by default. You can [disable it](https://vike.dev/ssr) for all your pages or only for some pages. + +### HTML Streaming + +You can enable/disable [HTML streaming](https://vike.dev/stream) for all your pages, or only for some pages while still using it for others. + diff --git a/app/.env b/app/.env new file mode 100644 index 0000000..94e7623 --- /dev/null +++ b/app/.env @@ -0,0 +1,4 @@ +# Google Analytics + +# See the documentation https://support.google.com/analytics/answer/9304153?hl=en#zippy=%2Cweb +PUBLIC_ENV__GOOGLE_ANALYTICS="G-XXXXXXXXXX" diff --git a/app/.prettierrc b/app/.prettierrc new file mode 100644 index 0000000..963354f --- /dev/null +++ b/app/.prettierrc @@ -0,0 +1,3 @@ +{ + "printWidth": 120 +} diff --git a/app/assets/logo.svg b/app/assets/logo.svg new file mode 100644 index 0000000..0fb65c0 --- /dev/null +++ b/app/assets/logo.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/biome.json b/app/biome.json new file mode 100644 index 0000000..d03d259 --- /dev/null +++ b/app/biome.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "files": { + "ignore": ["dist/**", "*.js", "*.cjs", "*.mjs", "*.spec.ts"] + } +} diff --git a/app/components/Link.tsx b/app/components/Link.tsx new file mode 100644 index 0000000..2e303dc --- /dev/null +++ b/app/components/Link.tsx @@ -0,0 +1,14 @@ +import { createMemo } from "solid-js"; +import { usePageContext } from "vike-solid/usePageContext"; + +export function Link(props: { href: string; children: string }) { + const pageContext = usePageContext(); + const isActive = createMemo(() => + props.href === "/" ? pageContext.urlPathname === props.href : pageContext.urlPathname.startsWith(props.href), + ); + return ( + + {props.children} + + ); +} diff --git a/app/database/todoItems.ts b/app/database/todoItems.ts new file mode 100644 index 0000000..5404680 --- /dev/null +++ b/app/database/todoItems.ts @@ -0,0 +1,17 @@ +interface TodoItem { + text: string; +} + +const todosDefault = [{ text: "Buy milk" }, { text: "Buy strawberries" }]; + +const database = + // We create an in-memory database. + // - We use globalThis so that the database isn't reset upon HMR. + // - The database is reset when restarting the server, use a proper database (SQLite/PostgreSQL/...) if you want persistent data. + // biome-ignore lint: + ((globalThis as unknown as { __database: { todos: TodoItem[] } }).__database ??= { todos: todosDefault }); + +const { todos } = database; + +export { todos }; +export type { TodoItem }; diff --git a/app/eslint.config.ts b/app/eslint.config.ts new file mode 100644 index 0000000..4176eb9 --- /dev/null +++ b/app/eslint.config.ts @@ -0,0 +1,56 @@ +import eslint from "@eslint/js"; +import prettier from "eslint-plugin-prettier/recommended"; +import solid from "eslint-plugin-solid/configs/typescript"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { + ignores: [ + "dist/*", + // Temporary compiled files + "**/*.ts.build-*.mjs", + + // JS files at the root of the project + "*.js", + "*.cjs", + "*.mjs", + ], + }, + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + parserOptions: { + warnOnUnsupportedTypeScriptVersion: false, + sourceType: "module", + ecmaVersion: "latest", + }, + }, + }, + { + rules: { + "@typescript-eslint/no-unused-vars": [ + 1, + { + argsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/no-namespace": 0, + }, + }, + + { + files: ["**/*.{ts,tsx,js,jsx}"], + ...solid, + languageOptions: { + parser: tseslint.parser, + globals: { + ...globals.serviceworker, + ...globals.browser, + }, + }, + }, + + prettier, +); diff --git a/app/fastify-entry.ts b/app/fastify-entry.ts new file mode 100644 index 0000000..4dc9cea --- /dev/null +++ b/app/fastify-entry.ts @@ -0,0 +1,76 @@ +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { authjsHandler, authjsSessionMiddleware } from "./server/authjs-handler"; + +import { vikeHandler } from "./server/vike-handler"; +import { telefuncHandler } from "./server/telefunc-handler"; +import Fastify from "fastify"; +import { createHandler, createMiddleware } from "@universal-middleware/fastify"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const root = __dirname; +const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; +const hmrPort = process.env.HMR_PORT ? parseInt(process.env.HMR_PORT, 10) : 24678; + +async function startServer() { + const app = Fastify(); + + // Avoid pre-parsing body, otherwise it will cause issue with universal handlers + // This will probably change in the future though, you can follow https://github.com/magne4000/universal-middleware for updates + app.removeAllContentTypeParsers(); + app.addContentTypeParser("*", function (_request, _payload, done) { + done(null, ""); + }); + + await app.register(await import("@fastify/middie")); + + if (process.env.NODE_ENV === "production") { + await app.register(await import("@fastify/static"), { + root: `${root}/dist/client`, + wildcard: false, + }); + } else { + // Instantiate Vite's development server and integrate its middleware to our server. + // ⚠️ We should instantiate it *only* in development. (It isn't needed in production + // and would unnecessarily bloat our server in production.) + const vite = await import("vite"); + const viteDevMiddleware = ( + await vite.createServer({ + root, + server: { middlewareMode: true, hmr: { port: hmrPort } }, + }) + ).middlewares; + app.use(viteDevMiddleware); + } + + await app.register(createMiddleware(authjsSessionMiddleware)()); + + /** + * Auth.js route + * @link {@see https://authjs.dev/getting-started/installation} + **/ + app.all("/api/auth/*", createHandler(authjsHandler)()); + + app.post<{ Body: string }>("/_telefunc", createHandler(telefuncHandler)()); + + /** + * Vike route + * + * @link {@see https://vike.dev} + **/ + app.all("/*", createHandler(vikeHandler)()); + + return app; +} + +const app = await startServer(); + +app.listen( + { + port: port, + }, + () => { + console.log(`Server listening on http://localhost:${port}`); + }, +); diff --git a/app/global.d.ts b/app/global.d.ts new file mode 100644 index 0000000..8cac553 --- /dev/null +++ b/app/global.d.ts @@ -0,0 +1,12 @@ +import { Session } from "@auth/core/types"; + +declare global { + namespace Vike { + interface PageContext { + session?: Session | null; + } + } +} + +// biome-ignore lint/complexity/noUselessEmptyExport: ensure that the file is considered as a module +export {}; diff --git a/app/layouts/LayoutDefault.tsx b/app/layouts/LayoutDefault.tsx new file mode 100644 index 0000000..b3e3aa9 --- /dev/null +++ b/app/layouts/LayoutDefault.tsx @@ -0,0 +1,50 @@ +import "./style.css"; + +import "./tailwind.css"; + +import type { JSX } from "solid-js"; +import logoUrl from "../assets/logo.svg"; +import { Link } from "../components/Link.js"; + +export default function LayoutDefault(props: { children?: JSX.Element }) { + return ( +
+ + + Welcome + Todo + Data Fetching + {""} + + {props.children} +
+ ); +} + +function Sidebar(props: { children: JSX.Element }) { + return ( + + ); +} + +function Content(props: { children: JSX.Element }) { + return ( +
+
+ {props.children} +
+
+ ); +} + +function Logo() { + return ( +
+ + logo + +
+ ); +} diff --git a/app/layouts/style.css b/app/layouts/style.css new file mode 100644 index 0000000..c5a3d28 --- /dev/null +++ b/app/layouts/style.css @@ -0,0 +1,29 @@ +/* Links */ +a { + text-decoration: none; +} +#sidebar a { + padding: 2px 10px; + margin-left: -10px; +} +#sidebar a.is-active { + background-color: #eee; +} + +/* Reset */ +body { + margin: 0; + font-family: sans-serif; +} +* { + box-sizing: border-box; +} + +/* Page Transition Animation */ +#page-content { + opacity: 1; + transition: opacity 0.3s ease-in-out; +} +body.page-is-transitioning #page-content { + opacity: 0; +} diff --git a/app/layouts/tailwind.css b/app/layouts/tailwind.css new file mode 100644 index 0000000..f1d8c73 --- /dev/null +++ b/app/layouts/tailwind.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/app/package.json b/app/package.json new file mode 100644 index 0000000..74aa314 --- /dev/null +++ b/app/package.json @@ -0,0 +1,40 @@ +{ + "scripts": { + "dev": "tsx ./fastify-entry.ts", + "build": "vike build", + "preview": "cross-env NODE_ENV=production tsx ./fastify-entry.ts", + "lint": "biome lint --write .", + "format": "biome format --write ." + }, + "dependencies": { + "vike": "^0.4.228", + "@auth/core": "^0.38.0", + "@universal-middleware/core": "^0.4.7", + "@fastify/middie": "^9.0.3", + "@fastify/static": "^8.1.1", + "@universal-middleware/fastify": "^0.5.16", + "fastify": "^5.3.0", + "solid-js": "^1.9.5", + "vike-solid": "^0.7.9", + "telefunc": "^0.2.3" + }, + "devDependencies": { + "typescript": "^5.8.3", + "vite": "^6.2.6", + "@biomejs/biome": "1.9.4", + "eslint": "^9.24.0", + "@eslint/js": "^9.24.0", + "typescript-eslint": "^8.29.1", + "globals": "^16.0.0", + "eslint-plugin-prettier": "^5.2.6", + "eslint-config-prettier": "^10.1.2", + "eslint-plugin-solid": "^0.14.5", + "@types/node": "^18.19.86", + "tsx": "^4.19.3", + "cross-env": "^7.0.3", + "prettier": "^3.5.3", + "tailwindcss": "^4.1.3", + "@tailwindcss/vite": "^4.1.3" + }, + "type": "module" +} \ No newline at end of file diff --git a/app/pages/+Head.tsx b/app/pages/+Head.tsx new file mode 100644 index 0000000..c6de5bb --- /dev/null +++ b/app/pages/+Head.tsx @@ -0,0 +1,21 @@ +/* eslint-disable solid/no-innerhtml */ + +// https://vike.dev/Head + +export default function HeadDefault() { + return ( + <> +