@sigil-dev/grimoire 0.7.5 → 0.7.7
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/.grimoire/_routes.dom.js +8 -0
- package/.grimoire/_routes.hydrate.js +8 -0
- package/.grimoire/tsconfig.generated.json +11 -0
- package/.grimoire/types/ambient.d.ts +59 -0
- package/.grimoire/types/api/hello/$types.d.ts +50 -0
- package/.grimoire/types/api/items/$types.d.ts +50 -0
- package/.grimoire/types/echo/$types.d.ts +50 -0
- package/.grimoire/types/env-private.d.ts +5 -0
- package/.grimoire/types/env-public.d.ts +5 -0
- package/.grimoire/types/mixed/$types.d.ts +50 -0
- package/.grimoire/types/params/[docId]/$types.d.ts +52 -0
- package/.grimoire/types/reject/$types.d.ts +50 -0
- package/index.ts +21 -20
- package/package.json +13 -7
- package/preload.js +3 -0
- package/public/__grimoire__/hydrate.js +585 -0
- package/public/__grimoire__/index.js +490 -0
- package/server.ts +13 -13
- package/src/client/head.ts +29 -0
- package/src/client/router.ts +254 -40
- package/src/dev/compile-module.ts +173 -0
- package/src/dev/effect-registry.ts +23 -0
- package/src/dev/graph.ts +114 -0
- package/src/dev/hmr-client.ts +158 -0
- package/src/dev/hmr-server.ts +187 -0
- package/src/dev/loader.ts +47 -0
- package/src/dev/paths.ts +14 -0
- package/src/dev/runtime-bundle.ts +49 -0
- package/src/dev/watcher.ts +44 -0
- package/src/env/index.ts +25 -0
- package/src/env/plugin.ts +13 -0
- package/src/env/private.ts +5 -0
- package/src/env/public.ts +7 -0
- package/src/env/typegen.ts +51 -0
- package/src/integrations/vite.ts +1 -0
- package/src/rendering/head.ts +22 -2
- package/src/rendering/hydrate.ts +111 -18
- package/src/rendering/index.ts +263 -153
- package/src/rendering/ssrPlugin.ts +59 -39
- package/src/routing/manifest-gen.ts +18 -2
- package/src/routing/router.ts +94 -83
- package/src/routing/scanner.ts +26 -14
- package/src/routing/transform-routes.ts +68 -68
- package/src/server/build.ts +225 -76
- package/src/server/coordinator.ts +9 -0
- package/src/server/hooks.ts +24 -3
- package/src/server/index.ts +388 -104
- package/src/typegen/index.ts +30 -14
- package/src/types.ts +12 -2
- package/test/middleware.test.ts +6 -4
- package/test/rendering.test.ts +510 -356
- package/test/routing.test.ts +36 -0
- package/test/scanning.test.ts +39 -8
- package/test/scope.test.ts +24 -8
- package/test/server.test.ts +27 -7
- package/test/streaming.test.ts +117 -98
- package/test/typegen.test.ts +52 -24
- package/tsconfig.json +1 -0
package/src/server/build.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { sigil } from "@sigil-dev/compiler/bun";
|
|
2
2
|
import { mkdir } from "fs/promises";
|
|
3
3
|
import { isAbsolute, join } from "path";
|
|
4
|
+
import { cwd } from "process";
|
|
5
|
+
import { normalizePath } from "../dev/paths";
|
|
4
6
|
import { generateManifest } from "../routing/manifest-gen";
|
|
5
7
|
import type { RouteTree } from "../routing/scanner";
|
|
6
8
|
import { scanRoutes } from "../routing/scanner";
|
|
@@ -9,82 +11,229 @@ import { generateTypes } from "../typegen";
|
|
|
9
11
|
import type { BuildResult, GrimoireConfig, GrimoirePlugin } from "../types";
|
|
10
12
|
import { runHook } from "./plugins";
|
|
11
13
|
|
|
14
|
+
export async function rebuildForChange(
|
|
15
|
+
changedFilePath: string,
|
|
16
|
+
config: GrimoireConfig,
|
|
17
|
+
plugins: GrimoirePlugin[] = [],
|
|
18
|
+
): Promise<{ result: BuildResult }> {
|
|
19
|
+
const { routes = "src/routes" } = config;
|
|
20
|
+
const routesDir = isAbsolute(routes) ? routes : join(process.cwd(), routes);
|
|
21
|
+
|
|
22
|
+
// only retransform the changed file, not all routes
|
|
23
|
+
const tree = await scanRoutes(routesDir, process.cwd());
|
|
24
|
+
const changedRoute = [
|
|
25
|
+
...tree.routes.filter((r) => r.type === "page" || r.type === "simple"),
|
|
26
|
+
...tree.layouts.filter((r) => r.type === "layout"),
|
|
27
|
+
].find((r) => normalizePath(r.filePath) === normalizePath(changedFilePath));
|
|
28
|
+
|
|
29
|
+
const compiledDir = join(process.cwd(), ".grimoire/compiled");
|
|
30
|
+
|
|
31
|
+
if (changedRoute) {
|
|
32
|
+
// retransform only the changed file
|
|
33
|
+
await Promise.all([
|
|
34
|
+
transformRoutes([changedRoute], compiledDir, "hydrate", plugins),
|
|
35
|
+
transformRoutes([changedRoute], compiledDir, "dom", plugins),
|
|
36
|
+
]);
|
|
37
|
+
// update manifests with full route list but new compiled paths
|
|
38
|
+
// (other routes already have compiled files from initial build)
|
|
39
|
+
const allClientRoutes = [
|
|
40
|
+
...tree.routes.filter((r) => r.type === "page" || r.type === "simple"),
|
|
41
|
+
...tree.layouts.filter((r) => r.type === "layout"),
|
|
42
|
+
];
|
|
43
|
+
// rebuild full manifests - cheap
|
|
44
|
+
const hydrateManifest = join(process.cwd(), ".grimoire/_routes.hydrate.js");
|
|
45
|
+
const domManifest = join(process.cwd(), ".grimoire/_routes.dom.js");
|
|
46
|
+
const [allHFiles, allDFiles] = await Promise.all([
|
|
47
|
+
transformRoutes(allClientRoutes, compiledDir, "hydrate", plugins),
|
|
48
|
+
transformRoutes(allClientRoutes, compiledDir, "dom", plugins),
|
|
49
|
+
]);
|
|
50
|
+
await Promise.all([
|
|
51
|
+
Bun.write(hydrateManifest, generateManifest(allClientRoutes, allHFiles)),
|
|
52
|
+
Bun.write(domManifest, generateManifest(allClientRoutes, allDFiles)),
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// still need Bun.build — this is the slow part, Step 3 kills it
|
|
57
|
+
const publicDir = join(process.cwd(), "public/__grimoire__");
|
|
58
|
+
const hydrateManifest = join(process.cwd(), ".grimoire/_routes.hydrate.js");
|
|
59
|
+
const domManifest = join(process.cwd(), ".grimoire/_routes.dom.js");
|
|
60
|
+
const resolvedAlias: Record<string, string> = {
|
|
61
|
+
...Object.fromEntries(
|
|
62
|
+
Object.entries(config.alias ?? {}).map(([k, v]) => [k, join(cwd(), v)]),
|
|
63
|
+
),
|
|
64
|
+
"$env/static/public": join(import.meta.dir, "../env/public.ts"),
|
|
65
|
+
"$env/static/private": join(import.meta.dir, "../env/private.ts"),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const makeRoutesPlugin = (manifestPath: string) => ({
|
|
69
|
+
name: "grimoire-routes",
|
|
70
|
+
setup(build: any) {
|
|
71
|
+
build.onResolve({ filter: /^#grimoire-routes$/ }, () => ({
|
|
72
|
+
path: manifestPath,
|
|
73
|
+
}));
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const [hydrateResult, domResult] = await Promise.all([
|
|
78
|
+
Bun.build({
|
|
79
|
+
entrypoints: [join(import.meta.dir, "../rendering/hydrate.ts")],
|
|
80
|
+
outdir: publicDir,
|
|
81
|
+
plugins: [sigil({ mode: "hydrate" }), makeRoutesPlugin(hydrateManifest)],
|
|
82
|
+
// @ts-expect-error
|
|
83
|
+
alias: resolvedAlias,
|
|
84
|
+
}),
|
|
85
|
+
Bun.build({
|
|
86
|
+
entrypoints: [join(import.meta.dir, "../client/index.ts")],
|
|
87
|
+
outdir: publicDir,
|
|
88
|
+
plugins: [sigil({ mode: "dom" }), makeRoutesPlugin(domManifest)],
|
|
89
|
+
// @ts-expect-error
|
|
90
|
+
alias: resolvedAlias,
|
|
91
|
+
}),
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
result: {
|
|
96
|
+
success: hydrateResult.success && domResult.success,
|
|
97
|
+
outputs: [...hydrateResult.outputs, ...domResult.outputs].map(
|
|
98
|
+
(o) => o.path,
|
|
99
|
+
),
|
|
100
|
+
errors: [...hydrateResult.logs, ...domResult.logs].map(String),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
12
105
|
export async function buildProject(
|
|
13
|
-
|
|
14
|
-
|
|
106
|
+
config: GrimoireConfig,
|
|
107
|
+
plugins: GrimoirePlugin[] = [],
|
|
15
108
|
): Promise<{ result: BuildResult; tree: RouteTree }> {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
await runHook(plugins, "onBuildStart");
|
|
110
|
+
|
|
111
|
+
const { routes = "src/routes" } = config;
|
|
112
|
+
|
|
113
|
+
const routesDir = isAbsolute(routes)
|
|
114
|
+
? routes.replace(/\0/g, "")
|
|
115
|
+
: join(process.cwd(), routes).replace(/\0/g, "");
|
|
116
|
+
const tree = await scanRoutes(routesDir, process.cwd());
|
|
117
|
+
|
|
118
|
+
const outDir = join(process.cwd(), ".grimoire");
|
|
119
|
+
await mkdir(outDir, { recursive: true });
|
|
120
|
+
|
|
121
|
+
await generateTypes(tree, {
|
|
122
|
+
projectRoot: process.cwd(),
|
|
123
|
+
routesDir,
|
|
124
|
+
outDir: join(outDir, "types"),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Generate $env type declarations from .env
|
|
128
|
+
const { generateEnvTypes } = await import("../env/typegen");
|
|
129
|
+
const envTypeDir = join(outDir, "types");
|
|
130
|
+
await mkdir(envTypeDir, { recursive: true });
|
|
131
|
+
await generateEnvTypes(process.cwd(), envTypeDir);
|
|
132
|
+
|
|
133
|
+
const compiledDir = join(outDir, "compiled");
|
|
134
|
+
await mkdir(compiledDir, { recursive: true });
|
|
135
|
+
|
|
136
|
+
const clientRoutes = [
|
|
137
|
+
...tree.routes.filter((r) => r.type === "page" || r.type === "simple"),
|
|
138
|
+
...tree.layouts.filter((r) => r.type === "layout"),
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const [hydrateFiles, domFiles] = await Promise.all([
|
|
142
|
+
transformRoutes(clientRoutes, compiledDir, "hydrate", plugins),
|
|
143
|
+
transformRoutes(clientRoutes, compiledDir, "dom", plugins),
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
const hydrateManifest = join(process.cwd(), ".grimoire/_routes.hydrate.js");
|
|
147
|
+
const domManifest = join(process.cwd(), ".grimoire/_routes.dom.js");
|
|
148
|
+
await Promise.all([
|
|
149
|
+
Bun.write(hydrateManifest, generateManifest(clientRoutes, hydrateFiles)),
|
|
150
|
+
Bun.write(domManifest, generateManifest(clientRoutes, domFiles)),
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
const makeRoutesPlugin = (manifestPath: string) => ({
|
|
154
|
+
name: "grimoire-routes",
|
|
155
|
+
setup(build: any) {
|
|
156
|
+
build.onResolve({ filter: /^#grimoire-routes$/ }, () => ({
|
|
157
|
+
path: manifestPath,
|
|
158
|
+
}));
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const resolvedAlias: Record<string, string> = {
|
|
163
|
+
...Object.fromEntries(
|
|
164
|
+
Object.entries(config.alias ?? {}).map(([k, v]) => [k, join(cwd(), v)]),
|
|
165
|
+
),
|
|
166
|
+
"$env/static/public": join(import.meta.dir, "../env/public.ts"),
|
|
167
|
+
"$env/static/private": join(import.meta.dir, "../env/private.ts"),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const bundleStrategy = config.bundleStrategy ?? "split";
|
|
171
|
+
const publicDir = join(process.cwd(), "public/__grimoire__");
|
|
172
|
+
|
|
173
|
+
if (bundleStrategy === "single") {
|
|
174
|
+
// Single bundle: combine hydrate + dom in one output
|
|
175
|
+
const singleResult = await Bun.build({
|
|
176
|
+
entrypoints: [
|
|
177
|
+
join(import.meta.dir, "../rendering/hydrate.ts"),
|
|
178
|
+
join(import.meta.dir, "../client/index.ts"),
|
|
179
|
+
],
|
|
180
|
+
outdir: publicDir,
|
|
181
|
+
plugins: [
|
|
182
|
+
sigil({ mode: "hydrate" }),
|
|
183
|
+
sigil({ mode: "dom" }),
|
|
184
|
+
makeRoutesPlugin(hydrateManifest),
|
|
185
|
+
makeRoutesPlugin(domManifest),
|
|
186
|
+
],
|
|
187
|
+
// @ts-expect-error alias is a valid Bun.build option not yet in types
|
|
188
|
+
alias: resolvedAlias,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (!singleResult.success) {
|
|
192
|
+
for (const log of singleResult.logs) console.error(log);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const result: BuildResult = {
|
|
196
|
+
success: singleResult.success,
|
|
197
|
+
outputs: singleResult.outputs.map((o) => o.path),
|
|
198
|
+
errors: singleResult.logs.map(String),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
await runHook(plugins, "onBuildEnd", result);
|
|
202
|
+
return { result, tree };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const [hydrateResult, domResult] = await Promise.all([
|
|
206
|
+
Bun.build({
|
|
207
|
+
entrypoints: [join(import.meta.dir, "../rendering/hydrate.ts")],
|
|
208
|
+
outdir: publicDir,
|
|
209
|
+
plugins: [sigil({ mode: "hydrate" }), makeRoutesPlugin(hydrateManifest)],
|
|
210
|
+
// @ts-expect-error alias is a valid Bun.build option not yet in types
|
|
211
|
+
alias: resolvedAlias,
|
|
212
|
+
}),
|
|
213
|
+
Bun.build({
|
|
214
|
+
entrypoints: [join(import.meta.dir, "../client/index.ts")],
|
|
215
|
+
outdir: publicDir,
|
|
216
|
+
plugins: [sigil({ mode: "dom" }), makeRoutesPlugin(domManifest)],
|
|
217
|
+
// @ts-expect-error alias is a valid Bun.build option not yet in types
|
|
218
|
+
alias: resolvedAlias,
|
|
219
|
+
}),
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
if (!hydrateResult.success) {
|
|
223
|
+
for (const log of hydrateResult.logs) console.error(log);
|
|
224
|
+
}
|
|
225
|
+
if (!domResult.success) {
|
|
226
|
+
for (const log of domResult.logs) console.error(log);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const result: BuildResult = {
|
|
230
|
+
success: hydrateResult.success && domResult.success,
|
|
231
|
+
outputs: [...hydrateResult.outputs, ...domResult.outputs].map(
|
|
232
|
+
(o) => o.path,
|
|
233
|
+
),
|
|
234
|
+
errors: [...hydrateResult.logs, ...domResult.logs].map(String),
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
await runHook(plugins, "onBuildEnd", result);
|
|
238
|
+
return { result, tree };
|
|
90
239
|
}
|
|
@@ -168,10 +168,19 @@ export async function startCoordinator(options: CoordinatorOptions) {
|
|
|
168
168
|
const workerEntry = join(import.meta.dir, "./worker.ts");
|
|
169
169
|
if (workerEnv.name) descriptor.name = workerEnv.name;
|
|
170
170
|
|
|
171
|
+
// Inject env vars prefixed with PUBLIC_ so they're available to client bundles
|
|
172
|
+
const publicEnvVars: Record<string, string> = {};
|
|
173
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
174
|
+
if (key.startsWith("PUBLIC_") && value !== undefined) {
|
|
175
|
+
publicEnvVars[key] = value;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
171
179
|
const proc = spawn(["bun", "run", workerEntry], {
|
|
172
180
|
env: {
|
|
173
181
|
...process.env,
|
|
174
182
|
...workerEnv.env,
|
|
183
|
+
...publicEnvVars,
|
|
175
184
|
GRIMOIRE_MODE: mode,
|
|
176
185
|
GRIMOIRE_WORKER_INDEX: String(i),
|
|
177
186
|
GRIMOIRE_WORKER_PORT: String(workerPort),
|
package/src/server/hooks.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SvelteKit-style server hooks.
|
|
3
3
|
*
|
|
4
|
-
* Create a hooks.
|
|
4
|
+
* Create a hooks.server.ts in your project root:
|
|
5
5
|
*
|
|
6
6
|
* import type { Handle } from "@sigil-dev/grimoire/hooks";
|
|
7
7
|
*
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
* return response;
|
|
13
13
|
* };
|
|
14
14
|
*
|
|
15
|
+
* export const handleError: HandleError = ({ error, status, message }) => {
|
|
16
|
+
* console.error(`[${status}] ${message}`, error);
|
|
17
|
+
* };
|
|
18
|
+
*
|
|
15
19
|
* // optional: runs once at server start
|
|
16
20
|
* export const init = () => {
|
|
17
21
|
* console.log("Server started");
|
|
@@ -32,8 +36,10 @@ export interface RequestEvent {
|
|
|
32
36
|
request: Request;
|
|
33
37
|
url: URL;
|
|
34
38
|
params: Record<string, string>;
|
|
35
|
-
locals:
|
|
39
|
+
locals: App.Locals;
|
|
36
40
|
cookies: Cookies;
|
|
41
|
+
route: { id: string };
|
|
42
|
+
fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
37
43
|
setHeaders: (headers: Record<string, string>) => void;
|
|
38
44
|
}
|
|
39
45
|
|
|
@@ -60,6 +66,21 @@ export type Handle = (input: {
|
|
|
60
66
|
resolve: ResolveFunction;
|
|
61
67
|
}) => MaybePromise<Response>;
|
|
62
68
|
|
|
69
|
+
export interface HandleErrorInput {
|
|
70
|
+
error: unknown;
|
|
71
|
+
event: RequestEvent;
|
|
72
|
+
status: number;
|
|
73
|
+
message: string;
|
|
74
|
+
}
|
|
75
|
+
export type HandleError = (input: HandleErrorInput) => void | Promise<void>;
|
|
76
|
+
|
|
77
|
+
export interface HandleFetchInput {
|
|
78
|
+
request: Request;
|
|
79
|
+
fetch: typeof globalThis.fetch;
|
|
80
|
+
event: RequestEvent;
|
|
81
|
+
}
|
|
82
|
+
export type HandleFetch = (input: HandleFetchInput) => MaybePromise<Response>;
|
|
83
|
+
|
|
63
84
|
export type InitFunction = () => void | Promise<void>;
|
|
64
85
|
|
|
65
86
|
/**
|
|
@@ -73,7 +94,7 @@ export function sequence(...handlers: Handle[]): Handle {
|
|
|
73
94
|
const next: ResolveFunction = async (evt) => {
|
|
74
95
|
if (i < handlers.length) {
|
|
75
96
|
const handler = handlers[i++];
|
|
76
|
-
return handler({ event: evt, resolve: next });
|
|
97
|
+
return handler!({ event: evt, resolve: next });
|
|
77
98
|
}
|
|
78
99
|
return resolve(evt);
|
|
79
100
|
};
|