scaffold Vike app with Bati
commit
9916e95de0
@ -0,0 +1,142 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# Cloudflare
|
||||
.wrangler/
|
||||
|
||||
# Vercel
|
||||
.vercel/
|
||||
|
||||
# Sentry Vite Plugin
|
||||
.env.sentry-build-plugin
|
||||
|
||||
# aws-cdk
|
||||
.cdk.staging
|
||||
cdk.out
|
@ -0,0 +1,68 @@
|
||||
Generated with [vike.dev/new](https://vike.dev/new) ([version 450](https://www.npmjs.com/package/create-vike/v/0.0.450)) using this command:
|
||||
|
||||
```sh
|
||||
pnpm create vike@latest --react --compiled-css --mantine --authjs --trpc --hono --cloudflare --biome
|
||||
```
|
||||
|
||||
## Contents
|
||||
|
||||
* [React](#react)
|
||||
|
||||
* [`/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)
|
||||
|
||||
* [Mantine](#mantine)
|
||||
|
||||
## React
|
||||
|
||||
This app is ready to start. It's powered by [Vike](https://vike.dev) and [React](https://react.dev/learn).
|
||||
|
||||
### `/pages/+config.ts`
|
||||
|
||||
Such `+` files are [the interface](https://vike.dev/config) between Vike and your code. It defines:
|
||||
|
||||
* A default [`<Layout>` component](https://vike.dev/Layout) (that wraps your [`<Page>` components](https://vike.dev/Page)).
|
||||
* A default [`title`](https://vike.dev/title).
|
||||
* Global [`<head>` 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.
|
||||
|
||||
## Mantine
|
||||
|
||||
This is a boilerplate for Mantine based on the [Getting Started](https://mantine.dev/docs/getting-started/) guide.
|
||||
|
||||
The following Packages are installed:
|
||||
|
||||
* `@mantine/hooks` Hooks for state and UI management
|
||||
* `@mantine/core` Core components library: inputs, buttons, overlays, etc.
|
||||
|
||||
If you add more packages, make sure to update the `layouts/LayoutDefault.tsx` file to include the required CSSs.
|
||||
|
||||
The theme is defined in `layouts/theme.ts`.
|
||||
|
@ -0,0 +1,67 @@
|
||||
<svg class="hammer" width="41.217" height="41.217" version="1.1" viewBox="-50 -50 41.217 41.217" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<mask id="mask111">
|
||||
<rect x="-19.21" y="-25.7" width="46.217" height="41.217" fill="url(#linearGradient115)"/>
|
||||
</mask>
|
||||
<linearGradient id="linearGradient115" x1="-25.395" x2="-25.395" y1="-9.3005" y2="-18.03" gradientTransform="matrix(1.0589 0 0 .94436 30.79 24.3)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0"/>
|
||||
<stop stop-color="#fff" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(-33.29,-24.3)" mask="url(#mask111)">
|
||||
<g stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m-8.511-10.449 1.126 4.064 2.707-2.765z" fill="#ababab"/>
|
||||
<path d="m-2.273-24.496-6.238 14.047 3.833 1.299 6.238-14.048z" fill="#949494"/>
|
||||
<path d="m-2.273-24.496 3.465-1.204.368 2.502z" fill="#ababab"/>
|
||||
<path d="m17.511 4.674-2.707 2.766-22.189-13.825 2.707-2.765z" fill="#949494"/>
|
||||
</g>
|
||||
<g stroke="#878787">
|
||||
<path d="m-9.045 20.369-1.169 2.634" stroke-width="9.6"/>
|
||||
<path d="m-12.418 23.191c-1.85-1.153-2.326-2.132-1.086-2.238 1.239-.106 3.642.709 5.493 1.862s2.326 2.132 1.087 2.238c-1.24.106-3.643-.709-5.494-1.862" fill="#878787" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="m-11.248 20.557c1.851 1.153 4.254 1.968 5.493 1.862 1.24-.106.764-1.085-1.086-2.238-1.851-1.153-4.254-1.968-5.494-1.862-1.239.106-.764 1.085 1.087 2.238" fill="#878787" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m-16.71-9.748 8.199-.701 1.126 4.064-8.199.701z" fill="#949494"/>
|
||||
<path d="m23.749-9.373-6.238 14.047-22.189-13.824 6.238-14.048z" fill="#757575"/>
|
||||
<path d="m10.271-16.073 3.751 3.534c.062.058.083.156.052.238l-1.95 5.128c-.046.121-.18.153-.268.065l-1.024-1.03c-.095-.096-.242-.048-.275.091l-.516 2.152c-.034.145-.191.19-.284.082 0 0-.606-.696-.606-.696-.094-.108-.25-.063-.285.082l-.803 3.384c-.05.212-.317.178-.336-.043l-.014-.147s.058-9.892.058-9.892c.001-.165.165-.253.277-.148l1.077 1.009c.101.095.25.034.274-.111l.597-3.587c.025-.146.174-.206.275-.111z" fill="#fbbf28" stroke="#fbbf28" stroke-width=".6"/>
|
||||
</g>
|
||||
<g stroke="#808080">
|
||||
<path d="m-8.362 18.833-.39.878" stroke-width="9.1"/>
|
||||
<path d="m-10.956 19.899c-1.85-1.153-2.326-2.132-1.086-2.238 1.239-.106 3.642.708 5.493 1.861s2.326 2.132 1.087 2.238c-1.24.106-3.643-.708-5.494-1.861" fill="#808080" stroke-linecap="round" stroke-linejoin="round" stroke-width=".5"/>
|
||||
<path d="m-10.566 19.021c1.851 1.153 4.254 1.967 5.494 1.861 1.239-.106.764-1.085-1.087-2.238s-4.254-1.967-5.494-1.861c-1.239.106-.764 1.085 1.087 2.238" fill="#808080" stroke-linecap="round" stroke-linejoin="round" stroke-width=".5"/>
|
||||
</g>
|
||||
<path d="m-16.71-9.748 8.199-.701 6.238-14.047-8.199.701z" fill="#757575" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="m-1.754 3.951-6.511 14.662" stroke="#91512b" stroke-width="8.6"/>
|
||||
<g stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m-10.468 18.801c-1.851-1.153-2.327-2.132-1.087-2.238 1.239-.106 3.643.709 5.493 1.862 1.851 1.153 2.327 2.132 1.087 2.238-1.239.106-3.643-.708-5.493-1.862" fill="#91512b"/>
|
||||
<path d="m-3.958 4.139c1.851 1.153 4.254 1.968 5.494 1.862 1.239-.106.764-1.086-1.087-2.239s-4.254-1.967-5.493-1.861c-1.24.106-.764 1.085 1.086 2.238" fill="#91512b"/>
|
||||
<path d="m1.192-25.7.368 2.502 22.189 13.825-.368-2.503z" fill="#949494"/>
|
||||
<path d="m-10.472-23.795 8.199-.701 3.465-1.204-8.199.701z" fill="#949494"/>
|
||||
</g>
|
||||
<g stroke="#6e6e6e">
|
||||
<path d="m-.487 1.097-1.17 2.634" stroke-width="9.1"/>
|
||||
<path d="m-3.86 3.92c-1.851-1.153-2.326-2.132-1.087-2.238s3.643.708 5.493 1.861c1.851 1.153 2.327 2.132 1.087 2.238-1.239.106-3.643-.708-5.493-1.861" fill="#6e6e6e" stroke-linecap="round" stroke-linejoin="round" stroke-width=".5"/>
|
||||
<path d="m-2.691 1.286c1.851 1.153 4.254 1.967 5.494 1.861 1.239-.106.764-1.085-1.087-2.238s-4.254-1.967-5.493-1.861c-1.24.106-.764 1.085 1.086 2.238" fill="#6e6e6e" stroke-linecap="round" stroke-linejoin="round" stroke-width=".5"/>
|
||||
</g>
|
||||
<g stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m18.269 6.236-3.465 1.204 2.707-2.766z" fill="#ababab"/>
|
||||
<path d="m14.804 7.44-8.199.701-22.189-13.825 8.199-.701z" fill="#757575"/>
|
||||
<path d="m-16.71-9.748 1.126 4.064-.367-2.502z" fill="#ababab"/>
|
||||
<path d="m24.507-7.812-6.238 14.048-.758-1.562 6.238-14.047z" fill="#949494"/>
|
||||
<path d="m-10.472-23.795-6.238 14.047.759 1.562 6.237-14.048z" fill="#949494"/>
|
||||
<path d="m24.507-7.812-1.126-4.064.368 2.503z" fill="#ababab"/>
|
||||
<path d="m23.381-11.876-8.199.701-22.189-13.824 8.199-.701z" fill="#757575"/>
|
||||
<path d="m-10.472-23.795 3.465-1.204-2.707 2.765z" fill="#ababab"/>
|
||||
<path d="m18.269 6.236-8.199.701-3.465 1.204 8.199-.701z" fill="#949494"/>
|
||||
<path d="m-15.951-8.186.367 2.502 22.189 13.825-.367-2.503z" fill="#949494"/>
|
||||
<path d="m18.269 6.236-8.199.701 6.238-14.048 8.199-.701z" fill="#757575"/>
|
||||
<path d="m-9.714-22.234-6.237 14.048 22.189 13.824 6.237-14.047z" fill="#757575"/>
|
||||
<path d="m2.545-12.79-4.583-1.659c-.076-.027-.156.008-.195.085 0 0-2.463 4.808-2.463 4.808-.058.114-.005.263.107.298l1.296.416c.122.039.171.21.093.321 0 0-1.205 1.722-1.205 1.722-.081.116-.024.294.105.325l.827.196c.128.031.186.209.104.325 0 0-1.899 2.701-1.899 2.701-.118.169.056.41.22.304l.11-.07 6.849-5.661c.115-.095.083-.304-.054-.354l-1.312-.48c-.123-.045-.165-.224-.078-.331 0 0 2.157-2.615 2.157-2.615.087-.106.045-.286-.079-.331z" fill="#fbbf28" stroke="#fbbf28" stroke-width=".6"/>
|
||||
<path d="m24.507-7.812-8.199.701-1.126-4.064 8.199-.701z" fill="#949494"/>
|
||||
<path d="m15.182-11.175-2.707 2.766-22.189-13.825 2.707-2.765z" fill="#949494"/>
|
||||
<path d="m10.07 6.937-3.465 1.204-.367-2.503z" fill="#ababab"/>
|
||||
<path d="m16.308-7.111-6.238 14.048-3.832-1.299 6.237-14.047z" fill="#949494"/>
|
||||
<path d="m16.308-7.111-1.126-4.064-2.707 2.766z" fill="#ababab"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.8 KiB |
@ -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"]
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import { usePageContext } from "vike-react/usePageContext";
|
||||
import { NavLink } from "@mantine/core";
|
||||
|
||||
export function Link({ href, label }: { href: string; label: string }) {
|
||||
const pageContext = usePageContext();
|
||||
const { urlPathname } = pageContext;
|
||||
const isActive = href === "/" ? urlPathname === href : urlPathname.startsWith(href);
|
||||
return <NavLink href={href} label={label} active={isActive} />;
|
||||
}
|
@ -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 };
|
@ -0,0 +1,12 @@
|
||||
import type { 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 {};
|
@ -0,0 +1,31 @@
|
||||
import { serve } from "@hono/node-server";
|
||||
import { serveStatic } from "@hono/node-server/serve-static";
|
||||
import { type Context, Hono } from "hono";
|
||||
import { env } from "hono/adapter";
|
||||
import { compress } from "hono/compress";
|
||||
import app from "./hono-entry.js";
|
||||
|
||||
const envs = env<{ NODE_ENV?: string; PORT?: string }>({ env: {} } as unknown as Context<{
|
||||
Bindings: { NODE_ENV?: string; PORT?: string };
|
||||
}>);
|
||||
|
||||
const nodeApp = new Hono();
|
||||
|
||||
nodeApp.use(compress());
|
||||
|
||||
nodeApp.use(
|
||||
"/*",
|
||||
serveStatic({
|
||||
root: `./dist/client/`,
|
||||
}),
|
||||
);
|
||||
|
||||
nodeApp.route("/", app!);
|
||||
|
||||
const port = envs.PORT ? parseInt(envs.PORT, 10) : 3000;
|
||||
|
||||
console.log(`Server listening on http://localhost:${port}`);
|
||||
serve({
|
||||
fetch: nodeApp.fetch,
|
||||
port: port,
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
import { authjsHandler, authjsSessionMiddleware } from "./server/authjs-handler";
|
||||
import { vikeHandler } from "./server/vike-handler";
|
||||
import { Hono } from "hono";
|
||||
import { createHandler, createMiddleware } from "@universal-middleware/hono";
|
||||
import { trpcHandler } from "./server/trpc-handler";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.use(createMiddleware(authjsSessionMiddleware)());
|
||||
|
||||
/**
|
||||
* Auth.js route
|
||||
* @link {@see https://authjs.dev/getting-started/installation}
|
||||
**/
|
||||
app.use("/api/auth/**", createHandler(authjsHandler)());
|
||||
|
||||
app.use("/api/trpc/*", createHandler(trpcHandler)("/api/trpc"));
|
||||
|
||||
/**
|
||||
* Vike route
|
||||
*
|
||||
* @link {@see https://vike.dev}
|
||||
**/
|
||||
app.all("*", createHandler(vikeHandler)());
|
||||
|
||||
export default app;
|
@ -0,0 +1,36 @@
|
||||
import "@mantine/core/styles.css";
|
||||
import { AppShell, Burger, Group, Image, MantineProvider } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import theme from "./theme.js";
|
||||
|
||||
import logoUrl from "../assets/logo.svg";
|
||||
import { Link } from "../components/Link";
|
||||
|
||||
export default function LayoutDefault({ children }: { children: React.ReactNode }) {
|
||||
const [opened, { toggle }] = useDisclosure();
|
||||
return (
|
||||
<MantineProvider theme={theme}>
|
||||
<AppShell
|
||||
header={{ height: 60 }}
|
||||
navbar={{ width: 300, breakpoint: "sm", collapsed: { mobile: !opened } }}
|
||||
padding="md"
|
||||
>
|
||||
<AppShell.Header>
|
||||
<Group h="100%" px="md">
|
||||
<Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
|
||||
<a href="/">
|
||||
{" "}
|
||||
<Image h={50} fit="contain" src={logoUrl} />{" "}
|
||||
</a>
|
||||
</Group>
|
||||
</AppShell.Header>
|
||||
<AppShell.Navbar p="md">
|
||||
<Link href="/" label="Welcome" />
|
||||
<Link href="/todo" label="Todo" />
|
||||
<Link href="/star-wars" label="Data Fetching" />
|
||||
</AppShell.Navbar>
|
||||
<AppShell.Main> {children} </AppShell.Main>
|
||||
</AppShell>
|
||||
</MantineProvider>
|
||||
);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import { createTheme } from "@mantine/core";
|
||||
import type { MantineThemeOverride } from "@mantine/core";
|
||||
|
||||
const theme: MantineThemeOverride = createTheme({
|
||||
/** Put your mantine theme override here */
|
||||
primaryColor: "violet",
|
||||
});
|
||||
|
||||
export default theme;
|
@ -0,0 +1,47 @@
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "vike dev",
|
||||
"build": "vike build",
|
||||
"preview": "run-s build preview:wrangler",
|
||||
"lint": "biome lint --write .",
|
||||
"format": "biome format --write .",
|
||||
"preview:wrangler": "wrangler pages dev",
|
||||
"deploy:wrangler": "wrangler pages deploy",
|
||||
"deploy": "run-s build deploy:wrangler"
|
||||
},
|
||||
"dependencies": {
|
||||
"vike": "^0.4.235",
|
||||
"@auth/core": "^0.40.0",
|
||||
"@universal-middleware/core": "^0.4.8",
|
||||
"@compiled/react": "^0.18.6",
|
||||
"@hono/node-server": "^1.14.4",
|
||||
"@universal-middleware/hono": "^0.4.14",
|
||||
"hono": "^4.8.2",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"vike-react": "^0.6.4",
|
||||
"@trpc/server": "^11.4.2",
|
||||
"@trpc/client": "^11.4.2",
|
||||
"vike-cloudflare": "^0.1.7",
|
||||
"@mantine/core": "^8.1.1",
|
||||
"@mantine/hooks": "^8.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5",
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"vite-plugin-compiled-react": "^1.3.1",
|
||||
"@hono/vite-dev-server": "^0.19.1",
|
||||
"@types/node": "^20.19.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@cloudflare/workers-types": "^4.20250620.0",
|
||||
"wrangler": "^4.20.5",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"postcss-simple-vars": "^7.0.1"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
// https://vike.dev/Head
|
||||
|
||||
import logoUrl from "../assets/logo.svg";
|
||||
|
||||
import { ColorSchemeScript } from "@mantine/core";
|
||||
|
||||
export default function HeadDefault() {
|
||||
return (
|
||||
<>
|
||||
<link rel="icon" href={logoUrl} />
|
||||
<ColorSchemeScript />
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import vikeReact from "vike-react/config";
|
||||
import type { Config } from "vike/types";
|
||||
import Layout from "../layouts/LayoutDefault.js";
|
||||
|
||||
// Default config (can be overridden by pages)
|
||||
// https://vike.dev/config
|
||||
|
||||
export default {
|
||||
// https://vike.dev/Layout
|
||||
Layout,
|
||||
|
||||
// https://vike.dev/head-tags
|
||||
title: "My Vike App",
|
||||
description: "Demo showcasing Vike",
|
||||
|
||||
passToClient: ["user"],
|
||||
extends: vikeReact,
|
||||
} satisfies Config;
|
@ -0,0 +1,6 @@
|
||||
import type { OnPageTransitionEndAsync } from "vike/types";
|
||||
|
||||
export const onPageTransitionEnd: OnPageTransitionEndAsync = async () => {
|
||||
console.log("Page transition end");
|
||||
document.querySelector("body")?.classList.remove("page-is-transitioning");
|
||||
};
|
@ -0,0 +1,6 @@
|
||||
import type { OnPageTransitionStartAsync } from "vike/types";
|
||||
|
||||
export const onPageTransitionStart: OnPageTransitionStartAsync = async () => {
|
||||
console.log("Page transition start");
|
||||
document.querySelector("body")?.classList.add("page-is-transitioning");
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
import { usePageContext } from "vike-react/usePageContext";
|
||||
|
||||
export default function Page() {
|
||||
const { is404 } = usePageContext();
|
||||
if (is404) {
|
||||
return (
|
||||
<>
|
||||
<h1>404 Page Not Found</h1>
|
||||
<p>This page could not be found.</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<h1>500 Internal Server Error</h1>
|
||||
<p>Something went wrong.</p>
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { Counter } from "./Counter.js";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<h1 css={{ fontWeight: "700", fontSize: "1.875rem", paddingBottom: "1rem" }}>My Vike app</h1>
|
||||
This page is:
|
||||
<ul>
|
||||
<li>Rendered to HTML.</li>
|
||||
<li>
|
||||
Interactive. <Counter />
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { useState } from "react";
|
||||
|
||||
export function Counter() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
css={{
|
||||
display: "inline-block",
|
||||
border: "1px solid black",
|
||||
borderRadius: "0.25rem",
|
||||
backgroundColor: "#e5e7eb",
|
||||
padding: "4px 8px 4px 8px",
|
||||
fontSize: "0.75rem",
|
||||
fontWeight: "500",
|
||||
textTransform: "uppercase",
|
||||
lineHeight: "1.5",
|
||||
}}
|
||||
onClick={() => setCount((count) => count + 1)}
|
||||
>
|
||||
Counter {count}
|
||||
</button>
|
||||
);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { useData } from "vike-react/useData";
|
||||
import type { Data } from "./+data.js";
|
||||
|
||||
export default function Page() {
|
||||
const movie = useData<Data>();
|
||||
return (
|
||||
<>
|
||||
<h1>{movie.title}</h1>
|
||||
Release Date: {movie.release_date}
|
||||
<br />
|
||||
Director: {movie.director}
|
||||
<br />
|
||||
Producer: {movie.producer}
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// https://vike.dev/data
|
||||
|
||||
import type { PageContextServer } from "vike/types";
|
||||
import type { MovieDetails } from "../types.js";
|
||||
import { useConfig } from "vike-react/useConfig";
|
||||
|
||||
export type Data = Awaited<ReturnType<typeof data>>;
|
||||
|
||||
export const data = async (pageContext: PageContextServer) => {
|
||||
// https://vike.dev/useConfig
|
||||
const config = useConfig();
|
||||
|
||||
const response = await fetch(`https://brillout.github.io/star-wars/api/films/${pageContext.routeParams.id}.json`);
|
||||
let movie = (await response.json()) as MovieDetails;
|
||||
|
||||
config({
|
||||
// Set <title>
|
||||
title: movie.title,
|
||||
});
|
||||
|
||||
// We remove data we don't need because the data is passed to
|
||||
// the client; we should minimize what is sent over the network.
|
||||
movie = minimize(movie);
|
||||
|
||||
return movie;
|
||||
};
|
||||
|
||||
function minimize(movie: MovieDetails): MovieDetails {
|
||||
const { id, title, release_date, director, producer } = movie;
|
||||
const minimizedMovie = { id, title, release_date, director, producer };
|
||||
return minimizedMovie;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { useData } from "vike-react/useData";
|
||||
import type { Data } from "./+data.js";
|
||||
|
||||
export default function Page() {
|
||||
const movies = useData<Data>();
|
||||
return (
|
||||
<>
|
||||
<h1>Star Wars Movies</h1>
|
||||
<ol>
|
||||
{movies.map(({ id, title, release_date }) => (
|
||||
<li key={id}>
|
||||
<a href={`/star-wars/${id}`}>{title}</a> ({release_date})
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
<p>
|
||||
Source: <a href="https://brillout.github.io/star-wars">brillout.github.io/star-wars</a>.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// https://vike.dev/data
|
||||
|
||||
import type { Movie, MovieDetails } from "../types.js";
|
||||
import { useConfig } from "vike-react/useConfig";
|
||||
|
||||
export type Data = Awaited<ReturnType<typeof data>>;
|
||||
|
||||
export const data = async () => {
|
||||
// https://vike.dev/useConfig
|
||||
const config = useConfig();
|
||||
|
||||
const response = await fetch("https://brillout.github.io/star-wars/api/films.json");
|
||||
const moviesData = (await response.json()) as MovieDetails[];
|
||||
|
||||
config({
|
||||
// Set <title>
|
||||
title: `${moviesData.length} Star Wars Movies`,
|
||||
});
|
||||
|
||||
// We remove data we don't need because the data is passed to the client; we should
|
||||
// minimize what is sent over the network.
|
||||
const movies = minimize(moviesData);
|
||||
|
||||
return movies;
|
||||
};
|
||||
|
||||
function minimize(movies: MovieDetails[]): Movie[] {
|
||||
return movies.map((movie) => {
|
||||
const { title, release_date, id } = movie;
|
||||
return { title, release_date, id };
|
||||
});
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
export type Movie = {
|
||||
id: string;
|
||||
title: string;
|
||||
release_date: string;
|
||||
};
|
||||
|
||||
export type MovieDetails = Movie & {
|
||||
director: string;
|
||||
producer: string;
|
||||
};
|
@ -0,0 +1,13 @@
|
||||
import type { Data } from "./+data";
|
||||
import { useData } from "vike-react/useData";
|
||||
import { TodoList } from "./TodoList.js";
|
||||
|
||||
export default function Page() {
|
||||
const data = useData<Data>();
|
||||
return (
|
||||
<>
|
||||
<h1>To-do List</h1>
|
||||
<TodoList initialTodoItems={data.todo} />
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export const config = {
|
||||
prerender: false,
|
||||
};
|
@ -0,0 +1,11 @@
|
||||
// https://vike.dev/data
|
||||
import { todos } from "../../database/todoItems";
|
||||
import type { PageContextServer } from "vike/types";
|
||||
|
||||
export type Data = {
|
||||
todo: { text: string }[];
|
||||
};
|
||||
|
||||
export default async function data(_pageContext: PageContextServer): Promise<Data> {
|
||||
return { todo: todos };
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { trpc } from "../../trpc/client";
|
||||
import { useState } from "react";
|
||||
|
||||
export function TodoList({ initialTodoItems }: { initialTodoItems: { text: string }[] }) {
|
||||
const [todoItems, setTodoItems] = useState(initialTodoItems);
|
||||
const [newTodo, setNewTodo] = useState("");
|
||||
return (
|
||||
<>
|
||||
<ul>
|
||||
{todoItems.map((todoItem, index) => (
|
||||
// biome-ignore lint:
|
||||
<li key={index}>{todoItem.text}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div>
|
||||
<form
|
||||
onSubmit={async (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
// Optimistic UI update
|
||||
setTodoItems((prev) => [...prev, { text: newTodo }]);
|
||||
try {
|
||||
await trpc.onNewTodo.mutate(newTodo);
|
||||
setNewTodo("");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// rollback
|
||||
setTodoItems((prev) => prev.slice(0, -1));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="text" onChange={(ev) => setNewTodo(ev.target.value)} value={newTodo} />
|
||||
<button type="submit">Add to-do</button>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
onlyBuiltDependencies:
|
||||
- "@biomejs/biome"
|
@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
"postcss-preset-mantine": {},
|
||||
"postcss-simple-vars": {
|
||||
variables: {
|
||||
"mantine-breakpoint-xs": "36em",
|
||||
"mantine-breakpoint-sm": "48em",
|
||||
"mantine-breakpoint-md": "62em",
|
||||
"mantine-breakpoint-lg": "75em",
|
||||
"mantine-breakpoint-xl": "88em",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -0,0 +1,95 @@
|
||||
import { Auth, type AuthConfig, createActionURL, setEnvDefaults } from "@auth/core";
|
||||
import CredentialsProvider from "@auth/core/providers/credentials";
|
||||
import type { Session } from "@auth/core/types";
|
||||
// TODO: stop using universal-middleware and directly integrate server middlewares instead and/or use vike-server https://vike.dev/server. (Bati generates boilerplates that use universal-middleware https://github.com/magne4000/universal-middleware to make Bati's internal logic easier. This is temporary and will be removed soon.)
|
||||
import type { Get, UniversalHandler, UniversalMiddleware } from "@universal-middleware/core";
|
||||
|
||||
const env: Record<string, string | undefined> =
|
||||
typeof process?.env !== "undefined"
|
||||
? process.env
|
||||
: import.meta && "env" in import.meta
|
||||
? (import.meta as ImportMeta & { env: Record<string, string | undefined> }).env
|
||||
: {};
|
||||
|
||||
if (!globalThis.crypto) {
|
||||
/**
|
||||
* Polyfill needed if Auth.js code runs on node18
|
||||
*/
|
||||
Object.defineProperty(globalThis, "crypto", {
|
||||
value: await import("node:crypto").then((crypto) => crypto.webcrypto as Crypto),
|
||||
writable: false,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
const authjsConfig = {
|
||||
basePath: "/api/auth",
|
||||
trustHost: Boolean(env.AUTH_TRUST_HOST ?? env.VERCEL ?? env.NODE_ENV !== "production"),
|
||||
// TODO: Replace secret {@see https://authjs.dev/reference/core#secret}
|
||||
secret: "MY_SECRET",
|
||||
providers: [
|
||||
// TODO: Choose and implement providers
|
||||
CredentialsProvider({
|
||||
name: "Credentials",
|
||||
credentials: {
|
||||
username: { label: "Username", type: "text", placeholder: "jsmith" },
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
async authorize() {
|
||||
// Add logic here to look up the user from the credentials supplied
|
||||
const user = { id: "1", name: "J Smith", email: "jsmith@example.com" };
|
||||
|
||||
// Any object returned will be saved in `user` property of the JWT
|
||||
// If you return null then an error will be displayed advising the user to check their details.
|
||||
// You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter
|
||||
return user ?? null;
|
||||
},
|
||||
}),
|
||||
],
|
||||
} satisfies Omit<AuthConfig, "raw">;
|
||||
|
||||
/**
|
||||
* Retrieve Auth.js session from Request
|
||||
*/
|
||||
export async function getSession(req: Request, config: Omit<AuthConfig, "raw">): Promise<Session | null> {
|
||||
setEnvDefaults(process.env, config);
|
||||
const requestURL = new URL(req.url);
|
||||
const url = createActionURL("session", requestURL.protocol, req.headers, process.env, config);
|
||||
|
||||
const response = await Auth(new Request(url, { headers: { cookie: req.headers.get("cookie") ?? "" } }), config);
|
||||
|
||||
const { status = 200 } = response;
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data || !Object.keys(data).length) return null;
|
||||
if (status === 200) return data;
|
||||
throw new Error(data.message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Auth.js session to context
|
||||
* @link {@see https://authjs.dev/getting-started/session-management/get-session}
|
||||
**/
|
||||
export const authjsSessionMiddleware: Get<[], UniversalMiddleware> = () => async (request, context) => {
|
||||
try {
|
||||
return {
|
||||
...context,
|
||||
session: await getSession(request, authjsConfig),
|
||||
};
|
||||
} catch (error) {
|
||||
console.debug("authjsSessionMiddleware:", error);
|
||||
return {
|
||||
...context,
|
||||
session: null,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Auth.js route
|
||||
* @link {@see https://authjs.dev/getting-started/installation}
|
||||
**/
|
||||
export const authjsHandler = (() => async (request) => {
|
||||
return Auth(request, authjsConfig);
|
||||
}) satisfies Get<[], UniversalHandler>;
|
@ -0,0 +1,20 @@
|
||||
import { appRouter } from "../trpc/server";
|
||||
// TODO: stop using universal-middleware and directly integrate server middlewares instead and/or use vike-server https://vike.dev/server. (Bati generates boilerplates that use universal-middleware https://github.com/magne4000/universal-middleware to make Bati's internal logic easier. This is temporary and will be removed soon.)
|
||||
import type { Get, UniversalHandler } from "@universal-middleware/core";
|
||||
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||
|
||||
export const trpcHandler = ((endpoint) => (request, context, runtime) => {
|
||||
return fetchRequestHandler({
|
||||
endpoint,
|
||||
req: request,
|
||||
router: appRouter,
|
||||
createContext({ req, resHeaders }) {
|
||||
return {
|
||||
...context,
|
||||
...runtime,
|
||||
req,
|
||||
resHeaders,
|
||||
};
|
||||
},
|
||||
});
|
||||
}) satisfies Get<[endpoint: string], UniversalHandler>;
|
@ -0,0 +1,18 @@
|
||||
/// <reference lib="webworker" />
|
||||
import { renderPage } from "vike/server";
|
||||
// TODO: stop using universal-middleware and directly integrate server middlewares instead and/or use vike-server https://vike.dev/server. (Bati generates boilerplates that use universal-middleware https://github.com/magne4000/universal-middleware to make Bati's internal logic easier. This is temporary and will be removed soon.)
|
||||
import type { Get, UniversalHandler } from "@universal-middleware/core";
|
||||
|
||||
export const vikeHandler: Get<[], UniversalHandler> = () => async (request, context, runtime) => {
|
||||
const pageContextInit = { ...context, ...runtime, urlOriginal: request.url, headersOriginal: request.headers };
|
||||
const pageContext = await renderPage(pageContextInit);
|
||||
const response = pageContext.httpResponse;
|
||||
|
||||
const { readable, writable } = new TransformStream();
|
||||
response.pipe(writable);
|
||||
|
||||
return new Response(readable, {
|
||||
status: response.statusCode,
|
||||
headers: response.headers,
|
||||
});
|
||||
};
|
@ -0,0 +1,10 @@
|
||||
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
|
||||
import type { AppRouter } from "./server.js";
|
||||
|
||||
export const trpc = createTRPCProxyClient<AppRouter>({
|
||||
links: [
|
||||
httpBatchLink({
|
||||
url: "/api/trpc",
|
||||
}),
|
||||
],
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
import { initTRPC } from "@trpc/server";
|
||||
|
||||
/**
|
||||
* Initialization of tRPC backend
|
||||
* Should be done only once per backend!
|
||||
*/
|
||||
const t = initTRPC.context<object>().create();
|
||||
|
||||
/**
|
||||
* Export reusable router and procedure helpers
|
||||
* that can be used throughout the router
|
||||
*/
|
||||
export const router = t.router;
|
||||
export const publicProcedure = t.procedure;
|
||||
|
||||
export const appRouter = router({
|
||||
demo: publicProcedure.query(async () => {
|
||||
return { demo: true };
|
||||
}),
|
||||
onNewTodo: publicProcedure
|
||||
.input((value): string => {
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
throw new Error("Input is not a string");
|
||||
})
|
||||
.mutation(async (opts) => {
|
||||
console.log("Received new todo", { text: opts.input });
|
||||
}),
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"module": "ESNext",
|
||||
"noEmit": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"target": "ES2022",
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ESNext"
|
||||
],
|
||||
"types": [
|
||||
"vite/client",
|
||||
"vike-react",
|
||||
"vike-cloudflare/types"
|
||||
],
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react"
|
||||
},
|
||||
"exclude": [
|
||||
"dist"
|
||||
]
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import { pages } from "vike-cloudflare";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import devServer from "@hono/vite-dev-server";
|
||||
import { compiled } from "vite-plugin-compiled-react";
|
||||
import { defineConfig } from "vite";
|
||||
import vike from "vike/plugin";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vike(),
|
||||
compiled({
|
||||
extract: true,
|
||||
}),
|
||||
devServer({
|
||||
entry: "hono-entry.ts",
|
||||
|
||||
exclude: [
|
||||
/^\/@.+$/,
|
||||
/.*\.(ts|tsx|vue)($|\?)/,
|
||||
/.*\.(s?css|less)($|\?)/,
|
||||
/^\/favicon\.ico$/,
|
||||
/.*\.(svg|png)($|\?)/,
|
||||
/^\/(public|assets|static)\/.+/,
|
||||
/^\/node_modules\/.*/,
|
||||
],
|
||||
|
||||
injectClientScript: false,
|
||||
}),
|
||||
react(),
|
||||
pages({
|
||||
server: {
|
||||
kind: "hono",
|
||||
entry: "hono-entry.ts",
|
||||
},
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
target: "es2022",
|
||||
},
|
||||
});
|
@ -0,0 +1,4 @@
|
||||
name = "my-app"
|
||||
compatibility_date = "2024-09-29"
|
||||
pages_build_output_dir = "./dist/cloudflare"
|
||||
compatibility_flags = [ "nodejs_compat" ]
|
Loading…
Reference in New Issue