@sigil-dev/grimoire 0.7.6 → 0.8.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.
Files changed (41) hide show
  1. package/index.ts +35 -34
  2. package/package.json +8 -6
  3. package/preload.js +3 -2
  4. package/server.ts +13 -13
  5. package/src/client/head.ts +29 -29
  6. package/src/client/router.ts +120 -53
  7. package/src/dev/compile-module.ts +173 -0
  8. package/src/dev/effect-registry.ts +23 -0
  9. package/src/dev/graph.ts +114 -0
  10. package/src/dev/hmr-client.ts +158 -0
  11. package/src/dev/hmr-server.ts +187 -0
  12. package/src/dev/loader.ts +47 -0
  13. package/src/dev/paths.ts +14 -0
  14. package/src/dev/runtime-bundle.ts +49 -0
  15. package/src/dev/watcher.ts +44 -0
  16. package/src/integrations/vite.ts +73 -72
  17. package/src/rendering/hydrate.ts +102 -64
  18. package/src/rendering/index.ts +296 -199
  19. package/src/rendering/ssrPlugin.ts +67 -53
  20. package/src/routing/manifest-gen.ts +42 -39
  21. package/src/routing/router.ts +109 -106
  22. package/src/routing/scanner.ts +141 -135
  23. package/src/routing/transform-routes.ts +101 -101
  24. package/src/server/build.ts +239 -147
  25. package/src/server/coordinator.ts +306 -306
  26. package/src/server/index.ts +260 -50
  27. package/src/server/worker.ts +59 -59
  28. package/src/typegen/index.ts +356 -353
  29. package/src/types.ts +270 -269
  30. package/test/context.test.ts +52 -52
  31. package/test/hydration.test.ts +119 -119
  32. package/test/middleware.test.ts +223 -223
  33. package/test/rendering.test.ts +579 -425
  34. package/test/routing.test.ts +81 -83
  35. package/test/scanning.test.ts +200 -181
  36. package/test/scope.test.ts +24 -8
  37. package/test/server.test.ts +249 -229
  38. package/test/streaming.test.ts +125 -106
  39. package/test/transform-routes.test.ts +84 -84
  40. package/test/typegen.test.ts +35 -25
  41. package/tsconfig.json +1 -0
