@vaadin/hilla-file-router 24.4.0-alpha19 → 24.4.0-alpha21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/hilla-file-router",
3
- "version": "24.4.0-alpha19",
3
+ "version": "24.4.0-alpha21",
4
4
  "description": "Hilla file-based router",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -80,7 +80,8 @@
80
80
  "type-fest": "^4.9.0"
81
81
  },
82
82
  "dependencies": {
83
- "@vaadin/hilla-generator-utils": "24.4.0-alpha19",
83
+ "@vaadin/hilla-generator-utils": "24.4.0-alpha21",
84
+ "@vaadin/hilla-react-auth": "24.4.0-alpha21",
84
85
  "react": "^18.2.0",
85
86
  "rollup": "^4.12.0",
86
87
  "typescript": "5.3.2"
@@ -0,0 +1,46 @@
1
+ import { type ComponentType } from 'react';
2
+ import { type RouteObject } from 'react-router-dom';
3
+ import type { AgnosticRoute, RouterConfiguration } from '../types.js';
4
+ /**
5
+ * A builder for creating a Vaadin-specific router for React with
6
+ * authentication and server routes support.
7
+ */
8
+ export declare class RouterConfigurationBuilder {
9
+ #private;
10
+ /**
11
+ * Adds the given routes to the current list of routes. All the routes are
12
+ * deeply merged to preserve the path uniqueness.
13
+ *
14
+ * @param routes - A list of routes to add to the current list.
15
+ */
16
+ withReactRoutes(...routes: readonly RouteObject[]): this;
17
+ /**
18
+ * Adds the given file routes to the current list of routes. All the routes
19
+ * are transformed to React RouterObjects and deeply merged to preserve the
20
+ * path uniqueness.
21
+ *
22
+ * @param routes - A list of routes to add to the current list.
23
+ */
24
+ withFileRoutes(...routes: readonly AgnosticRoute[]): this;
25
+ /**
26
+ * Adds the given server route element to each branch of the current list of
27
+ * routes.
28
+ *
29
+ * @param component - The React component to add to each branch of the
30
+ * current list of routes.
31
+ */
32
+ withFallback(component: ComponentType): this;
33
+ /**
34
+ * Protects all the routes that require authentication. For more details see
35
+ * {@link @vaadin/hilla-react-auth#protectRoutes} function.
36
+ *
37
+ * @param redirectPath - the path to redirect to if the route is protected
38
+ * and the user is not authenticated.
39
+ */
40
+ protect(redirectPath?: string): this;
41
+ /**
42
+ * Builds the router with the current list of routes.
43
+ */
44
+ build(): RouterConfiguration;
45
+ }
46
+ //# sourceMappingURL=RouterConfigurationBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RouterConfigurationBuilder.d.ts","sourceRoot":"","sources":["../src/runtime/RouterConfigurationBuilder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAoC,MAAM,OAAO,CAAC;AAC7E,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AA4BtE;;;GAGG;AACH,qBAAa,0BAA0B;;IAIrC;;;;;OAKG;IACH,eAAe,CAAC,GAAG,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI;IAYxD;;;;;;OAMG;IACH,cAAc,CAAC,GAAG,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI;IAazD;;;;;;OAMG;IACH,YAAY,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI;IA2B5C;;;;;;OAMG;IACH,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI;IAKpC;;OAEG;IACH,KAAK,IAAI,mBAAmB;CAS7B"}
@@ -0,0 +1,110 @@
1
+ import { protectRoutes } from "@vaadin/hilla-react-auth";
2
+ import { createElement } from "react";
3
+ import { createBrowserRouter } from "react-router-dom";
4
+ import { transformTreeSync } from "../shared/transformTree.js";
5
+ import { toReactRouter } from "./toReactRouter.js";
6
+ function mergeRoutes(a, b) {
7
+ return b.reduce(
8
+ (result, route) => {
9
+ const existingRoute = result.find((r) => r.path === route.path);
10
+ if (existingRoute) {
11
+ Object.assign(existingRoute, route);
12
+ existingRoute.children = existingRoute.children ? mergeRoutes(existingRoute.children, route.children ?? []) : route.children;
13
+ } else {
14
+ result.push(route);
15
+ }
16
+ return result;
17
+ },
18
+ [...a]
19
+ );
20
+ }
21
+ class RouterConfigurationBuilder {
22
+ #initializers = [];
23
+ #finalizers = [];
24
+ /**
25
+ * Adds the given routes to the current list of routes. All the routes are
26
+ * deeply merged to preserve the path uniqueness.
27
+ *
28
+ * @param routes - A list of routes to add to the current list.
29
+ */
30
+ withReactRoutes(...routes) {
31
+ this.#initializers.push((existingRoutes) => {
32
+ if (!existingRoutes.length) {
33
+ return routes;
34
+ }
35
+ return mergeRoutes(existingRoutes, routes);
36
+ });
37
+ return this;
38
+ }
39
+ /**
40
+ * Adds the given file routes to the current list of routes. All the routes
41
+ * are transformed to React RouterObjects and deeply merged to preserve the
42
+ * path uniqueness.
43
+ *
44
+ * @param routes - A list of routes to add to the current list.
45
+ */
46
+ withFileRoutes(...routes) {
47
+ this.#initializers.push((existingRoutes) => {
48
+ const reactRoutes = routes.map(toReactRouter);
49
+ if (!existingRoutes.length) {
50
+ return reactRoutes;
51
+ }
52
+ return mergeRoutes(existingRoutes, reactRoutes);
53
+ });
54
+ return this;
55
+ }
56
+ /**
57
+ * Adds the given server route element to each branch of the current list of
58
+ * routes.
59
+ *
60
+ * @param component - The React component to add to each branch of the
61
+ * current list of routes.
62
+ */
63
+ withFallback(component) {
64
+ this.#finalizers.push((existingRoutes) => {
65
+ const createServerRoute = () => ({ path: "*", element: createElement(component) });
66
+ const newRoutes = existingRoutes.map(
67
+ (route) => transformTreeSync(
68
+ route,
69
+ (r) => r.children?.values(),
70
+ (r, children) => children ? (
71
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
72
+ {
73
+ ...r,
74
+ children: [...children, createServerRoute()]
75
+ }
76
+ ) : r
77
+ )
78
+ );
79
+ newRoutes.push(createServerRoute());
80
+ return newRoutes;
81
+ });
82
+ return this;
83
+ }
84
+ /**
85
+ * Protects all the routes that require authentication. For more details see
86
+ * {@link @vaadin/hilla-react-auth#protectRoutes} function.
87
+ *
88
+ * @param redirectPath - the path to redirect to if the route is protected
89
+ * and the user is not authenticated.
90
+ */
91
+ protect(redirectPath) {
92
+ this.#finalizers.push((existingRoutes) => protectRoutes(existingRoutes, redirectPath));
93
+ return this;
94
+ }
95
+ /**
96
+ * Builds the router with the current list of routes.
97
+ */
98
+ build() {
99
+ let routes = this.#initializers.reduce((acc, callback) => callback(acc), []);
100
+ routes = this.#finalizers.reduce((acc, callback) => callback(acc), routes);
101
+ return {
102
+ routes,
103
+ router: createBrowserRouter([...routes])
104
+ };
105
+ }
106
+ }
107
+ export {
108
+ RouterConfigurationBuilder
109
+ };
110
+ //# sourceMappingURL=RouterConfigurationBuilder.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/runtime/RouterConfigurationBuilder.ts"],
4
+ "sourcesContent": ["import { protectRoutes, type RouteObjectWithAuth } from '@vaadin/hilla-react-auth';\nimport { type ComponentType, createElement, type ReactElement } from 'react';\nimport { createBrowserRouter, type RouteObject } from 'react-router-dom';\nimport { transformTreeSync } from '../shared/transformTree.js';\nimport type { AgnosticRoute, RouterConfiguration } from '../types.js';\nimport { toReactRouter } from './toReactRouter.js';\n\n/**\n * Deeply merges two lists of routes. If the specific path is already present,\n * the route is merged, otherwise the new routes are added to the list.\n *\n * @param a - The first list of routes.\n * @param b - The second list of routes.\n */\nfunction mergeRoutes(a: readonly RouteObject[], b: readonly RouteObject[]): RouteObject[] {\n return b.reduce(\n (result, route) => {\n const existingRoute = result.find((r) => r.path === route.path);\n if (existingRoute) {\n Object.assign(existingRoute, route);\n existingRoute.children = existingRoute.children\n ? mergeRoutes(existingRoute.children, route.children ?? [])\n : route.children;\n } else {\n result.push(route);\n }\n return result;\n },\n [...a],\n );\n}\n\n/**\n * A builder for creating a Vaadin-specific router for React with\n * authentication and server routes support.\n */\nexport class RouterConfigurationBuilder {\n readonly #initializers: Array<(routes: readonly RouteObject[]) => readonly RouteObject[]> = [];\n readonly #finalizers: Array<(routes: readonly RouteObject[]) => readonly RouteObject[]> = [];\n\n /**\n * Adds the given routes to the current list of routes. All the routes are\n * deeply merged to preserve the path uniqueness.\n *\n * @param routes - A list of routes to add to the current list.\n */\n withReactRoutes(...routes: readonly RouteObject[]): this {\n this.#initializers.push((existingRoutes) => {\n if (!existingRoutes.length) {\n return routes;\n }\n\n return mergeRoutes(existingRoutes, routes);\n });\n\n return this;\n }\n\n /**\n * Adds the given file routes to the current list of routes. All the routes\n * are transformed to React RouterObjects and deeply merged to preserve the\n * path uniqueness.\n *\n * @param routes - A list of routes to add to the current list.\n */\n withFileRoutes(...routes: readonly AgnosticRoute[]): this {\n this.#initializers.push((existingRoutes) => {\n const reactRoutes = routes.map(toReactRouter);\n\n if (!existingRoutes.length) {\n return reactRoutes;\n }\n\n return mergeRoutes(existingRoutes, reactRoutes);\n });\n return this;\n }\n\n /**\n * Adds the given server route element to each branch of the current list of\n * routes.\n *\n * @param component - The React component to add to each branch of the\n * current list of routes.\n */\n withFallback(component: ComponentType): this {\n this.#finalizers.push((existingRoutes) => {\n const createServerRoute = () => ({ path: '*', element: createElement(component) });\n\n const newRoutes = existingRoutes.map((route) =>\n transformTreeSync<RouteObject, RouteObject>(\n route,\n (r) => r.children?.values(),\n (r, children) =>\n children\n ? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n ({\n ...r,\n children: [...children, createServerRoute()],\n } as RouteObject)\n : r,\n ),\n );\n\n newRoutes.push(createServerRoute());\n\n return newRoutes;\n });\n\n return this;\n }\n\n /**\n * Protects all the routes that require authentication. For more details see\n * {@link @vaadin/hilla-react-auth#protectRoutes} function.\n *\n * @param redirectPath - the path to redirect to if the route is protected\n * and the user is not authenticated.\n */\n protect(redirectPath?: string): this {\n this.#finalizers.push((existingRoutes) => protectRoutes(existingRoutes as RouteObjectWithAuth[], redirectPath));\n return this;\n }\n\n /**\n * Builds the router with the current list of routes.\n */\n build(): RouterConfiguration {\n let routes = this.#initializers.reduce<readonly RouteObject[]>((acc, callback) => callback(acc), []);\n routes = this.#finalizers.reduce<readonly RouteObject[]>((acc, callback) => callback(acc), routes);\n\n return {\n routes,\n router: createBrowserRouter([...routes]),\n };\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,qBAA+C;AACxD,SAA6B,qBAAwC;AACrE,SAAS,2BAA6C;AACtD,SAAS,yBAAyB;AAElC,SAAS,qBAAqB;AAS9B,SAAS,YAAY,GAA2B,GAA0C;AACxF,SAAO,EAAE;AAAA,IACP,CAAC,QAAQ,UAAU;AACjB,YAAM,gBAAgB,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,IAAI;AAC9D,UAAI,eAAe;AACjB,eAAO,OAAO,eAAe,KAAK;AAClC,sBAAc,WAAW,cAAc,WACnC,YAAY,cAAc,UAAU,MAAM,YAAY,CAAC,CAAC,IACxD,MAAM;AAAA,MACZ,OAAO;AACL,eAAO,KAAK,KAAK;AAAA,MACnB;AACA,aAAO;AAAA,IACT;AAAA,IACA,CAAC,GAAG,CAAC;AAAA,EACP;AACF;AAMO,MAAM,2BAA2B;AAAA,EAC7B,gBAAmF,CAAC;AAAA,EACpF,cAAiF,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ3F,mBAAmB,QAAsC;AACvD,SAAK,cAAc,KAAK,CAAC,mBAAmB;AAC1C,UAAI,CAAC,eAAe,QAAQ;AAC1B,eAAO;AAAA,MACT;AAEA,aAAO,YAAY,gBAAgB,MAAM;AAAA,IAC3C,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,kBAAkB,QAAwC;AACxD,SAAK,cAAc,KAAK,CAAC,mBAAmB;AAC1C,YAAM,cAAc,OAAO,IAAI,aAAa;AAE5C,UAAI,CAAC,eAAe,QAAQ;AAC1B,eAAO;AAAA,MACT;AAEA,aAAO,YAAY,gBAAgB,WAAW;AAAA,IAChD,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAa,WAAgC;AAC3C,SAAK,YAAY,KAAK,CAAC,mBAAmB;AACxC,YAAM,oBAAoB,OAAO,EAAE,MAAM,KAAK,SAAS,cAAc,SAAS,EAAE;AAEhF,YAAM,YAAY,eAAe;AAAA,QAAI,CAAC,UACpC;AAAA,UACE;AAAA,UACA,CAAC,MAAM,EAAE,UAAU,OAAO;AAAA,UAC1B,CAAC,GAAG,aACF;AAAA;AAAA,YAEK;AAAA,cACC,GAAG;AAAA,cACH,UAAU,CAAC,GAAG,UAAU,kBAAkB,CAAC;AAAA,YAC7C;AAAA,cACA;AAAA,QACR;AAAA,MACF;AAEA,gBAAU,KAAK,kBAAkB,CAAC;AAElC,aAAO;AAAA,IACT,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAQ,cAA6B;AACnC,SAAK,YAAY,KAAK,CAAC,mBAAmB,cAAc,gBAAyC,YAAY,CAAC;AAC9G,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,QAA6B;AAC3B,QAAI,SAAS,KAAK,cAAc,OAA+B,CAAC,KAAK,aAAa,SAAS,GAAG,GAAG,CAAC,CAAC;AACnG,aAAS,KAAK,YAAY,OAA+B,CAAC,KAAK,aAAa,SAAS,GAAG,GAAG,MAAM;AAEjG,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,oBAAoB,CAAC,GAAG,MAAM,CAAC;AAAA,IACzC;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -16,7 +16,7 @@ function toReactRouter(routes) {
16
16
  return {
17
17
  path: module?.config?.route ?? path,
18
18
  element: module?.default ? createElement(module.default) : void 0,
19
- children: children.length > 0 ? children : void 0,
19
+ children: children && children.length > 0 ? children : void 0,
20
20
  handle: {
21
21
  ...module?.config,
22
22
  title
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/runtime/toReactRouter.ts"],
4
- "sourcesContent": ["import { type ComponentType, createElement } from 'react';\nimport type { RouteObject as ReactRouteObject } from 'react-router-dom';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport { transformTreeSync } from '../shared/transformTree.js';\nimport type { AgnosticRoute, Module, RouteModule } from '../types.js';\n\nfunction isReactRouteModule(module?: Module): module is RouteModule<ComponentType> | undefined {\n return module ? 'default' in module && typeof module.default === 'function' : true;\n}\n\n/**\n * Transforms framework-agnostic route tree into a format that can be used by React Router.\n *\n * @param routes - Generated routes\n *\n * @returns The React Router tree.\n */\nexport function toReactRouter(routes: AgnosticRoute): ReactRouteObject {\n return transformTreeSync(\n routes,\n (route) => route.children?.values(),\n ({ path, module }, children) => {\n if (!isReactRouteModule(module)) {\n throw new Error(`The module for the \"${path}\" section doesn't have the React component exported by default`);\n }\n\n const title = module?.config?.title ?? convertComponentNameToTitle(module?.default);\n\n return {\n path: module?.config?.route ?? path,\n element: module?.default ? createElement(module.default) : undefined,\n children: children.length > 0 ? (children as ReactRouteObject[]) : undefined,\n handle: {\n ...module?.config,\n title,\n },\n } satisfies ReactRouteObject;\n },\n );\n}\n"],
5
- "mappings": "AAAA,SAA6B,qBAAqB;AAElD,SAAS,mCAAmC;AAC5C,SAAS,yBAAyB;AAGlC,SAAS,mBAAmB,QAAmE;AAC7F,SAAO,SAAS,aAAa,UAAU,OAAO,OAAO,YAAY,aAAa;AAChF;AASO,SAAS,cAAc,QAAyC;AACrE,SAAO;AAAA,IACL;AAAA,IACA,CAAC,UAAU,MAAM,UAAU,OAAO;AAAA,IAClC,CAAC,EAAE,MAAM,OAAO,GAAG,aAAa;AAC9B,UAAI,CAAC,mBAAmB,MAAM,GAAG;AAC/B,cAAM,IAAI,MAAM,uBAAuB,IAAI,gEAAgE;AAAA,MAC7G;AAEA,YAAM,QAAQ,QAAQ,QAAQ,SAAS,4BAA4B,QAAQ,OAAO;AAElF,aAAO;AAAA,QACL,MAAM,QAAQ,QAAQ,SAAS;AAAA,QAC/B,SAAS,QAAQ,UAAU,cAAc,OAAO,OAAO,IAAI;AAAA,QAC3D,UAAU,SAAS,SAAS,IAAK,WAAkC;AAAA,QACnE,QAAQ;AAAA,UACN,GAAG,QAAQ;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { type ComponentType, createElement } from 'react';\nimport type { RouteObject as ReactRouteObject } from 'react-router-dom';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport { transformTreeSync } from '../shared/transformTree.js';\nimport type { AgnosticRoute, Module, RouteModule } from '../types.js';\n\nfunction isReactRouteModule(module?: Module): module is RouteModule<ComponentType> | undefined {\n return module ? 'default' in module && typeof module.default === 'function' : true;\n}\n\n/**\n * Transforms framework-agnostic route tree into a format that can be used by React Router.\n *\n * @param routes - Generated routes\n *\n * @returns The React Router tree.\n */\nexport function toReactRouter(routes: AgnosticRoute): ReactRouteObject {\n return transformTreeSync(\n routes,\n (route) => route.children?.values(),\n ({ path, module }, children) => {\n if (!isReactRouteModule(module)) {\n throw new Error(`The module for the \"${path}\" section doesn't have the React component exported by default`);\n }\n\n const title = module?.config?.title ?? convertComponentNameToTitle(module?.default);\n\n return {\n path: module?.config?.route ?? path,\n element: module?.default ? createElement(module.default) : undefined,\n children: children && children.length > 0 ? (children as ReactRouteObject[]) : undefined,\n handle: {\n ...module?.config,\n title,\n },\n } satisfies ReactRouteObject;\n },\n );\n}\n"],
5
+ "mappings": "AAAA,SAA6B,qBAAqB;AAElD,SAAS,mCAAmC;AAC5C,SAAS,yBAAyB;AAGlC,SAAS,mBAAmB,QAAmE;AAC7F,SAAO,SAAS,aAAa,UAAU,OAAO,OAAO,YAAY,aAAa;AAChF;AASO,SAAS,cAAc,QAAyC;AACrE,SAAO;AAAA,IACL;AAAA,IACA,CAAC,UAAU,MAAM,UAAU,OAAO;AAAA,IAClC,CAAC,EAAE,MAAM,OAAO,GAAG,aAAa;AAC9B,UAAI,CAAC,mBAAmB,MAAM,GAAG;AAC/B,cAAM,IAAI,MAAM,uBAAuB,IAAI,gEAAgE;AAAA,MAC7G;AAEA,YAAM,QAAQ,QAAQ,QAAQ,SAAS,4BAA4B,QAAQ,OAAO;AAElF,aAAO;AAAA,QACL,MAAM,QAAQ,QAAQ,SAAS;AAAA,QAC/B,SAAS,QAAQ,UAAU,cAAc,OAAO,OAAO,IAAI;AAAA,QAC3D,UAAU,YAAY,SAAS,SAAS,IAAK,WAAkC;AAAA,QAC/E,QAAQ;AAAA,UACN,GAAG,QAAQ;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
package/runtime.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './runtime/createRoute.js';
2
2
  export * from './runtime/toReactRouter.js';
3
+ export * from './runtime/RouterConfigurationBuilder.js';
3
4
  export * from './runtime/useViewConfig.js';
4
5
  export * from './runtime/createMenuItems.js';
5
6
  //# sourceMappingURL=runtime.d.ts.map
package/runtime.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["src/runtime.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["src/runtime.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yCAAyC,CAAC;AACxD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC"}
package/runtime.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./runtime/createRoute.js";
2
2
  export * from "./runtime/toReactRouter.js";
3
+ export * from "./runtime/RouterConfigurationBuilder.js";
3
4
  export * from "./runtime/useViewConfig.js";
4
5
  export * from "./runtime/createMenuItems.js";
5
6
  //# sourceMappingURL=runtime.js.map
package/runtime.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["src/runtime.ts"],
4
- "sourcesContent": ["export * from './runtime/createRoute.js';\nexport * from './runtime/toReactRouter.js';\nexport * from './runtime/useViewConfig.js';\nexport * from './runtime/createMenuItems.js';\n"],
5
- "mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
4
+ "sourcesContent": ["export * from './runtime/createRoute.js';\nexport * from './runtime/toReactRouter.js';\nexport * from './runtime/RouterConfigurationBuilder.js';\nexport * from './runtime/useViewConfig.js';\nexport * from './runtime/createMenuItems.js';\n"],
5
+ "mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
6
6
  "names": []
7
7
  }
@@ -6,7 +6,7 @@ import type { RouteParamType } from './routeParamType.js';
6
6
  * view configuration with the route parameters.
7
7
  */
8
8
  export type ServerViewConfig = Readonly<{
9
- children: readonly ServerViewConfig[];
9
+ children?: readonly ServerViewConfig[];
10
10
  params?: Readonly<Record<string, RouteParamType>>;
11
11
  }> &
12
12
  ViewConfig;
@@ -7,6 +7,6 @@
7
7
  *
8
8
  * @returns The transformed route.
9
9
  */
10
- export declare function transformTreeSync<T, U>(node: T, getChildren: (node: T) => IterableIterator<T> | null | undefined, transformer: (node: T, children: readonly U[]) => U): U;
11
- export declare function transformTree<T, U>(node: T, getChildren: (node: T) => IterableIterator<T> | null | undefined, transformer: (node: T, children: readonly U[]) => Promise<U>): Promise<U>;
10
+ export declare function transformTreeSync<T, U>(node: T, getChildren: (node: T) => IterableIterator<T> | null | undefined, transformer: (node: T, children?: readonly U[]) => U): U;
11
+ export declare function transformTree<T, U>(node: T, getChildren: (node: T) => IterableIterator<T> | null | undefined, transformer: (node: T, children?: readonly U[]) => Promise<U>): Promise<U>;
12
12
  //# sourceMappingURL=transformTree.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transformTree.d.ts","sourceRoot":"","sources":["../src/shared/transformTree.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EACpC,IAAI,EAAE,CAAC,EACP,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAChE,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,KAAK,CAAC,GAClD,CAAC,CAOH;AAED,wBAAsB,aAAa,CAAC,CAAC,EAAE,CAAC,EACtC,IAAI,EAAE,CAAC,EACP,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAChE,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAC3D,OAAO,CAAC,CAAC,CAAC,CASZ"}
1
+ {"version":3,"file":"transformTree.d.ts","sourceRoot":"","sources":["../src/shared/transformTree.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EACpC,IAAI,EAAE,CAAC,EACP,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAChE,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,SAAS,CAAC,EAAE,KAAK,CAAC,GACnD,CAAC,CAOH;AAED,wBAAsB,aAAa,CAAC,CAAC,EAAE,CAAC,EACtC,IAAI,EAAE,CAAC,EACP,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAChE,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,SAAS,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5D,OAAO,CAAC,CAAC,CAAC,CASZ"}
@@ -2,14 +2,14 @@ function transformTreeSync(node, getChildren, transformer) {
2
2
  const children = getChildren(node);
3
3
  return transformer(
4
4
  node,
5
- children ? Array.from(children, (child) => transformTreeSync(child, getChildren, transformer)) : []
5
+ children ? Array.from(children, (child) => transformTreeSync(child, getChildren, transformer)) : void 0
6
6
  );
7
7
  }
8
8
  async function transformTree(node, getChildren, transformer) {
9
9
  const children = getChildren(node);
10
10
  return transformer(
11
11
  node,
12
- children ? await Promise.all(Array.from(children, async (child) => transformTree(child, getChildren, transformer))) : []
12
+ children ? await Promise.all(Array.from(children, async (child) => transformTree(child, getChildren, transformer))) : void 0
13
13
  );
14
14
  }
15
15
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/shared/transformTree.ts"],
4
- "sourcesContent": ["/**\n * Transforms the whole route tree into a new format.\n *\n * @param node - The route to transform.\n * @param getChildren - A function that returns the children of the route.\n * @param transformer - A function that transforms the route and its children.\n *\n * @returns The transformed route.\n */\nexport function transformTreeSync<T, U>(\n node: T,\n getChildren: (node: T) => IterableIterator<T> | null | undefined,\n transformer: (node: T, children: readonly U[]) => U,\n): U {\n const children = getChildren(node);\n\n return transformer(\n node,\n children ? Array.from(children, (child) => transformTreeSync(child, getChildren, transformer)) : [],\n );\n}\n\nexport async function transformTree<T, U>(\n node: T,\n getChildren: (node: T) => IterableIterator<T> | null | undefined,\n transformer: (node: T, children: readonly U[]) => Promise<U>,\n): Promise<U> {\n const children = getChildren(node);\n\n return transformer(\n node,\n children\n ? await Promise.all(Array.from(children, async (child) => transformTree(child, getChildren, transformer)))\n : [],\n );\n}\n"],
5
- "mappings": "AASO,SAAS,kBACd,MACA,aACA,aACG;AACH,QAAM,WAAW,YAAY,IAAI;AAEjC,SAAO;AAAA,IACL;AAAA,IACA,WAAW,MAAM,KAAK,UAAU,CAAC,UAAU,kBAAkB,OAAO,aAAa,WAAW,CAAC,IAAI,CAAC;AAAA,EACpG;AACF;AAEA,eAAsB,cACpB,MACA,aACA,aACY;AACZ,QAAM,WAAW,YAAY,IAAI;AAEjC,SAAO;AAAA,IACL;AAAA,IACA,WACI,MAAM,QAAQ,IAAI,MAAM,KAAK,UAAU,OAAO,UAAU,cAAc,OAAO,aAAa,WAAW,CAAC,CAAC,IACvG,CAAC;AAAA,EACP;AACF;",
4
+ "sourcesContent": ["/**\n * Transforms the whole route tree into a new format.\n *\n * @param node - The route to transform.\n * @param getChildren - A function that returns the children of the route.\n * @param transformer - A function that transforms the route and its children.\n *\n * @returns The transformed route.\n */\nexport function transformTreeSync<T, U>(\n node: T,\n getChildren: (node: T) => IterableIterator<T> | null | undefined,\n transformer: (node: T, children?: readonly U[]) => U,\n): U {\n const children = getChildren(node);\n\n return transformer(\n node,\n children ? Array.from(children, (child) => transformTreeSync(child, getChildren, transformer)) : undefined,\n );\n}\n\nexport async function transformTree<T, U>(\n node: T,\n getChildren: (node: T) => IterableIterator<T> | null | undefined,\n transformer: (node: T, children?: readonly U[]) => Promise<U>,\n): Promise<U> {\n const children = getChildren(node);\n\n return transformer(\n node,\n children\n ? await Promise.all(Array.from(children, async (child) => transformTree(child, getChildren, transformer)))\n : undefined,\n );\n}\n"],
5
+ "mappings": "AASO,SAAS,kBACd,MACA,aACA,aACG;AACH,QAAM,WAAW,YAAY,IAAI;AAEjC,SAAO;AAAA,IACL;AAAA,IACA,WAAW,MAAM,KAAK,UAAU,CAAC,UAAU,kBAAkB,OAAO,aAAa,WAAW,CAAC,IAAI;AAAA,EACnG;AACF;AAEA,eAAsB,cACpB,MACA,aACA,aACY;AACZ,QAAM,WAAW,YAAY,IAAI;AAEjC,SAAO;AAAA,IACL;AAAA,IACA,WACI,MAAM,QAAQ,IAAI,MAAM,KAAK,UAAU,OAAO,UAAU,cAAc,OAAO,aAAa,WAAW,CAAC,CAAC,IACvG;AAAA,EACN;AACF;",
6
6
  "names": []
7
7
  }
package/types.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { createBrowserRouter } from 'react-router-dom';
2
+
1
3
  export type ViewConfig = Readonly<{
2
4
  /**
3
5
  * View title used in the main layout header, as <title> and as the default
@@ -9,7 +11,7 @@ export type ViewConfig = Readonly<{
9
11
  /**
10
12
  * Same as in the explicit React Router configuration.
11
13
  */
12
- rolesAllowed?: readonly string[];
14
+ rolesAllowed?: readonly [string, ...string[]];
13
15
 
14
16
  /**
15
17
  * Set to true to require the user to be logged in to access the view.
@@ -79,3 +81,8 @@ export type MenuItem = Readonly<{
79
81
  icon?: string;
80
82
  title?: string;
81
83
  }>;
84
+
85
+ export type RouterConfiguration = Readonly<{
86
+ routes: readonly RouterObject[];
87
+ router: ReturnType<typeof createBrowserRouter>;
88
+ }>;
@@ -3,7 +3,7 @@ export type RouteMeta = Readonly<{
3
3
  path: string;
4
4
  file?: URL;
5
5
  layout?: URL;
6
- children: RouteMeta[];
6
+ children?: RouteMeta[];
7
7
  }>;
8
8
  /**
9
9
  * Routes collector options.
@@ -1 +1 @@
1
- {"version":3,"file":"collectRoutesFromFS.d.ts","sourceRoot":"","sources":["../src/vite-plugin/collectRoutesFromFS.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,QAAQ,EAAE,SAAS,EAAE,CAAC;CACvB,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C;;OAEG;IACH,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,GAAG,CAAC;IACb;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAmBH;;;;;;;;;;;;;GAaG;AACH,wBAA8B,mBAAmB,CAC/C,GAAG,EAAE,GAAG,EACR,EAAE,UAAU,EAAE,MAAM,EAAE,MAAY,EAAE,EAAE,oBAAoB,GACzD,OAAO,CAAC,SAAS,CAAC,CA6DpB"}
1
+ {"version":3,"file":"collectRoutesFromFS.d.ts","sourceRoot":"","sources":["../src/vite-plugin/collectRoutesFromFS.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;CACxB,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C;;OAEG;IACH,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,GAAG,CAAC;IACb;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAoBH;;;;;;;;;;;;;GAaG;AACH,wBAA8B,mBAAmB,CAC/C,GAAG,EAAE,GAAG,EACR,EAAE,UAAU,EAAE,MAAM,EAAE,MAAY,EAAE,EAAE,oBAAoB,GACzD,OAAO,CAAC,SAAS,CAAC,CA0DpB"}
@@ -5,10 +5,11 @@ import { cleanUp } from "./utils.js";
5
5
  async function checkFile(url, logger) {
6
6
  if (url) {
7
7
  const contents = await readFile(url, "utf-8");
8
- if (contents === "") {
8
+ if (contents.trim() === "") {
9
9
  return void 0;
10
10
  } else if (!contents.includes("export default")) {
11
11
  logger.error(`The file "${String(url)}" should contain a default export of a component`);
12
+ return void 0;
12
13
  }
13
14
  }
14
15
  return url;
@@ -24,6 +25,10 @@ async function collectRoutesFromFS(dir, { extensions, logger, parent = dir }) {
24
25
  children.push(await collectRoutesFromFS(new URL(`${d.name}/`, dir), { extensions, logger, parent: dir }));
25
26
  } else if (d.isFile() && extensions.includes(extname(d.name))) {
26
27
  const file = new URL(d.name, dir);
28
+ const url = await checkFile(file, logger);
29
+ if (url === void 0) {
30
+ continue;
31
+ }
27
32
  const name = basename(d.name, extname(d.name));
28
33
  if (name.startsWith("@")) {
29
34
  if (name === "@layout") {
@@ -31,8 +36,7 @@ async function collectRoutesFromFS(dir, { extensions, logger, parent = dir }) {
31
36
  } else if (name === "@index") {
32
37
  children.push({
33
38
  path: "",
34
- file,
35
- children: []
39
+ file
36
40
  });
37
41
  } else {
38
42
  throw new Error(
@@ -42,8 +46,7 @@ async function collectRoutesFromFS(dir, { extensions, logger, parent = dir }) {
42
46
  } else if (!name.startsWith("_")) {
43
47
  children.push({
44
48
  path: name,
45
- file,
46
- children: []
49
+ file
47
50
  });
48
51
  }
49
52
  } else if (d.isFile() && !d.name.startsWith("_") && warningFor.includes(extname(d.name))) {
@@ -54,15 +57,11 @@ async function collectRoutesFromFS(dir, { extensions, logger, parent = dir }) {
54
57
  }
55
58
  [children, layout] = await Promise.all([
56
59
  Promise.all(
57
- children.map(async (child) => {
58
- let { file: f, layout: l } = child;
59
- [f, l] = await Promise.all([checkFile(f, logger), checkFile(l, logger)]);
60
- return {
61
- ...child,
62
- file: f,
63
- layout: l
64
- };
65
- })
60
+ children.map(async (child) => ({
61
+ ...child,
62
+ file: child.file,
63
+ layout: await checkFile(child.layout, logger)
64
+ }))
66
65
  ),
67
66
  checkFile(layout, logger)
68
67
  ]);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/vite-plugin/collectRoutesFromFS.ts"],
4
- "sourcesContent": ["import { opendir, readFile } from 'node:fs/promises';\nimport { basename, extname, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Logger } from 'vite';\nimport { cleanUp } from './utils.js';\n\nexport type RouteMeta = Readonly<{\n path: string;\n file?: URL;\n layout?: URL;\n children: RouteMeta[];\n}>;\n\n/**\n * Routes collector options.\n */\nexport type CollectRoutesOptions = Readonly<{\n /**\n * The list of extensions for files that will be collected as routes.\n */\n extensions: readonly string[];\n /**\n * The parent directory of the current directory. This is a\n * nested parameter used inside the function only.\n */\n parent?: URL;\n /**\n * The Vite logger instance.\n */\n logger: Logger;\n}>;\n\nasync function checkFile(url: URL | undefined, logger: Logger): Promise<URL | undefined> {\n if (url) {\n const contents = await readFile(url, 'utf-8');\n if (contents === '') {\n return undefined;\n } else if (!contents.includes('export default')) {\n logger.error(`The file \"${String(url)}\" should contain a default export of a component`);\n }\n }\n\n return url;\n}\n\nconst collator = new Intl.Collator('en-US');\n\nconst warningFor = ['.ts', '.js'];\n\n/**\n * Collect route metadata from the file system and build a route tree.\n *\n * It accepts files that start with `@` as special files.\n * - `@layout` contains a component that wraps the child components.\n * - `@index` contains a component that will be used as the index page of the directory.\n *\n * It accepts files that start with `_` as private files. They will be ignored.\n *\n * @param dir - The directory to collect routes from.\n * @param options - The options object.\n *\n * @returns The route metadata tree.\n */\nexport default async function collectRoutesFromFS(\n dir: URL,\n { extensions, logger, parent = dir }: CollectRoutesOptions,\n): Promise<RouteMeta> {\n const path = relative(fileURLToPath(parent), fileURLToPath(dir));\n let children: RouteMeta[] = [];\n let layout: URL | undefined;\n\n for await (const d of await opendir(dir)) {\n if (d.isDirectory() && !d.name.startsWith('_')) {\n children.push(await collectRoutesFromFS(new URL(`${d.name}/`, dir), { extensions, logger, parent: dir }));\n } else if (d.isFile() && extensions.includes(extname(d.name))) {\n const file = new URL(d.name, dir);\n const name = basename(d.name, extname(d.name));\n\n if (name.startsWith('@')) {\n if (name === '@layout') {\n layout = file;\n } else if (name === '@index') {\n children.push({\n path: '',\n file,\n children: [],\n });\n } else {\n throw new Error(\n 'Symbol \"@\" is reserved for special directories and files; only \"@layout\" and \"@index\" are allowed',\n );\n }\n } else if (!name.startsWith('_')) {\n children.push({\n path: name,\n file,\n children: [],\n });\n }\n } else if (d.isFile() && !d.name.startsWith('_') && warningFor.includes(extname(d.name))) {\n logger.warn(\n `File System based router expects only JSX files in 'Frontend/views/' directory, such as '*.tsx' and '*.jsx'. The file '${d.name}' will be ignored by router, as it doesn't match this convention. Please consider storing it in another directory.`,\n );\n }\n }\n\n [children, layout] = await Promise.all([\n Promise.all(\n children.map(async (child) => {\n let { file: f, layout: l } = child;\n [f, l] = await Promise.all([checkFile(f, logger), checkFile(l, logger)]);\n\n return {\n ...child,\n file: f,\n layout: l,\n };\n }),\n ),\n checkFile(layout, logger),\n ]);\n\n return {\n path,\n layout,\n children: children.sort(({ path: a }, { path: b }) => collator.compare(cleanUp(a), cleanUp(b))),\n };\n}\n"],
5
- "mappings": "AAAA,SAAS,SAAS,gBAAgB;AAClC,SAAS,UAAU,SAAS,gBAAgB;AAC5C,SAAS,qBAAqB;AAE9B,SAAS,eAAe;AA4BxB,eAAe,UAAU,KAAsB,QAA0C;AACvF,MAAI,KAAK;AACP,UAAM,WAAW,MAAM,SAAS,KAAK,OAAO;AAC5C,QAAI,aAAa,IAAI;AACnB,aAAO;AAAA,IACT,WAAW,CAAC,SAAS,SAAS,gBAAgB,GAAG;AAC/C,aAAO,MAAM,aAAa,OAAO,GAAG,CAAC,kDAAkD;AAAA,IACzF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,MAAM,WAAW,IAAI,KAAK,SAAS,OAAO;AAE1C,MAAM,aAAa,CAAC,OAAO,KAAK;AAgBhC,eAAO,oBACL,KACA,EAAE,YAAY,QAAQ,SAAS,IAAI,GACf;AACpB,QAAM,OAAO,SAAS,cAAc,MAAM,GAAG,cAAc,GAAG,CAAC;AAC/D,MAAI,WAAwB,CAAC;AAC7B,MAAI;AAEJ,mBAAiB,KAAK,MAAM,QAAQ,GAAG,GAAG;AACxC,QAAI,EAAE,YAAY,KAAK,CAAC,EAAE,KAAK,WAAW,GAAG,GAAG;AAC9C,eAAS,KAAK,MAAM,oBAAoB,IAAI,IAAI,GAAG,EAAE,IAAI,KAAK,GAAG,GAAG,EAAE,YAAY,QAAQ,QAAQ,IAAI,CAAC,CAAC;AAAA,IAC1G,WAAW,EAAE,OAAO,KAAK,WAAW,SAAS,QAAQ,EAAE,IAAI,CAAC,GAAG;AAC7D,YAAM,OAAO,IAAI,IAAI,EAAE,MAAM,GAAG;AAChC,YAAM,OAAO,SAAS,EAAE,MAAM,QAAQ,EAAE,IAAI,CAAC;AAE7C,UAAI,KAAK,WAAW,GAAG,GAAG;AACxB,YAAI,SAAS,WAAW;AACtB,mBAAS;AAAA,QACX,WAAW,SAAS,UAAU;AAC5B,mBAAS,KAAK;AAAA,YACZ,MAAM;AAAA,YACN;AAAA,YACA,UAAU,CAAC;AAAA,UACb,CAAC;AAAA,QACH,OAAO;AACL,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF,WAAW,CAAC,KAAK,WAAW,GAAG,GAAG;AAChC,iBAAS,KAAK;AAAA,UACZ,MAAM;AAAA,UACN;AAAA,UACA,UAAU,CAAC;AAAA,QACb,CAAC;AAAA,MACH;AAAA,IACF,WAAW,EAAE,OAAO,KAAK,CAAC,EAAE,KAAK,WAAW,GAAG,KAAK,WAAW,SAAS,QAAQ,EAAE,IAAI,CAAC,GAAG;AACxF,aAAO;AAAA,QACL,0HAA0H,EAAE,IAAI;AAAA,MAClI;AAAA,IACF;AAAA,EACF;AAEA,GAAC,UAAU,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,IACrC,QAAQ;AAAA,MACN,SAAS,IAAI,OAAO,UAAU;AAC5B,YAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,IAAI;AAC7B,SAAC,GAAG,CAAC,IAAI,MAAM,QAAQ,IAAI,CAAC,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC,CAAC;AAEvE,eAAO;AAAA,UACL,GAAG;AAAA,UACH,MAAM;AAAA,UACN,QAAQ;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,UAAU,QAAQ,MAAM;AAAA,EAC1B,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU,SAAS,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,SAAS,QAAQ,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;AAAA,EAChG;AACF;",
4
+ "sourcesContent": ["import { opendir, readFile } from 'node:fs/promises';\nimport { basename, extname, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Logger } from 'vite';\nimport { cleanUp } from './utils.js';\n\nexport type RouteMeta = Readonly<{\n path: string;\n file?: URL;\n layout?: URL;\n children?: RouteMeta[];\n}>;\n\n/**\n * Routes collector options.\n */\nexport type CollectRoutesOptions = Readonly<{\n /**\n * The list of extensions for files that will be collected as routes.\n */\n extensions: readonly string[];\n /**\n * The parent directory of the current directory. This is a\n * nested parameter used inside the function only.\n */\n parent?: URL;\n /**\n * The Vite logger instance.\n */\n logger: Logger;\n}>;\n\nasync function checkFile(url: URL | undefined, logger: Logger): Promise<URL | undefined> {\n if (url) {\n const contents = await readFile(url, 'utf-8');\n if (contents.trim() === '') {\n return undefined;\n } else if (!contents.includes('export default')) {\n logger.error(`The file \"${String(url)}\" should contain a default export of a component`);\n return undefined;\n }\n }\n\n return url;\n}\n\nconst collator = new Intl.Collator('en-US');\n\nconst warningFor = ['.ts', '.js'];\n\n/**\n * Collect route metadata from the file system and build a route tree.\n *\n * It accepts files that start with `@` as special files.\n * - `@layout` contains a component that wraps the child components.\n * - `@index` contains a component that will be used as the index page of the directory.\n *\n * It accepts files that start with `_` as private files. They will be ignored.\n *\n * @param dir - The directory to collect routes from.\n * @param options - The options object.\n *\n * @returns The route metadata tree.\n */\nexport default async function collectRoutesFromFS(\n dir: URL,\n { extensions, logger, parent = dir }: CollectRoutesOptions,\n): Promise<RouteMeta> {\n const path = relative(fileURLToPath(parent), fileURLToPath(dir));\n let children: RouteMeta[] = [];\n let layout: URL | undefined;\n\n for await (const d of await opendir(dir)) {\n if (d.isDirectory() && !d.name.startsWith('_')) {\n children.push(await collectRoutesFromFS(new URL(`${d.name}/`, dir), { extensions, logger, parent: dir }));\n } else if (d.isFile() && extensions.includes(extname(d.name))) {\n const file = new URL(d.name, dir);\n const url = await checkFile(file, logger);\n if (url === undefined) {\n continue;\n }\n const name = basename(d.name, extname(d.name));\n\n if (name.startsWith('@')) {\n if (name === '@layout') {\n layout = file;\n } else if (name === '@index') {\n children.push({\n path: '',\n file,\n });\n } else {\n throw new Error(\n 'Symbol \"@\" is reserved for special directories and files; only \"@layout\" and \"@index\" are allowed',\n );\n }\n } else if (!name.startsWith('_')) {\n children.push({\n path: name,\n file,\n });\n }\n } else if (d.isFile() && !d.name.startsWith('_') && warningFor.includes(extname(d.name))) {\n logger.warn(\n `File System based router expects only JSX files in 'Frontend/views/' directory, such as '*.tsx' and '*.jsx'. The file '${d.name}' will be ignored by router, as it doesn't match this convention. Please consider storing it in another directory.`,\n );\n }\n }\n\n [children, layout] = await Promise.all([\n Promise.all(\n children.map(async (child) => ({\n ...child,\n file: child.file,\n layout: await checkFile(child.layout, logger),\n })),\n ),\n checkFile(layout, logger),\n ]);\n\n return {\n path,\n layout,\n children: children.sort(({ path: a }, { path: b }) => collator.compare(cleanUp(a), cleanUp(b))),\n };\n}\n"],
5
+ "mappings": "AAAA,SAAS,SAAS,gBAAgB;AAClC,SAAS,UAAU,SAAS,gBAAgB;AAC5C,SAAS,qBAAqB;AAE9B,SAAS,eAAe;AA4BxB,eAAe,UAAU,KAAsB,QAA0C;AACvF,MAAI,KAAK;AACP,UAAM,WAAW,MAAM,SAAS,KAAK,OAAO;AAC5C,QAAI,SAAS,KAAK,MAAM,IAAI;AAC1B,aAAO;AAAA,IACT,WAAW,CAAC,SAAS,SAAS,gBAAgB,GAAG;AAC/C,aAAO,MAAM,aAAa,OAAO,GAAG,CAAC,kDAAkD;AACvF,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,MAAM,WAAW,IAAI,KAAK,SAAS,OAAO;AAE1C,MAAM,aAAa,CAAC,OAAO,KAAK;AAgBhC,eAAO,oBACL,KACA,EAAE,YAAY,QAAQ,SAAS,IAAI,GACf;AACpB,QAAM,OAAO,SAAS,cAAc,MAAM,GAAG,cAAc,GAAG,CAAC;AAC/D,MAAI,WAAwB,CAAC;AAC7B,MAAI;AAEJ,mBAAiB,KAAK,MAAM,QAAQ,GAAG,GAAG;AACxC,QAAI,EAAE,YAAY,KAAK,CAAC,EAAE,KAAK,WAAW,GAAG,GAAG;AAC9C,eAAS,KAAK,MAAM,oBAAoB,IAAI,IAAI,GAAG,EAAE,IAAI,KAAK,GAAG,GAAG,EAAE,YAAY,QAAQ,QAAQ,IAAI,CAAC,CAAC;AAAA,IAC1G,WAAW,EAAE,OAAO,KAAK,WAAW,SAAS,QAAQ,EAAE,IAAI,CAAC,GAAG;AAC7D,YAAM,OAAO,IAAI,IAAI,EAAE,MAAM,GAAG;AAChC,YAAM,MAAM,MAAM,UAAU,MAAM,MAAM;AACxC,UAAI,QAAQ,QAAW;AACrB;AAAA,MACF;AACA,YAAM,OAAO,SAAS,EAAE,MAAM,QAAQ,EAAE,IAAI,CAAC;AAE7C,UAAI,KAAK,WAAW,GAAG,GAAG;AACxB,YAAI,SAAS,WAAW;AACtB,mBAAS;AAAA,QACX,WAAW,SAAS,UAAU;AAC5B,mBAAS,KAAK;AAAA,YACZ,MAAM;AAAA,YACN;AAAA,UACF,CAAC;AAAA,QACH,OAAO;AACL,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF,WAAW,CAAC,KAAK,WAAW,GAAG,GAAG;AAChC,iBAAS,KAAK;AAAA,UACZ,MAAM;AAAA,UACN;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,WAAW,EAAE,OAAO,KAAK,CAAC,EAAE,KAAK,WAAW,GAAG,KAAK,WAAW,SAAS,QAAQ,EAAE,IAAI,CAAC,GAAG;AACxF,aAAO;AAAA,QACL,0HAA0H,EAAE,IAAI;AAAA,MAClI;AAAA,IACF;AAAA,EACF;AAEA,GAAC,UAAU,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,IACrC,QAAQ;AAAA,MACN,SAAS,IAAI,OAAO,WAAW;AAAA,QAC7B,GAAG;AAAA,QACH,MAAM,MAAM;AAAA,QACZ,QAAQ,MAAM,UAAU,MAAM,QAAQ,MAAM;AAAA,MAC9C,EAAE;AAAA,IACJ;AAAA,IACA,UAAU,QAAQ,MAAM;AAAA,EAC1B,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU,SAAS,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,SAAS,QAAQ,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;AAAA,EAChG;AACF;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"createRoutesFromMeta.d.ts","sourceRoot":"","sources":["../src/vite-plugin/createRoutesFromMeta.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAmDjE;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,eAAe,GAAG,MAAM,CA4D1G"}
1
+ {"version":3,"file":"createRoutesFromMeta.d.ts","sourceRoot":"","sources":["../src/vite-plugin/createRoutesFromMeta.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAmDjE;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,eAAe,GAAG,MAAM,CAgE1G"}
@@ -21,7 +21,7 @@ function createImport(mod, file) {
21
21
  }
22
22
  function createRouteData(path, mod, children) {
23
23
  return template(
24
- `const route = createRoute("${path}"${mod ? `, ${mod}` : ""}${children.length > 0 ? `, CHILDREN` : ""})`,
24
+ `const route = createRoute("${path}"${mod ? `, ${mod}` : ""}${children && children.length > 0 ? `, CHILDREN` : ""})`,
25
25
  ([statement]) => statement.declarationList.declarations[0].initializer,
26
26
  [
27
27
  transformer(
@@ -43,10 +43,13 @@ function createRoutesFromMeta(views, { code: codeFile }) {
43
43
  const routes = transformTreeSync(
44
44
  views,
45
45
  (view) => {
46
- const paths = view.children.map((c) => c.path);
47
- const uniquePaths = new Set(paths);
48
- paths.filter((p) => !uniquePaths.delete(p)).forEach((p) => errors.push(`console.error("Two views share the same path: ${p}");`));
49
- return view.children.values();
46
+ if (view.children) {
47
+ const paths = view.children.map((c) => c.path);
48
+ const uniquePaths = new Set(paths);
49
+ paths.filter((p) => !uniquePaths.delete(p)).forEach((p) => errors.push(`console.error("Two views share the same path: ${p}");`));
50
+ return view.children.values();
51
+ }
52
+ return void 0;
50
53
  },
51
54
  ({ file: file2, layout, path }, children) => {
52
55
  const currentId = id;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/vite-plugin/createRoutesFromMeta.ts"],
4
- "sourcesContent": ["import { sep, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { template, transform as transformer } from '@vaadin/hilla-generator-utils/ast.js';\nimport createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';\nimport ts, {\n type CallExpression,\n type ImportDeclaration,\n type StringLiteral,\n type VariableStatement,\n} from 'typescript';\n\nimport { transformTreeSync } from '../shared/transformTree.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport type { RuntimeFileUrls } from './generateRuntimeFiles.js';\nimport { convertFSRouteSegmentToURLPatternFormat } from './utils.js';\n\nconst printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });\n\n/**\n * Convert a file URL to a relative path from the generated directory.\n *\n * @param url - The file URL to convert.\n * @param generatedDir - The directory where the generated view file will be stored.\n */\nfunction relativize(url: URL, generatedDir: URL): string {\n const result = relative(fileURLToPath(generatedDir), fileURLToPath(url)).replaceAll(sep, '/');\n\n if (!result.startsWith('.')) {\n return `./${result}`;\n }\n\n return result;\n}\n\n/**\n * Create an import declaration for a `views` module.\n *\n * @param mod - The name of the route module to import.\n * @param file - The file path of the module.\n */\nfunction createImport(mod: string, file: string): ImportDeclaration {\n const path = `${file.substring(0, file.lastIndexOf('.'))}.js`;\n return template(`import * as ${mod} from '${path}';\\n`, ([statement]) => statement as ts.ImportDeclaration);\n}\n\n/**\n * Create an abstract route creation function call. The nested function calls create a route tree.\n *\n * @param path - The path of the route.\n * @param mod - The name of the route module imported as a namespace.\n * @param children - The list of child route call expressions.\n */\nfunction createRouteData(path: string, mod: string | undefined, children: readonly CallExpression[]): CallExpression {\n return template(\n `const route = createRoute(\"${path}\"${mod ? `, ${mod}` : ''}${children.length > 0 ? `, CHILDREN` : ''})`,\n ([statement]) => (statement as VariableStatement).declarationList.declarations[0].initializer as CallExpression,\n [\n transformer((node) =>\n ts.isIdentifier(node) && node.text === 'CHILDREN' ? ts.factory.createArrayLiteralExpression(children) : node,\n ),\n ],\n );\n}\n\n/**\n * Loads all the files from the received metadata and creates a framework-agnostic route tree.\n *\n * @param views - The abstract route tree.\n * @param generatedDir - The directory where the generated view file will be stored.\n */\nexport default function createRoutesFromMeta(views: RouteMeta, { code: codeFile }: RuntimeFileUrls): string {\n const codeDir = new URL('./', codeFile);\n const imports: ImportDeclaration[] = [\n template(\n 'import { createRoute } from \"@vaadin/hilla-file-router/runtime.js\";',\n ([statement]) => statement as ts.ImportDeclaration,\n ),\n ];\n const errors: string[] = [];\n let id = 0;\n\n const routes = transformTreeSync<RouteMeta, CallExpression>(\n views,\n (view) => {\n const paths = view.children.map((c) => c.path);\n const uniquePaths = new Set(paths);\n paths\n .filter((p) => !uniquePaths.delete(p))\n .forEach((p) => errors.push(`console.error(\"Two views share the same path: ${p}\");`));\n return view.children.values();\n },\n ({ file, layout, path }, children) => {\n const currentId = id;\n id += 1;\n\n let mod: string | undefined;\n if (file) {\n mod = `Page${currentId}`;\n imports.push(createImport(mod, relativize(file, codeDir)));\n } else if (layout) {\n mod = `Layout${currentId}`;\n imports.push(createImport(mod, relativize(layout, codeDir)));\n }\n\n return createRouteData(convertFSRouteSegmentToURLPatternFormat(path), mod, children);\n },\n );\n\n const routeDeclaration = template(\n `import a from 'IMPORTS';\n\n${errors.join('\\n')}\n\nconst routes = ROUTE;\n\nexport default routes;\n`,\n [\n transformer((node) =>\n ts.isImportDeclaration(node) && (node.moduleSpecifier as StringLiteral).text === 'IMPORTS' ? imports : node,\n ),\n transformer((node) => (ts.isIdentifier(node) && node.text === 'ROUTE' ? routes : node)),\n ],\n );\n\n const file = createSourceFile(routeDeclaration, 'file-routes.ts');\n // also keep the old file temporarily for compatibility purposes:\n const tempFile = createSourceFile(routeDeclaration, 'views.ts');\n printer.printFile(tempFile);\n return printer.printFile(file);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { sep, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { template, transform as transformer } from '@vaadin/hilla-generator-utils/ast.js';\nimport createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';\nimport ts, {\n type CallExpression,\n type ImportDeclaration,\n type StringLiteral,\n type VariableStatement,\n} from 'typescript';\n\nimport { transformTreeSync } from '../shared/transformTree.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport type { RuntimeFileUrls } from './generateRuntimeFiles.js';\nimport { convertFSRouteSegmentToURLPatternFormat } from './utils.js';\n\nconst printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });\n\n/**\n * Convert a file URL to a relative path from the generated directory.\n *\n * @param url - The file URL to convert.\n * @param generatedDir - The directory where the generated view file will be stored.\n */\nfunction relativize(url: URL, generatedDir: URL): string {\n const result = relative(fileURLToPath(generatedDir), fileURLToPath(url)).replaceAll(sep, '/');\n\n if (!result.startsWith('.')) {\n return `./${result}`;\n }\n\n return result;\n}\n\n/**\n * Create an import declaration for a `views` module.\n *\n * @param mod - The name of the route module to import.\n * @param file - The file path of the module.\n */\nfunction createImport(mod: string, file: string): ImportDeclaration {\n const path = `${file.substring(0, file.lastIndexOf('.'))}.js`;\n return template(`import * as ${mod} from '${path}';\\n`, ([statement]) => statement as ts.ImportDeclaration);\n}\n\n/**\n * Create an abstract route creation function call. The nested function calls create a route tree.\n *\n * @param path - The path of the route.\n * @param mod - The name of the route module imported as a namespace.\n * @param children - The list of child route call expressions.\n */\nfunction createRouteData(path: string, mod: string | undefined, children?: readonly CallExpression[]): CallExpression {\n return template(\n `const route = createRoute(\"${path}\"${mod ? `, ${mod}` : ''}${children && children.length > 0 ? `, CHILDREN` : ''})`,\n ([statement]) => (statement as VariableStatement).declarationList.declarations[0].initializer as CallExpression,\n [\n transformer((node) =>\n ts.isIdentifier(node) && node.text === 'CHILDREN' ? ts.factory.createArrayLiteralExpression(children) : node,\n ),\n ],\n );\n}\n\n/**\n * Loads all the files from the received metadata and creates a framework-agnostic route tree.\n *\n * @param views - The abstract route tree.\n * @param generatedDir - The directory where the generated view file will be stored.\n */\nexport default function createRoutesFromMeta(views: RouteMeta, { code: codeFile }: RuntimeFileUrls): string {\n const codeDir = new URL('./', codeFile);\n const imports: ImportDeclaration[] = [\n template(\n 'import { createRoute } from \"@vaadin/hilla-file-router/runtime.js\";',\n ([statement]) => statement as ts.ImportDeclaration,\n ),\n ];\n const errors: string[] = [];\n let id = 0;\n\n const routes = transformTreeSync<RouteMeta, CallExpression>(\n views,\n (view) => {\n if (view.children) {\n const paths = view.children.map((c) => c.path);\n const uniquePaths = new Set(paths);\n paths\n .filter((p) => !uniquePaths.delete(p))\n .forEach((p) => errors.push(`console.error(\"Two views share the same path: ${p}\");`));\n return view.children.values();\n }\n\n return undefined;\n },\n ({ file, layout, path }, children) => {\n const currentId = id;\n id += 1;\n\n let mod: string | undefined;\n if (file) {\n mod = `Page${currentId}`;\n imports.push(createImport(mod, relativize(file, codeDir)));\n } else if (layout) {\n mod = `Layout${currentId}`;\n imports.push(createImport(mod, relativize(layout, codeDir)));\n }\n\n return createRouteData(convertFSRouteSegmentToURLPatternFormat(path), mod, children);\n },\n );\n\n const routeDeclaration = template(\n `import a from 'IMPORTS';\n\n${errors.join('\\n')}\n\nconst routes = ROUTE;\n\nexport default routes;\n`,\n [\n transformer((node) =>\n ts.isImportDeclaration(node) && (node.moduleSpecifier as StringLiteral).text === 'IMPORTS' ? imports : node,\n ),\n transformer((node) => (ts.isIdentifier(node) && node.text === 'ROUTE' ? routes : node)),\n ],\n );\n\n const file = createSourceFile(routeDeclaration, 'file-routes.ts');\n // also keep the old file temporarily for compatibility purposes:\n const tempFile = createSourceFile(routeDeclaration, 'views.ts');\n printer.printFile(tempFile);\n return printer.printFile(file);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["file"]
7
7
  }
@@ -13,7 +13,7 @@ function* walkAST(node) {
13
13
  async function createViewConfigJson(views) {
14
14
  const res = await transformTree(
15
15
  views,
16
- (route) => route.children.values(),
16
+ (route) => route.children?.values(),
17
17
  async ({ path, file, layout }, children) => {
18
18
  if (!file && !layout) {
19
19
  return {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/vite-plugin/createViewConfigJson.ts"],
4
- "sourcesContent": ["import { readFile } from 'node:fs/promises';\nimport { Script } from 'node:vm';\nimport ts, { type Node } from 'typescript';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport type { ServerViewConfig } from '../shared/internal.js';\nimport { transformTree } from '../shared/transformTree.js';\nimport type { ViewConfig } from '../types.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport { convertFSRouteSegmentToURLPatternFormat, extractParameterFromRouteSegment } from './utils.js';\n\n/**\n * Walks the TypeScript AST using the deep-first search algorithm.\n *\n * @param node - The node to walk.\n */\nfunction* walkAST(node: Node): Generator<Node> {\n yield node;\n\n for (const child of node.getChildren()) {\n yield* walkAST(child);\n }\n}\n\n/**\n * Creates a map of all leaf routes to their configuration. This file is used by the server to provide server-side\n * routes along with managing the client-side routes.\n *\n * @param views - The route metadata tree.\n */\nexport default async function createViewConfigJson(views: RouteMeta): Promise<string> {\n const res = await transformTree<RouteMeta, ServerViewConfig>(\n views,\n (route) => route.children.values(),\n async ({ path, file, layout }, children) => {\n if (!file && !layout) {\n return {\n route: path,\n params: extractParameterFromRouteSegment(path),\n children,\n } as const;\n }\n\n const sourceFile = ts.createSourceFile(\n 'f.ts',\n await readFile(file ?? layout!, 'utf8'),\n ts.ScriptTarget.ESNext,\n true,\n );\n let config: ViewConfig | undefined;\n let waitingForIdentifier = false;\n let componentName: string | undefined;\n\n for (const node of walkAST(sourceFile)) {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === 'config') {\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const code = node.initializer.getText(sourceFile);\n const script = new Script(`(${code})`);\n config = script.runInThisContext() as ViewConfig;\n }\n } else if (node.getText(sourceFile).startsWith('export default')) {\n waitingForIdentifier = true;\n } else if (waitingForIdentifier && ts.isIdentifier(node)) {\n componentName = node.text;\n break;\n }\n }\n\n return {\n route: convertFSRouteSegmentToURLPatternFormat(path),\n ...config,\n params: extractParameterFromRouteSegment(config?.route ?? path),\n title: config?.title ?? convertComponentNameToTitle(componentName),\n children,\n } as const;\n },\n );\n\n return JSON.stringify(res);\n}\n"],
5
- "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,OAAO,YAAuB;AAC9B,SAAS,mCAAmC;AAE5C,SAAS,qBAAqB;AAG9B,SAAS,yCAAyC,wCAAwC;AAO1F,UAAU,QAAQ,MAA6B;AAC7C,QAAM;AAEN,aAAW,SAAS,KAAK,YAAY,GAAG;AACtC,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;AAQA,eAAO,qBAA4C,OAAmC;AACpF,QAAM,MAAM,MAAM;AAAA,IAChB;AAAA,IACA,CAAC,UAAU,MAAM,SAAS,OAAO;AAAA,IACjC,OAAO,EAAE,MAAM,MAAM,OAAO,GAAG,aAAa;AAC1C,UAAI,CAAC,QAAQ,CAAC,QAAQ;AACpB,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ,iCAAiC,IAAI;AAAA,UAC7C;AAAA,QACF;AAAA,MACF;AAEA,YAAM,aAAa,GAAG;AAAA,QACpB;AAAA,QACA,MAAM,SAAS,QAAQ,QAAS,MAAM;AAAA,QACtC,GAAG,aAAa;AAAA,QAChB;AAAA,MACF;AACA,UAAI;AACJ,UAAI,uBAAuB;AAC3B,UAAI;AAEJ,iBAAW,QAAQ,QAAQ,UAAU,GAAG;AACtC,YAAI,GAAG,sBAAsB,IAAI,KAAK,GAAG,aAAa,KAAK,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AAC/F,cAAI,KAAK,eAAe,GAAG,0BAA0B,KAAK,WAAW,GAAG;AACtE,kBAAM,OAAO,KAAK,YAAY,QAAQ,UAAU;AAChD,kBAAM,SAAS,IAAI,OAAO,IAAI,IAAI,GAAG;AACrC,qBAAS,OAAO,iBAAiB;AAAA,UACnC;AAAA,QACF,WAAW,KAAK,QAAQ,UAAU,EAAE,WAAW,gBAAgB,GAAG;AAChE,iCAAuB;AAAA,QACzB,WAAW,wBAAwB,GAAG,aAAa,IAAI,GAAG;AACxD,0BAAgB,KAAK;AACrB;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,QACL,OAAO,wCAAwC,IAAI;AAAA,QACnD,GAAG;AAAA,QACH,QAAQ,iCAAiC,QAAQ,SAAS,IAAI;AAAA,QAC9D,OAAO,QAAQ,SAAS,4BAA4B,aAAa;AAAA,QACjE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,KAAK,UAAU,GAAG;AAC3B;",
4
+ "sourcesContent": ["import { readFile } from 'node:fs/promises';\nimport { Script } from 'node:vm';\nimport ts, { type Node } from 'typescript';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport type { ServerViewConfig } from '../shared/internal.js';\nimport { transformTree } from '../shared/transformTree.js';\nimport type { ViewConfig } from '../types.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport { convertFSRouteSegmentToURLPatternFormat, extractParameterFromRouteSegment } from './utils.js';\n\n/**\n * Walks the TypeScript AST using the deep-first search algorithm.\n *\n * @param node - The node to walk.\n */\nfunction* walkAST(node: Node): Generator<Node> {\n yield node;\n\n for (const child of node.getChildren()) {\n yield* walkAST(child);\n }\n}\n\n/**\n * Creates a map of all leaf routes to their configuration. This file is used by the server to provide server-side\n * routes along with managing the client-side routes.\n *\n * @param views - The route metadata tree.\n */\nexport default async function createViewConfigJson(views: RouteMeta): Promise<string> {\n const res = await transformTree<RouteMeta, ServerViewConfig>(\n views,\n (route) => route.children?.values(),\n async ({ path, file, layout }, children) => {\n if (!file && !layout) {\n return {\n route: path,\n params: extractParameterFromRouteSegment(path),\n children,\n } as const;\n }\n\n const sourceFile = ts.createSourceFile(\n 'f.ts',\n await readFile(file ?? layout!, 'utf8'),\n ts.ScriptTarget.ESNext,\n true,\n );\n let config: ViewConfig | undefined;\n let waitingForIdentifier = false;\n let componentName: string | undefined;\n\n for (const node of walkAST(sourceFile)) {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === 'config') {\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const code = node.initializer.getText(sourceFile);\n const script = new Script(`(${code})`);\n config = script.runInThisContext() as ViewConfig;\n }\n } else if (node.getText(sourceFile).startsWith('export default')) {\n waitingForIdentifier = true;\n } else if (waitingForIdentifier && ts.isIdentifier(node)) {\n componentName = node.text;\n break;\n }\n }\n\n return {\n route: convertFSRouteSegmentToURLPatternFormat(path),\n ...config,\n params: extractParameterFromRouteSegment(config?.route ?? path),\n title: config?.title ?? convertComponentNameToTitle(componentName),\n children,\n } as const;\n },\n );\n\n return JSON.stringify(res);\n}\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,OAAO,YAAuB;AAC9B,SAAS,mCAAmC;AAE5C,SAAS,qBAAqB;AAG9B,SAAS,yCAAyC,wCAAwC;AAO1F,UAAU,QAAQ,MAA6B;AAC7C,QAAM;AAEN,aAAW,SAAS,KAAK,YAAY,GAAG;AACtC,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;AAQA,eAAO,qBAA4C,OAAmC;AACpF,QAAM,MAAM,MAAM;AAAA,IAChB;AAAA,IACA,CAAC,UAAU,MAAM,UAAU,OAAO;AAAA,IAClC,OAAO,EAAE,MAAM,MAAM,OAAO,GAAG,aAAa;AAC1C,UAAI,CAAC,QAAQ,CAAC,QAAQ;AACpB,eAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ,iCAAiC,IAAI;AAAA,UAC7C;AAAA,QACF;AAAA,MACF;AAEA,YAAM,aAAa,GAAG;AAAA,QACpB;AAAA,QACA,MAAM,SAAS,QAAQ,QAAS,MAAM;AAAA,QACtC,GAAG,aAAa;AAAA,QAChB;AAAA,MACF;AACA,UAAI;AACJ,UAAI,uBAAuB;AAC3B,UAAI;AAEJ,iBAAW,QAAQ,QAAQ,UAAU,GAAG;AACtC,YAAI,GAAG,sBAAsB,IAAI,KAAK,GAAG,aAAa,KAAK,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AAC/F,cAAI,KAAK,eAAe,GAAG,0BAA0B,KAAK,WAAW,GAAG;AACtE,kBAAM,OAAO,KAAK,YAAY,QAAQ,UAAU;AAChD,kBAAM,SAAS,IAAI,OAAO,IAAI,IAAI,GAAG;AACrC,qBAAS,OAAO,iBAAiB;AAAA,UACnC;AAAA,QACF,WAAW,KAAK,QAAQ,UAAU,EAAE,WAAW,gBAAgB,GAAG;AAChE,iCAAuB;AAAA,QACzB,WAAW,wBAAwB,GAAG,aAAa,IAAI,GAAG;AACxD,0BAAgB,KAAK;AACrB;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,QACL,OAAO,wCAAwC,IAAI;AAAA,QACnD,GAAG;AAAA,QACH,QAAQ,iCAAiC,QAAQ,SAAS,IAAI;AAAA,QAC9D,OAAO,QAAQ,SAAS,4BAA4B,aAAa;AAAA,QACjE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,KAAK,UAAU,GAAG;AAC3B;",
6
6
  "names": []
7
7
  }
@@ -1,10 +0,0 @@
1
- /**
2
- * Traverse a tree and yield the sequence of parent nodes to each leaf node.
3
- *
4
- * @param tree - The tree to traverse.
5
- * @param parents - The sequence of parent nodes for the current node.
6
- */
7
- export default function traverse<T extends {
8
- children?: readonly T[];
9
- }>(tree: T, parents?: readonly T[]): Generator<readonly T[], undefined, undefined>;
10
- //# sourceMappingURL=traverse.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"traverse.d.ts","sourceRoot":"","sources":["../src/shared/traverse.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAW,QAAQ,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC,EAAE,CAAA;CAAE,EACrE,IAAI,EAAE,CAAC,EACP,OAAO,GAAE,SAAS,CAAC,EAAO,GACzB,SAAS,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAW/C"}
@@ -1,14 +0,0 @@
1
- function* traverse(tree, parents = []) {
2
- const chain = [...parents, tree];
3
- const children = tree.children ?? [];
4
- if (children.length === 0) {
5
- yield chain;
6
- }
7
- for (const child of children) {
8
- yield* traverse(child, chain);
9
- }
10
- }
11
- export {
12
- traverse as default
13
- };
14
- //# sourceMappingURL=traverse.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/shared/traverse.ts"],
4
- "sourcesContent": ["/**\n * Traverse a tree and yield the sequence of parent nodes to each leaf node.\n *\n * @param tree - The tree to traverse.\n * @param parents - The sequence of parent nodes for the current node.\n */\nexport default function* traverse<T extends { children?: readonly T[] }>(\n tree: T,\n parents: readonly T[] = [],\n): Generator<readonly T[], undefined, undefined> {\n const chain = [...parents, tree];\n const children = tree.children ?? [];\n\n if (children.length === 0) {\n yield chain;\n }\n\n for (const child of children) {\n yield* traverse(child, chain);\n }\n}\n"],
5
- "mappings": "AAMe,UAAR,SACL,MACA,UAAwB,CAAC,GACsB;AAC/C,QAAM,QAAQ,CAAC,GAAG,SAAS,IAAI;AAC/B,QAAM,WAAW,KAAK,YAAY,CAAC;AAEnC,MAAI,SAAS,WAAW,GAAG;AACzB,UAAM;AAAA,EACR;AAEA,aAAW,SAAS,UAAU;AAC5B,WAAO,SAAS,OAAO,KAAK;AAAA,EAC9B;AACF;",
6
- "names": []
7
- }