@sigil-dev/grimoire 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -174
- package/index.ts +34 -16
- package/package.json +6 -6
- package/src/{enhance.ts → client/enhance.ts} +2 -1
- package/src/client/index.ts +5 -0
- package/src/{client-router.ts → client/router.ts} +1 -1
- package/src/{vite-plugin.ts → integrations/vite.ts} +4 -4
- package/src/{hydrate.ts → rendering/hydrate.ts} +2 -2
- package/src/{renderer.ts → rendering/index.ts} +29 -16
- package/src/{ssrPlugin.ts → rendering/ssrPlugin.ts} +3 -2
- package/src/{scanner.ts → routing/scanner.ts} +16 -4
- package/src/{transform-routes.ts → routing/transform-routes.ts} +3 -2
- package/src/{fail.ts → sentinels/fail.ts} +1 -1
- package/src/{build.ts → server/build.ts} +12 -10
- package/src/server/coordinator.ts +297 -0
- package/src/{hooks.ts → server/hooks.ts} +1 -1
- package/src/{server.ts → server/index.ts} +85 -37
- package/src/server/plugins.ts +119 -0
- package/src/server/worker.ts +59 -0
- package/src/{typegen.ts → typegen/index.ts} +5 -2
- package/src/types.ts +134 -3
- package/test/context.test.ts +1 -1
- package/test/fail.test.ts +1 -1
- package/test/headers.test.ts +6 -2
- package/test/hydration.test.ts +1 -1
- package/test/middleware.test.ts +8 -4
- package/test/preload.ts +1 -1
- package/test/redirect-error.test.ts +2 -2
- package/test/rendering.test.ts +15 -6
- package/test/routing.test.ts +2 -2
- package/test/scanning.test.ts +40 -11
- package/test/scope.test.ts +25 -10
- package/test/server.test.ts +39 -10
- package/test/streaming.test.ts +21 -8
- package/test/transform-routes.test.ts +2 -2
- package/test/typegen.test.ts +5 -3
- package/tsconfig.json +3 -1
- package/.grimoire/_routes.ts +0 -4
- package/public/__grimoire__/client.js +0 -55
- package/public/__grimoire__/hydrate.js +0 -63
- package/src/client.ts +0 -4
- package/src/plugins.ts +0 -39
- package/src/sync.ts +0 -18
- /package/src/{scope.ts → client/scope.ts} +0 -0
- /package/src/{head.ts → rendering/head.ts} +0 -0
- /package/src/{manifest-gen.ts → routing/manifest-gen.ts} +0 -0
- /package/src/{router.ts → routing/router.ts} +0 -0
- /package/src/{error.ts → sentinels/error.ts} +0 -0
- /package/src/{redirect.ts → sentinels/redirect.ts} +0 -0
- /package/src/{context.ts → server/context.ts} +0 -0
- /package/src/{cookie-utils.ts → server/cookie-utils.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,174 +1,174 @@
|
|
|
1
|
-
# @sigil-dev/grimoire
|
|
2
|
-
|
|
3
|
-
Full-stack server framework for [Sigil](https://git.cane1712.dev/sigil/sigil).
|
|
4
|
-
Heavily inspired by SvelteKit. Most of [its documentation](https://svelte.dev/docs/kit/introduction) applies here
|
|
5
|
-
|
|
6
|
-
## Quick start
|
|
7
|
-
|
|
8
|
-
```sh
|
|
9
|
-
bun i -g @sigil-dev/cli
|
|
10
|
-
sigil create
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Route files
|
|
14
|
-
|
|
15
|
-
Place files in /src/routes.
|
|
16
|
-
|
|
17
|
-
| File | Purpose |
|
|
18
|
-
|---|---|
|
|
19
|
-
| `+page.tsx | index.tsx | routeName.tsx` | Page |
|
|
20
|
-
| `+page.server.ts` | Server data passed to page / form actions |
|
|
21
|
-
| `+layout.tsx` | Page layout |
|
|
22
|
-
| `+layout.server.ts` | Layout-level `load` |
|
|
23
|
-
| `+server.ts` | API route (raw `Request → Response`) |
|
|
24
|
-
| `+error.tsx` | Error boundary page |
|
|
25
|
-
| `hooks.
|
|
26
|
-
|
|
27
|
-
`/routes/about/index.tsx`, `/routes/about/+page.tsx` and `/routes/about.tsx` all work and all do the same thing.
|
|
28
|
-
For path parameters use square brackets. `src/routes/posts/[slug]/+page.tsx` → `/posts/:slug`.
|
|
29
|
-
|
|
30
|
-
## Loading data
|
|
31
|
-
|
|
32
|
-
```ts
|
|
33
|
-
// src/routes/posts/[slug]/+page.server.ts
|
|
34
|
-
import { error } from "@sigil-dev/grimoire";
|
|
35
|
-
import type { TypedLoadContext } from "@sigil-dev/grimoire";
|
|
36
|
-
|
|
37
|
-
export async function load({ params }: TypedLoadContext<{ slug: string }>) {
|
|
38
|
-
const post = await db.posts.findBySlug(params.slug);
|
|
39
|
-
if (!post) throw error(404, "Post not found");
|
|
40
|
-
return { post }; // → page receives { data: { post }, params }
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
The return value is passed as the `data` prop to the matching `+page.tsx` component.
|
|
45
|
-
|
|
46
|
-
## Form actions
|
|
47
|
-
|
|
48
|
-
Export HTTP method handlers alongside `load`:
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
// src/routes/login/+page.server.ts
|
|
52
|
-
import { fail, redirect } from "@sigil-dev/grimoire";
|
|
53
|
-
|
|
54
|
-
export async function POST({ request }) {
|
|
55
|
-
const data = await request.formData();
|
|
56
|
-
const user = await auth.login(data.get("email"), data.get("password"));
|
|
57
|
-
if (!user) return fail(400, { error: "Invalid credentials" });
|
|
58
|
-
throw redirect(303, "/dashboard");
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Use `enhance` with the `use` prop on the client for fetch-based submission without a full page reload:
|
|
63
|
-
> This is jank and will likely be reworked.
|
|
64
|
-
|
|
65
|
-
```tsx
|
|
66
|
-
// src/routes/login/+page.tsx
|
|
67
|
-
import { enhance } from "@sigil-dev/grimoire/client";
|
|
68
|
-
|
|
69
|
-
export default function LoginPage() {
|
|
70
|
-
const errors = $state({});
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<form use={[enhance, { onFail: (data) => errors.set(data) }]}>
|
|
74
|
-
<input name="email" />
|
|
75
|
-
<input name="password" type="password" />
|
|
76
|
-
<button>Log in</button>
|
|
77
|
-
</form>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Error handling
|
|
83
|
-
|
|
84
|
-
`error(status, message)` can be thrown from any `load` function or action. Grimoire renders the closest `+error.tsx` if one exists, otherwise falls back to a plain-text response.
|
|
85
|
-
|
|
86
|
-
```tsx
|
|
87
|
-
// src/routes/+error.tsx
|
|
88
|
-
export default function ErrorPage({ status, message }: { status: number; message: string }) {
|
|
89
|
-
return (
|
|
90
|
-
<html>
|
|
91
|
-
<body>
|
|
92
|
-
<h1>{status}</h1>
|
|
93
|
-
<p>{message}</p>
|
|
94
|
-
</body>
|
|
95
|
-
</html>
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
## API routes
|
|
101
|
-
|
|
102
|
-
```ts
|
|
103
|
-
// src/routes/api/items/+server.ts
|
|
104
|
-
export async function GET({ url }) {
|
|
105
|
-
const items = await db.items.list();
|
|
106
|
-
return Response.json(items);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export async function POST({ request }) {
|
|
110
|
-
const body = await request.json();
|
|
111
|
-
const item = await db.items.create(body);
|
|
112
|
-
return Response.json(item, { status: 201 });
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Layouts
|
|
117
|
-
|
|
118
|
-
```tsx
|
|
119
|
-
// src/routes/+layout.tsx
|
|
120
|
-
import { Head } from "@sigil-dev/grimoire";
|
|
121
|
-
|
|
122
|
-
export default function Layout({ children }) {
|
|
123
|
-
return (
|
|
124
|
-
<html>
|
|
125
|
-
<Head><title>My App</title></Head>
|
|
126
|
-
<body>{children}</body>
|
|
127
|
-
</html>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Hooks (middleware)
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
// hooks.
|
|
136
|
-
import type { Handle } from "@sigil-dev/grimoire/hooks";
|
|
137
|
-
|
|
138
|
-
export const handle: Handle = async ({ event, resolve }) => {
|
|
139
|
-
event.locals.user = await getUser(event.request);
|
|
140
|
-
return resolve(event);
|
|
141
|
-
};
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Configuration
|
|
145
|
-
|
|
146
|
-
```ts
|
|
147
|
-
import { defineConfig } from "@sigil-dev/grimoire";
|
|
148
|
-
|
|
149
|
-
export default defineConfig({
|
|
150
|
-
port: 3000,
|
|
151
|
-
host: "localhost",
|
|
152
|
-
routes: "src/routes", // default
|
|
153
|
-
plugins: [],
|
|
154
|
-
});
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## Helpers
|
|
158
|
-
|
|
159
|
-
| Import | Description |
|
|
160
|
-
|---|---|
|
|
161
|
-
| `error(status, message)` | Throw an HTTP error from `load` or an action |
|
|
162
|
-
| `fail(status, data)` | Return validation errors from an action |
|
|
163
|
-
| `redirect(status, location)` | Throw a redirect from `load` or an action |
|
|
164
|
-
| `sequence(...handles)` | Compose multiple `Handle` middleware functions |
|
|
165
|
-
|
|
166
|
-
## Security headers
|
|
167
|
-
|
|
168
|
-
```ts
|
|
169
|
-
import { securityHeaders } from "@sigil-dev/grimoire/headers";
|
|
170
|
-
|
|
171
|
-
// use as a Handle in hooks.
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
`securityHeaders()` returns a `Handle` that sets CSP, HSTS, X-Frame-Options, and related headers on every response.
|
|
1
|
+
# @sigil-dev/grimoire
|
|
2
|
+
|
|
3
|
+
Full-stack server framework for [Sigil](https://git.cane1712.dev/sigil/sigil).
|
|
4
|
+
Heavily inspired by SvelteKit. Most of [its documentation](https://svelte.dev/docs/kit/introduction) applies here
|
|
5
|
+
|
|
6
|
+
## Quick start
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
bun i -g @sigil-dev/cli
|
|
10
|
+
sigil create
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Route files
|
|
14
|
+
|
|
15
|
+
Place files in /src/routes.
|
|
16
|
+
|
|
17
|
+
| File | Purpose |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `+page.tsx | index.tsx | routeName.tsx` | Page |
|
|
20
|
+
| `+page.server.ts` | Server data passed to page / form actions |
|
|
21
|
+
| `+layout.tsx` | Page layout |
|
|
22
|
+
| `+layout.server.ts` | Layout-level `load` |
|
|
23
|
+
| `+server.ts` | API route (raw `Request → Response`) |
|
|
24
|
+
| `+error.tsx` | Error boundary page |
|
|
25
|
+
| `hooks.index.ts` | Global request middleware (project root) |
|
|
26
|
+
|
|
27
|
+
`/routes/about/index.tsx`, `/routes/about/+page.tsx` and `/routes/about.tsx` all work and all do the same thing.
|
|
28
|
+
For path parameters use square brackets. `src/routes/posts/[slug]/+page.tsx` → `/posts/:slug`.
|
|
29
|
+
|
|
30
|
+
## Loading data
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// src/routes/posts/[slug]/+page.server.ts
|
|
34
|
+
import { error } from "@sigil-dev/grimoire";
|
|
35
|
+
import type { TypedLoadContext } from "@sigil-dev/grimoire";
|
|
36
|
+
|
|
37
|
+
export async function load({ params }: TypedLoadContext<{ slug: string }>) {
|
|
38
|
+
const post = await db.posts.findBySlug(params.slug);
|
|
39
|
+
if (!post) throw error(404, "Post not found");
|
|
40
|
+
return { post }; // → page receives { data: { post }, params }
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The return value is passed as the `data` prop to the matching `+page.tsx` component.
|
|
45
|
+
|
|
46
|
+
## Form actions
|
|
47
|
+
|
|
48
|
+
Export HTTP method handlers alongside `load`:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// src/routes/login/+page.server.ts
|
|
52
|
+
import { fail, redirect } from "@sigil-dev/grimoire";
|
|
53
|
+
|
|
54
|
+
export async function POST({ request }) {
|
|
55
|
+
const data = await request.formData();
|
|
56
|
+
const user = await auth.login(data.get("email"), data.get("password"));
|
|
57
|
+
if (!user) return fail(400, { error: "Invalid credentials" });
|
|
58
|
+
throw redirect(303, "/dashboard");
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Use `enhance` with the `use` prop on the client for fetch-based submission without a full page reload:
|
|
63
|
+
> This is jank and will likely be reworked.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// src/routes/login/+page.tsx
|
|
67
|
+
import { enhance } from "@sigil-dev/grimoire/client";
|
|
68
|
+
|
|
69
|
+
export default function LoginPage() {
|
|
70
|
+
const errors = $state({});
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<form use={[enhance, { onFail: (data) => errors.set(data) }]}>
|
|
74
|
+
<input name="email" />
|
|
75
|
+
<input name="password" type="password" />
|
|
76
|
+
<button>Log in</button>
|
|
77
|
+
</form>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Error handling
|
|
83
|
+
|
|
84
|
+
`error(status, message)` can be thrown from any `load` function or action. Grimoire renders the closest `+error.tsx` if one exists, otherwise falls back to a plain-text response.
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
// src/routes/+error.tsx
|
|
88
|
+
export default function ErrorPage({ status, message }: { status: number; message: string }) {
|
|
89
|
+
return (
|
|
90
|
+
<html>
|
|
91
|
+
<body>
|
|
92
|
+
<h1>{status}</h1>
|
|
93
|
+
<p>{message}</p>
|
|
94
|
+
</body>
|
|
95
|
+
</html>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API routes
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// src/routes/api/items/+server.ts
|
|
104
|
+
export async function GET({ url }) {
|
|
105
|
+
const items = await db.items.list();
|
|
106
|
+
return Response.json(items);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function POST({ request }) {
|
|
110
|
+
const body = await request.json();
|
|
111
|
+
const item = await db.items.create(body);
|
|
112
|
+
return Response.json(item, { status: 201 });
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Layouts
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// src/routes/+layout.tsx
|
|
120
|
+
import { Head } from "@sigil-dev/grimoire";
|
|
121
|
+
|
|
122
|
+
export default function Layout({ children }) {
|
|
123
|
+
return (
|
|
124
|
+
<html>
|
|
125
|
+
<Head><title>My App</title></Head>
|
|
126
|
+
<body>{children}</body>
|
|
127
|
+
</html>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Hooks (middleware)
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// hooks.index.ts (project root)
|
|
136
|
+
import type { Handle } from "@sigil-dev/grimoire/hooks";
|
|
137
|
+
|
|
138
|
+
export const handle: Handle = async ({ event, resolve }) => {
|
|
139
|
+
event.locals.user = await getUser(event.request);
|
|
140
|
+
return resolve(event);
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Configuration
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { defineConfig } from "@sigil-dev/grimoire";
|
|
148
|
+
|
|
149
|
+
export default defineConfig({
|
|
150
|
+
port: 3000,
|
|
151
|
+
host: "localhost",
|
|
152
|
+
routes: "src/routes", // default
|
|
153
|
+
plugins: [],
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Helpers
|
|
158
|
+
|
|
159
|
+
| Import | Description |
|
|
160
|
+
|---|---|
|
|
161
|
+
| `error(status, message)` | Throw an HTTP error from `load` or an action |
|
|
162
|
+
| `fail(status, data)` | Return validation errors from an action |
|
|
163
|
+
| `redirect(status, location)` | Throw a redirect from `load` or an action |
|
|
164
|
+
| `sequence(...handles)` | Compose multiple `Handle` middleware functions |
|
|
165
|
+
|
|
166
|
+
## Security headers
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { securityHeaders } from "@sigil-dev/grimoire/headers";
|
|
170
|
+
|
|
171
|
+
// use as a Handle in hooks.index.ts, or compose with sequence()
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`securityHeaders()` returns a `Handle` that sets CSP, HSTS, X-Frame-Options, and related headers on every response.
|
package/index.ts
CHANGED
|
@@ -1,29 +1,47 @@
|
|
|
1
|
-
export
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
1
|
+
export { Head } from "./src/rendering/head";
|
|
2
|
+
export type { RouteFile, RouteTree } from "./src/routing/scanner";
|
|
3
|
+
export { scanRoutes } from "./src/routing/scanner";
|
|
4
|
+
export type { ErrorResult } from "./src/sentinels/error.ts";
|
|
5
|
+
export { error, isErrorResult } from "./src/sentinels/error.ts";
|
|
6
|
+
export type { FailResult } from "./src/sentinels/fail.ts";
|
|
7
|
+
export { fail, isFailResult } from "./src/sentinels/fail.ts";
|
|
8
|
+
export type { RedirectResult } from "./src/sentinels/redirect.ts";
|
|
9
|
+
export { isRedirectResult, redirect } from "./src/sentinels/redirect.ts";
|
|
4
10
|
export { createServer } from "./src/server";
|
|
5
|
-
export {
|
|
6
|
-
export
|
|
7
|
-
export {
|
|
8
|
-
export type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
export { buildProject } from "./src/server/build";
|
|
12
|
+
export { parseCookies } from "./src/server/cookie-utils";
|
|
13
|
+
export { startCoordinator } from "./src/server/coordinator.ts";
|
|
14
|
+
export type {
|
|
15
|
+
CookieOptions,
|
|
16
|
+
Cookies,
|
|
17
|
+
Handle,
|
|
18
|
+
RequestEvent,
|
|
19
|
+
ResolveFunction,
|
|
20
|
+
} from "./src/server/hooks";
|
|
21
|
+
export { createHooks, sequence } from "./src/server/hooks";
|
|
22
|
+
export {
|
|
23
|
+
runDeserializeLocals,
|
|
24
|
+
runRouteRequest,
|
|
25
|
+
runSerializeLocals,
|
|
26
|
+
runWorkerSpawn,
|
|
27
|
+
} from "./src/server/plugins";
|
|
14
28
|
export type { TypegenConfig } from "./src/typegen";
|
|
15
29
|
export { generateTypes } from "./src/typegen";
|
|
16
30
|
export type {
|
|
31
|
+
BuiltinWorkerMode,
|
|
32
|
+
CoordinatorContext,
|
|
17
33
|
GrimoireConfig,
|
|
18
34
|
GrimoirePlugin,
|
|
35
|
+
LayoutServerLoad,
|
|
19
36
|
LoadContext,
|
|
37
|
+
PageServerLoad,
|
|
20
38
|
RenderContext,
|
|
39
|
+
RequestHandler,
|
|
21
40
|
RouteInfo,
|
|
22
41
|
TypedLoadContext,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
42
|
+
WorkerDescriptor,
|
|
43
|
+
WorkerEnv,
|
|
44
|
+
WorkerMode,
|
|
26
45
|
WsRouteHandler,
|
|
27
46
|
} from "./src/types";
|
|
28
47
|
export { defineConfig } from "./src/types";
|
|
29
|
-
export { Head } from "./src/head";
|
package/package.json
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.6.0",
|
|
7
7
|
"devDependencies": {
|
|
8
8
|
"@types/bun": "latest"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
11
|
".": "./index.ts",
|
|
12
|
-
"./client": "./src/enhance.ts",
|
|
12
|
+
"./client": "./src/client/enhance.ts",
|
|
13
13
|
"./headers": "./src/headers.ts",
|
|
14
|
-
"./hooks": "./src/hooks.ts",
|
|
15
|
-
"./vite": "./src/vite
|
|
14
|
+
"./hooks": "./src/server/hooks.ts",
|
|
15
|
+
"./vite": "./src/integrations/vite.ts"
|
|
16
16
|
},
|
|
17
17
|
"bin": {
|
|
18
18
|
"grimoire": "src/sync.ts"
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"@babel/plugin-syntax-jsx": "^8.0.0-rc.6",
|
|
27
27
|
"@babel/plugin-syntax-typescript": "^8.0.0-rc.6",
|
|
28
28
|
"@babel/types": "^8.0.0-rc.6",
|
|
29
|
-
"@sigil-dev/compiler": "0.
|
|
30
|
-
"@sigil-dev/runtime": "0.
|
|
29
|
+
"@sigil-dev/compiler": "0.5.0",
|
|
30
|
+
"@sigil-dev/runtime": "0.5.0",
|
|
31
31
|
"vite": "^8.0.16"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
@@ -42,7 +42,8 @@ export function enhance(
|
|
|
42
42
|
e.preventDefault();
|
|
43
43
|
|
|
44
44
|
const formData = new FormData(form);
|
|
45
|
-
const url =
|
|
45
|
+
const url =
|
|
46
|
+
action ?? form.getAttribute("action") ?? window.location.pathname;
|
|
46
47
|
|
|
47
48
|
try {
|
|
48
49
|
const res = await fetch(url, {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { isAbsolute, join, resolve } from "node:path";
|
|
2
2
|
import type { Plugin } from "vite";
|
|
3
|
-
import { renderRoute } from "
|
|
4
|
-
import { matchRoute } from "
|
|
5
|
-
import { scanRoutes } from "
|
|
3
|
+
import { renderRoute } from "../rendering";
|
|
4
|
+
import { matchRoute } from "../routing/router.ts";
|
|
5
|
+
import { scanRoutes } from "../routing/scanner.ts";
|
|
6
6
|
|
|
7
|
-
const CLIENT_ENTRY = resolve(import.meta.dir, "./
|
|
7
|
+
const CLIENT_ENTRY = resolve(import.meta.dir, "./index.ts");
|
|
8
8
|
|
|
9
9
|
export function grimoire(options: { routes?: string } = {}): Plugin {
|
|
10
10
|
let isBuild = false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { routes } from "#grimoire-routes";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { initRouter } from "../client/router.ts";
|
|
3
|
+
import { withEffectScope } from "../client/scope.ts";
|
|
4
4
|
|
|
5
5
|
const stateEl = document.getElementById("__grimoire_state__");
|
|
6
6
|
let initialDispose: (() => void) | undefined;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { SafeHtml } from "@sigil-dev/runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { findClosestError, type MatchedRoute } from "../routing/router";
|
|
3
|
+
import type { RouteFile } from "../routing/scanner";
|
|
4
|
+
import { isErrorResult } from "../sentinels/error.ts";
|
|
5
|
+
import { isRedirectResult } from "../sentinels/redirect.ts";
|
|
6
|
+
import { runWithContext } from "../server/context";
|
|
7
|
+
import { runHook } from "../server/plugins";
|
|
8
|
+
import type { GrimoirePlugin, LoadContext, Route } from "../types";
|
|
4
9
|
import { collectHead, initHead } from "./head";
|
|
5
|
-
import { runHook } from "./plugins";
|
|
6
|
-
import { isRedirectResult } from "./redirect";
|
|
7
|
-
import { findClosestError, type MatchedRoute } from "./router";
|
|
8
|
-
import type { RouteFile } from "./scanner";
|
|
9
|
-
import type { GrimoirePlugin, LoadContext, Route } from "./types";
|
|
10
10
|
|
|
11
11
|
export type ModuleLoader = (path: string) => Promise<any>;
|
|
12
12
|
|
|
@@ -20,7 +20,10 @@ async function renderErrorPage(
|
|
|
20
20
|
if (!errorPage) return null;
|
|
21
21
|
const mod = await import(errorPage.filePath);
|
|
22
22
|
const html = mod.default({ status, message });
|
|
23
|
-
return new Response(html, {
|
|
23
|
+
return new Response(html, {
|
|
24
|
+
status,
|
|
25
|
+
headers: { "Content-Type": "text/html" },
|
|
26
|
+
});
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export async function renderRoute(
|
|
@@ -64,8 +67,12 @@ export async function renderRoute(
|
|
|
64
67
|
}
|
|
65
68
|
if (isErrorResult(e)) {
|
|
66
69
|
return (
|
|
67
|
-
(await renderErrorPage(
|
|
68
|
-
|
|
70
|
+
(await renderErrorPage(
|
|
71
|
+
errorRoutes,
|
|
72
|
+
context.url.pathname,
|
|
73
|
+
e.status,
|
|
74
|
+
e.message,
|
|
75
|
+
)) ?? new Response(e.message, { status: e.status })
|
|
69
76
|
);
|
|
70
77
|
}
|
|
71
78
|
throw e;
|
|
@@ -86,8 +93,12 @@ export async function renderRoute(
|
|
|
86
93
|
}
|
|
87
94
|
if (isErrorResult(e)) {
|
|
88
95
|
return (
|
|
89
|
-
(await renderErrorPage(
|
|
90
|
-
|
|
96
|
+
(await renderErrorPage(
|
|
97
|
+
errorRoutes,
|
|
98
|
+
context.url.pathname,
|
|
99
|
+
e.status,
|
|
100
|
+
e.message,
|
|
101
|
+
)) ?? new Response(e.message, { status: e.status })
|
|
91
102
|
);
|
|
92
103
|
}
|
|
93
104
|
throw e;
|
|
@@ -118,10 +129,12 @@ export async function renderRoute(
|
|
|
118
129
|
let bodyHtml: string = wrappedPage;
|
|
119
130
|
if (matched.layout) {
|
|
120
131
|
const layoutMod = await import(matched.layout.filePath);
|
|
121
|
-
bodyHtml = String(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
132
|
+
bodyHtml = String(
|
|
133
|
+
layoutMod.default({
|
|
134
|
+
data: layoutData,
|
|
135
|
+
children: new SafeHtml(wrappedPage),
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
125
138
|
}
|
|
126
139
|
|
|
127
140
|
const stateJson = JSON.stringify({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// packages/grimoire/src/ssr-plugin.ts
|
|
2
2
|
import { transformSync } from "@babel/core";
|
|
3
3
|
import sigilPlugin from "@sigil-dev/compiler/babel";
|
|
4
|
-
import type { GrimoirePlugin } from "
|
|
4
|
+
import type { GrimoirePlugin } from "../types";
|
|
5
5
|
|
|
6
6
|
export function registerSSRPlugin(plugins: GrimoirePlugin[] = []) {
|
|
7
7
|
Bun.plugin({
|
|
@@ -19,7 +19,8 @@ export function registerSSRPlugin(plugins: GrimoirePlugin[] = []) {
|
|
|
19
19
|
});
|
|
20
20
|
let contents = transpiler.transformSync(result?.code ?? "");
|
|
21
21
|
for (const plugin of plugins) {
|
|
22
|
-
if (plugin.transform)
|
|
22
|
+
if (plugin.transform)
|
|
23
|
+
contents = (await plugin.transform(contents, path)) ?? contents;
|
|
23
24
|
}
|
|
24
25
|
return {
|
|
25
26
|
contents,
|
|
@@ -93,10 +93,22 @@ export async function scanRoutes(
|
|
|
93
93
|
return aScore - bScore;
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
assertNoDuplicatePaths(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
assertNoDuplicatePaths(
|
|
97
|
+
routes.filter((r) => r.type === "page" || r.type === "simple"),
|
|
98
|
+
"page",
|
|
99
|
+
);
|
|
100
|
+
assertNoDuplicatePaths(
|
|
101
|
+
routes.filter((r) => r.type === "pageServer"),
|
|
102
|
+
"page server",
|
|
103
|
+
);
|
|
104
|
+
assertNoDuplicatePaths(
|
|
105
|
+
layouts.filter((l) => l.type === "layout"),
|
|
106
|
+
"layout",
|
|
107
|
+
);
|
|
108
|
+
assertNoDuplicatePaths(
|
|
109
|
+
layouts.filter((l) => l.type === "layoutServer"),
|
|
110
|
+
"layout server",
|
|
111
|
+
);
|
|
100
112
|
assertNoDuplicatePaths(servers, "API route");
|
|
101
113
|
assertNoDuplicatePaths(errors, "error page");
|
|
102
114
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { transformSync } from "@babel/core";
|
|
2
2
|
import sigilPlugin from "@sigil-dev/compiler/babel";
|
|
3
3
|
import { basename, dirname, join, relative, resolve } from "path";
|
|
4
|
+
import type { GrimoirePlugin } from "../types";
|
|
4
5
|
import type { RouteFile } from "./scanner";
|
|
5
|
-
import type { GrimoirePlugin } from "./types";
|
|
6
6
|
|
|
7
7
|
const STYLE_RE = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
8
8
|
|
|
@@ -80,7 +80,8 @@ export async function transformRoutes(
|
|
|
80
80
|
});
|
|
81
81
|
let out = transpiler.transformSync(res?.code ?? "");
|
|
82
82
|
for (const plugin of plugins) {
|
|
83
|
-
if (plugin.transform)
|
|
83
|
+
if (plugin.transform)
|
|
84
|
+
out = (await plugin.transform(out, route.filePath)) ?? out;
|
|
84
85
|
}
|
|
85
86
|
const rel = relative(process.cwd(), route.filePath)
|
|
86
87
|
.replace(/\.[cm]?[jt]sx?$/, "")
|