@@ -1,72 +1,73 @@
1
- import { isAbsolute, join, resolve } from "node:path";
2
- import type { Plugin } from "vite";
3
- import { renderRoute } from "../rendering";
4
- import { matchRoute } from "../routing/router.ts";
5
- import { scanRoutes } from "../routing/scanner.ts";
6
-
7
- const CLIENT_ENTRY = resolve(import.meta.dir, "./index.ts");
8
-
9
- export function grimoire(options: { routes?: string } = {}): Plugin {
10
- let isBuild = false;
11
-
12
- return {
13
- name: "grimoire",
14
-
15
- configResolved(config) {
16
- isBuild = config.command === "build";
17
- },
18
-
19
- configureServer(vite) {
20
- const routesDir = isAbsolute(options.routes ?? "src/routes")
21
- ? options.routes!
22
- : join(process.cwd(), options.routes ?? "src/routes");
23
-
24
- // client entry
25
- vite.middlewares.use("/__grimoire__/client.js", async (req, res) => {
26
- const result = await vite.transformRequest(CLIENT_ENTRY);
27
- if (!result) {
28
- res.statusCode = 404;
29
- res.end();
30
- return;
31
- }
32
- res.setHeader("Content-Type", "application/javascript");
33
- res.end(result.code);
34
- });
35
-
36
- // page routes
37
- vite.middlewares.use(async (req, res, next) => {
38
- const url = new URL(req.url!, "http://localhost");
39
- if (url.pathname.startsWith("/__") || url.pathname.includes(".")) {
40
- return next();
41
- }
42
- try {
43
- const tree = await scanRoutes(routesDir, process.cwd());
44
- const matched = matchRoute(tree, url);
45
- if (!matched) return next();
46
-
47
- const response = await renderRoute(
48
- matched,
49
- new Request(`http://localhost${req.url}`),
50
- (path) => vite.ssrLoadModule(path), // Vite transforms SSR files correctly
51
- );
52
-
53
- const html = await response.text();
54
- res.setHeader("Content-Type", "text/html");
55
- res.end(html);
56
- } catch (e) {
57
- vite.ssrFixStacktrace(e as Error);
58
- next(e);
59
- }
60
- });
61
- },
62
-
63
- buildStart() {
64
- if (!isBuild) return;
65
- this.emitFile({
66
- type: "chunk",
67
- id: CLIENT_ENTRY,
68
- fileName: "client.js",
69
- });
70
- },
71
- };
72
- }
1
+ import { isAbsolute, join, resolve } from "node:path";
2
+ import type { Plugin } from "vite";
3
+ import { renderRoute } from "../rendering";
4
+ import { matchRoute } from "../routing/router.ts";
5
+ import { scanRoutes } from "../routing/scanner.ts";
6
+
7
+ const CLIENT_ENTRY = resolve(import.meta.dir, "./index.ts");
8
+
9
+ export function grimoire(options: { routes?: string } = {}): Plugin {
10
+ let isBuild = false;
11
+
12
+ return {
13
+ name: "grimoire",
14
+
15
+ configResolved(config) {
16
+ isBuild = config.command === "build";
17
+ },
18
+
19
+ configureServer(vite) {
20
+ const routesDir = isAbsolute(options.routes ?? "src/routes")
21
+ ? options.routes!
22
+ : join(process.cwd(), options.routes ?? "src/routes");
23
+
24
+ // client entry
25
+ vite.middlewares.use("/__grimoire__/client.js", async (req, res) => {
26
+ const result = await vite.transformRequest(CLIENT_ENTRY);
27
+ if (!result) {
28
+ res.statusCode = 404;
29
+ res.end();
30
+ return;
31
+ }
32
+ res.setHeader("Content-Type", "application/javascript");
33
+ res.end(result.code);
34
+ });
35
+
36
+ // page routes
37
+ vite.middlewares.use(async (req, res, next) => {
38
+ const url = new URL(req.url!, "http://localhost");
39
+ if (url.pathname.startsWith("/__") || url.pathname.includes(".")) {
40
+ return next();
41
+ }
42
+ try {
43
+ const tree = await scanRoutes(routesDir, process.cwd());
44
+ const matched = matchRoute(tree, url);
45
+ if (!matched) return next();
46
+
47
+ const response = await renderRoute(
48
+ matched,
49
+ new Request(`http://localhost${req.url}`),
50
+ [],
51
+ (path) => vite.ssrLoadModule(path), // Vite transforms SSR files correctly
52
+ );
53
+
54
+ const html = await response.text();
55
+ res.setHeader("Content-Type", "text/html");
56
+ res.end(html);
57
+ } catch (e) {
58
+ vite.ssrFixStacktrace(e as Error);
59
+ next(e);
60
+ }
61
+ });
62
+ },
63
+
64
+ buildStart() {
65
+ if (!isBuild) return;
66
+ this.emitFile({
67
+ type: "chunk",
68
+ id: CLIENT_ENTRY,
69
+ fileName: "client.js",
70
+ });
71
+ },
72
+ };
73
+ }
@@ -1,81 +1,119 @@
1
- import { routes, layouts } from "#grimoire-routes";
2
- import { popHydrationNodes, pushHydrationNodes, claim, insert, getHydrationNodes } from "@sigil-dev/runtime";
1
+ import {
2
+ claim,
3
+ getHydrationNodes,
4
+ insert,
5
+ popHydrationNodes,
6
+ pushHydrationNodes,
7
+ } from "@sigil-dev/runtime";
8
+ //@ts-expect-error compiler generated
9
+ import { layouts, routes } from "#grimoire-routes";
10
+ import {
11
+ afterNavigate,
12
+ beforeNavigate,
13
+ navigate,
14
+ onNavigate,
15
+ } from "../client/router";
3
16
  import { initRouter } from "../client/router.ts";
4
17
  import { withEffectScope } from "../client/scope.ts";
18
+ import { Head } from "./head";
19
+
20
+ // expose for HMR module shim
21
+ (globalThis as any).__grimoire_Head__ = Head;
22
+ (globalThis as any).__grimoire_navigate__ = navigate;
23
+ (globalThis as any).__grimoire_beforeNavigate__ = beforeNavigate;
24
+ (globalThis as any).__grimoire_onNavigate__ = onNavigate;
25
+ (globalThis as any).__grimoire_afterNavigate__ = afterNavigate;
5
26
 
6
27
  const stateEl = document.getElementById("__grimoire_state__");
7
28
  let initialDispose: (() => void) | undefined;
8
29
 
