feat: Add initial project files and configurations
This commit is contained in:
parent
ef3cc4efd9
commit
09c156b987
49
README.md
Normal file
49
README.md
Normal file
@ -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 [`<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.
|
||||||
|
|
||||||
4
app/.env
Normal file
4
app/.env
Normal file
@ -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"
|
||||||
3
app/.prettierrc
Normal file
3
app/.prettierrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 120
|
||||||
|
}
|
||||||
67
app/assets/logo.svg
Normal file
67
app/assets/logo.svg
Normal file
@ -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 |
15
app/biome.json
Normal file
15
app/biome.json
Normal file
@ -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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
14
app/components/Link.tsx
Normal file
14
app/components/Link.tsx
Normal file
@ -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 (
|
||||||
|
<a href={props.href} class={isActive() ? "is-active" : undefined}>
|
||||||
|
{props.children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
app/database/todoItems.ts
Normal file
17
app/database/todoItems.ts
Normal file
@ -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 };
|
||||||
56
app/eslint.config.ts
Normal file
56
app/eslint.config.ts
Normal file
@ -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,
|
||||||
|
);
|
||||||
76
app/fastify-entry.ts
Normal file
76
app/fastify-entry.ts
Normal file
@ -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}`);
|
||||||
|
},
|
||||||
|
);
|
||||||
12
app/global.d.ts
vendored
Normal file
12
app/global.d.ts
vendored
Normal file
@ -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 {};
|
||||||
50
app/layouts/LayoutDefault.tsx
Normal file
50
app/layouts/LayoutDefault.tsx
Normal file
@ -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 (
|
||||||
|
<div class={"flex max-w-5xl m-auto"}>
|
||||||
|
<Sidebar>
|
||||||
|
<Logo />
|
||||||
|
<Link href="/">Welcome</Link>
|
||||||
|
<Link href="/todo">Todo</Link>
|
||||||
|
<Link href="/star-wars">Data Fetching</Link>
|
||||||
|
{""}
|
||||||
|
</Sidebar>
|
||||||
|
<Content>{props.children}</Content>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Sidebar(props: { children: JSX.Element }) {
|
||||||
|
return (
|
||||||
|
<div id="sidebar" class={"p-5 flex flex-col shrink-0 border-r-2 border-r-gray-200"}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Content(props: { children: JSX.Element }) {
|
||||||
|
return (
|
||||||
|
<div id="page-container">
|
||||||
|
<div id="page-content" class={"p-5 pb-12 min-h-screen"}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Logo() {
|
||||||
|
return (
|
||||||
|
<div class={"p-5 mb-2"}>
|
||||||
|
<a href="/">
|
||||||
|
<img src={logoUrl} height={64} width={64} alt="logo" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
29
app/layouts/style.css
Normal file
29
app/layouts/style.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
1
app/layouts/tailwind.css
Normal file
1
app/layouts/tailwind.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
40
app/package.json
Normal file
40
app/package.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
21
app/pages/+Head.tsx
Normal file
21
app/pages/+Head.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/* eslint-disable solid/no-innerhtml */
|
||||||
|
|
||||||
|
// https://vike.dev/Head
|
||||||
|
|
||||||
|
export default function HeadDefault() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
src={`https://www.googletagmanager.com/gtag/js?id=${import.meta.env.PUBLIC_ENV__GOOGLE_ANALYTICS}`}
|
||||||
|
/>
|
||||||
|
<script
|
||||||
|
innerHTML={`window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
|
||||||
|
gtag('config', '${import.meta.env.PUBLIC_ENV__GOOGLE_ANALYTICS}');`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
app/pages/+config.ts
Normal file
18
app/pages/+config.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import vikeSolid from "vike-solid/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: vikeSolid,
|
||||||
|
} satisfies Config;
|
||||||
6
app/pages/+onPageTransitionEnd.ts
Normal file
6
app/pages/+onPageTransitionEnd.ts
Normal file
@ -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");
|
||||||
|
};
|
||||||
6
app/pages/+onPageTransitionStart.ts
Normal file
6
app/pages/+onPageTransitionStart.ts
Normal file
@ -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");
|
||||||
|
};
|
||||||
20
app/pages/_error/+Page.tsx
Normal file
20
app/pages/_error/+Page.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Show } from "solid-js";
|
||||||
|
import { usePageContext } from "vike-solid/usePageContext";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const { is404 } = usePageContext();
|
||||||
|
return (
|
||||||
|
<Show
|
||||||
|
when={is404}
|
||||||
|
fallback={
|
||||||
|
<>
|
||||||
|
<h1>500 Internal Server Error</h1>
|
||||||
|
<p>Something went wrong.</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<h1>404 Page Not Found</h1>
|
||||||
|
<p>This page could not be found.</p>
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
app/pages/index/+Page.tsx
Normal file
16
app/pages/index/+Page.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Counter } from "./Counter.js";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1 class={"font-bold text-3xl pb-4"}>My Vike app</h1>
|
||||||
|
This page is:
|
||||||
|
<ul>
|
||||||
|
<li>Rendered to HTML.</li>
|
||||||
|
<li>
|
||||||
|
Interactive. <Counter />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
app/pages/index/Counter.tsx
Normal file
19
app/pages/index/Counter.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { createSignal } from "solid-js";
|
||||||
|
|
||||||
|
export { Counter };
|
||||||
|
|
||||||
|
function Counter() {
|
||||||
|
const [count, setCount] = createSignal(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={
|
||||||
|
"inline-block border border-black rounded bg-gray-200 px-2 py-1 text-xs font-medium uppercase leading-normal"
|
||||||
|
}
|
||||||
|
onClick={() => setCount((count) => count + 1)}
|
||||||
|
>
|
||||||
|
Counter {count()}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
app/pages/star-wars/@id/+Page.tsx
Normal file
16
app/pages/star-wars/@id/+Page.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { useData } from "vike-solid/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}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
32
app/pages/star-wars/@id/+data.ts
Normal file
32
app/pages/star-wars/@id/+data.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// https://vike.dev/data
|
||||||
|
|
||||||
|
import type { PageContextServer } from "vike/types";
|
||||||
|
import type { MovieDetails } from "../types.js";
|
||||||
|
import { useConfig } from "vike-solid/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;
|
||||||
|
}
|
||||||
24
app/pages/star-wars/index/+Page.tsx
Normal file
24
app/pages/star-wars/index/+Page.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { For } from "solid-js";
|
||||||
|
import { useData } from "vike-solid/useData";
|
||||||
|
import type { Data } from "./+data.js";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const movies = useData<Data>();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Star Wars Movies</h1>
|
||||||
|
<ol>
|
||||||
|
<For each={movies}>
|
||||||
|
{(movie) => (
|
||||||
|
<li>
|
||||||
|
<a href={`/star-wars/${movie.id}`}>{movie.title}</a> ({movie.release_date})
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</ol>
|
||||||
|
<p>
|
||||||
|
Source: <a href="https://brillout.github.io/star-wars">brillout.github.io/star-wars</a>.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
32
app/pages/star-wars/index/+data.ts
Normal file
32
app/pages/star-wars/index/+data.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// https://vike.dev/data
|
||||||
|
|
||||||
|
import type { Movie, MovieDetails } from "../types.js";
|
||||||
|
import { useConfig } from "vike-solid/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 };
|
||||||
|
});
|
||||||
|
}
|
||||||
10
app/pages/star-wars/types.ts
Normal file
10
app/pages/star-wars/types.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export type Movie = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
release_date: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MovieDetails = Movie & {
|
||||||
|
director: string;
|
||||||
|
producer: string;
|
||||||
|
};
|
||||||
13
app/pages/todo/+Page.tsx
Normal file
13
app/pages/todo/+Page.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { Data } from "./+data";
|
||||||
|
import { useData } from "vike-solid/useData";
|
||||||
|
import { TodoList } from "./TodoList.js";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const data = useData<Data>();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>To-do List</h1>
|
||||||
|
<TodoList initialTodoItems={data.todo} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
app/pages/todo/+config.ts
Normal file
3
app/pages/todo/+config.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const config = {
|
||||||
|
prerender: false,
|
||||||
|
};
|
||||||
11
app/pages/todo/+data.ts
Normal file
11
app/pages/todo/+data.ts
Normal file
@ -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 };
|
||||||
|
}
|
||||||
7
app/pages/todo/TodoList.telefunc.ts
Normal file
7
app/pages/todo/TodoList.telefunc.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// We use Telefunc (https://telefunc.com) for data mutations. Being able to use Telefunc for fetching initial data is work-in-progress (https://vike.dev/data-fetching#tools).
|
||||||
|
|
||||||
|
import { todos } from "../../database/todoItems";
|
||||||
|
|
||||||
|
export async function onNewTodo({ text }: { text: string }) {
|
||||||
|
todos.push({ text });
|
||||||
|
}
|
||||||
49
app/pages/todo/TodoList.tsx
Normal file
49
app/pages/todo/TodoList.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { onNewTodo } from "./TodoList.telefunc";
|
||||||
|
import { createSignal, For, untrack } from "solid-js";
|
||||||
|
|
||||||
|
export function TodoList(props: { initialTodoItems: { text: string }[] }) {
|
||||||
|
const [todoItems, setTodoItems] = createSignal(props.initialTodoItems);
|
||||||
|
const [newTodo, setNewTodo] = createSignal("");
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ul>
|
||||||
|
<For each={todoItems()}>{(todoItem) => <li>{todoItem.text}</li>}</For>
|
||||||
|
</ul>
|
||||||
|
<div>
|
||||||
|
<form
|
||||||
|
onSubmit={async (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
// Optimistic UI update
|
||||||
|
setTodoItems((prev) => [...prev, { text: untrack(newTodo) }]);
|
||||||
|
try {
|
||||||
|
await onNewTodo({ text: untrack(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()}
|
||||||
|
class={
|
||||||
|
"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 w-full sm:w-auto p-2 mr-1 mb-1"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class={
|
||||||
|
"text-white bg-blue-700 hover:bg-blue-800 focus:ring-2 focus:outline-hidden focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto p-2"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Add to-do
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
95
app/server/authjs-handler.ts
Normal file
95
app/server/authjs-handler.ts
Normal file
@ -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>;
|
||||||
22
app/server/telefunc-handler.ts
Normal file
22
app/server/telefunc-handler.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { telefunc } from "telefunc";
|
||||||
|
// 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 telefuncHandler: Get<[], UniversalHandler> = () => async (request, context, runtime) => {
|
||||||
|
const httpResponse = await telefunc({
|
||||||
|
url: request.url.toString(),
|
||||||
|
method: request.method,
|
||||||
|
body: await request.text(),
|
||||||
|
context: {
|
||||||
|
...context,
|
||||||
|
...runtime,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { body, statusCode, contentType } = httpResponse;
|
||||||
|
return new Response(body, {
|
||||||
|
status: statusCode,
|
||||||
|
headers: {
|
||||||
|
"content-type": contentType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
18
app/server/vike-handler.ts
Normal file
18
app/server/vike-handler.ts
Normal file
@ -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,
|
||||||
|
});
|
||||||
|
};
|
||||||
28
app/tsconfig.json
Normal file
28
app/tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"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-solid/client"
|
||||||
|
],
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "solid-js"
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
||||||
12
app/vite.config.ts
Normal file
12
app/vite.config.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { telefunc } from "telefunc/vite";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import vikeSolid from "vike-solid/vite";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import vike from "vike/plugin";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vike(), vikeSolid(), tailwindcss(), telefunc()],
|
||||||
|
build: {
|
||||||
|
target: "es2022",
|
||||||
|
},
|
||||||
|
});
|
||||||
14
compose-old.yml
Normal file
14
compose-old.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
memento-dev:
|
||||||
|
container_name: memento-dev
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: pnpm.Dockerfile
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
ports:
|
||||||
|
- "${PORT}:${PORT}"
|
||||||
|
- "${HMR_PORT}:${HMR_PORT}"
|
||||||
|
volumes:
|
||||||
|
- ./app:/app
|
||||||
|
restart: unless-stopped
|
||||||
@ -1,9 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
memento-dev:
|
app:
|
||||||
container_name: memento-dev
|
container_name: memento-dev
|
||||||
build:
|
image: oven/bun:alpine
|
||||||
context: .
|
|
||||||
dockerfile: pnpm.Dockerfile
|
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
ports:
|
ports:
|
||||||
@ -11,4 +9,5 @@ services:
|
|||||||
- "${HMR_PORT}:${HMR_PORT}"
|
- "${HMR_PORT}:${HMR_PORT}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./app:/app
|
- ./app:/app
|
||||||
restart: unless-stopped
|
working_dir: /app
|
||||||
|
command: bun run dev
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user