9
30
  if (stateEl) {
10
- const state = JSON.parse(stateEl.textContent!);
11
- const Page = routes[state.pattern];
12
- if (Page) {
13
- const slot = document.getElementById("grimoire-root");
14
- if (slot) {
15
- const matchedLayouts = layouts
16
- .filter((l: any) => l.path === "/" || state.pattern === l.path || state.pattern.startsWith(l.path + "/"))
17
- .sort((a: any, b: any) => a.path.length - b.path.length);
31
+ const state = JSON.parse(stateEl.textContent!);
32
+ const Page = routes[state.pattern];
33
+ if (Page) {
34
+ const slot = document.getElementById("grimoire-root");
35
+ if (slot) {
36
+ const matchedLayouts = layouts
37
+ .filter(
38
+ (l: any) =>
39
+ l.path === "/" ||
40
+ state.pattern === l.path ||
41
+ state.pattern.startsWith(l.path + "/"),
42
+ )
43
+ .sort((a: any, b: any) => a.path.length - b.path.length);
18
44
 
19
- // When layouts are involved, nested <!--g--> delimiters in the flat SSR pool
20
- // cause anchor-mount to claim the wrong anchor and remove layout DOM nodes.
21
- // Clear SSR content and re-render in DOM mode (empty pool) instead.
22
- const hasLayouts = matchedLayouts.length > 0;
23
- if (hasLayouts) {
24
- slot.replaceChildren();
25
- }
45
+ // When layouts are involved, nested <!--g--> delimiters in the flat SSR pool
46
+ // cause anchor-mount to claim the wrong anchor and remove layout DOM nodes.
47
+ // Clear SSR content and re-render in DOM mode (empty pool) instead.
48
+ const hasLayouts = matchedLayouts.length > 0;
49
+ if (hasLayouts) {
50
+ slot.replaceChildren();
51
+ }
26
52
 
27
- const ssrClones = hasLayouts ? [] : Array.from(slot.childNodes).map(n => n.cloneNode(true));
28
- pushHydrationNodes(hasLayouts ? [] : Array.from(slot.childNodes) as ChildNode[]);
29
- try {
30
- initialDispose = withEffectScope(() => {
31
- try {
32
- let renderFn = () => {
33
- const pageDiv = claim(getHydrationNodes(), "div");
34
- pageDiv.id = "grimoire-page";
53
+ const ssrClones = hasLayouts
54
+ ? []
55
+ : Array.from(slot.childNodes).map((n) => n.cloneNode(true));
56
+ pushHydrationNodes(
57
+ hasLayouts ? [] : (Array.from(slot.childNodes) as ChildNode[]),
58
+ );
59
+ try {
60
+ initialDispose = withEffectScope(() => {
61
+ try {
62
+ let renderFn = () => {
63
+ const pageDiv = claim(getHydrationNodes(), "div");
64
+ pageDiv.id = "grimoire-page";
35
65
 
36
- if (pageDiv.childNodes.length > 0) {
37
- pushHydrationNodes(Array.from(pageDiv.childNodes) as ChildNode[]);
38
- const pageNode = Page({ data: state.data, params: state.params });
39
- popHydrationNodes();
40
- insert(pageDiv, pageNode);
41
- } else {
42
- const pageNode = Page({ data: state.data, params: state.params });
43
- insert(pageDiv, pageNode);
44
- }
66
+ if (pageDiv.childNodes.length > 0) {
67
+ pushHydrationNodes(
68
+ Array.from(pageDiv.childNodes) as ChildNode[],
69
+ );
70
+ const pageNode = Page({
71
+ data: state.data,
72
+ params: state.params,
73
+ });
74
+ popHydrationNodes();
75
+ insert(pageDiv, pageNode);
76
+ } else {
77
+ const pageNode = Page({
78
+ data: state.data,
79
+ params: state.params,
80
+ });
81
+ insert(pageDiv, pageNode);
82
+ }
45
83
 
46
- return pageDiv;
47
- };
84
+ return pageDiv;
85
+ };
48
86
 
49
- for (let i = matchedLayouts.length - 1; i >= 0; i--) {
50
- const LayoutComponent = matchedLayouts[i].component;
51
- const innerRender = renderFn;
52
- const layoutData = state.layoutData?.[i];
53
- renderFn = () => {
54
- // Pre-render inner content so children is a Node (insert() doesn't call functions)
55
- const childNode = innerRender();
56
- return LayoutComponent({
57
- data: layoutData,
58
- params: state.params,
59
- children: childNode,
60
- });
61
- };
62
- }
87
+ for (let i = matchedLayouts.length - 1; i >= 0; i--) {
88
+ const LayoutComponent = matchedLayouts[i].component;
89
+ const innerRender = renderFn;
90
+ const layoutData = state.layoutData?.[i];
91
+ renderFn = () => {
92
+ // Pre-render inner content so children is a Node (insert() doesn't call functions)
93
+ const childNode = innerRender();
94
+ return LayoutComponent({
95
+ data: layoutData,
96
+ params: state.params,
97
+ children: childNode,
98
+ });
99
+ };
100
+ }
63
101
 
64
- const rootNode = renderFn();
65
- insert(slot, rootNode);
66
- } finally {
67
- popHydrationNodes();
68
- }
69
- });
70
- } catch (e) {
71
- console.warn("[grimoire] hydration error:", e);
72
- }
102
+ const rootNode = renderFn();
103
+ insert(slot, rootNode);
104
+ } finally {
105
+ popHydrationNodes();
106
+ }
107
+ });
108
+ } catch (e) {
109
+ console.warn("[grimoire] hydration error:", e);
110
+ }
73
111
 
74
- if (!slot.hasChildNodes() && ssrClones.length > 0) {
75
- slot.replaceChildren(...ssrClones);
76
- }
77
- }
78
- }
112
+ if (!slot.hasChildNodes() && ssrClones.length > 0) {
113
+ slot.replaceChildren(...ssrClones);
114
+ }
115
+ }
116
+ }
79
117
  }
80
118
 
81
119
  initRouter(routes, layouts, initialDispose);