@vaadin/hilla-file-router 24.6.0-beta2 → 24.6.0-beta4

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.6.0-beta2",
3
+ "version": "24.6.0-beta4",
4
4
  "description": "Hilla file-based router",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -70,6 +70,7 @@
70
70
  "@types/sinon": "^10.0.17",
71
71
  "@types/sinon-chai": "^3.2.12",
72
72
  "chai-as-promised": "^7.1.1",
73
+ "chai-deep-equal-ignore-undefined": "^1.1.1",
73
74
  "chai-fs": "^2.0.0",
74
75
  "chai-like": "^1.1.1",
75
76
  "deep-equal-in-any-order": "^2.0.6",
@@ -79,9 +80,9 @@
79
80
  "type-fest": "^4.9.0"
80
81
  },
81
82
  "dependencies": {
82
- "@vaadin/hilla-generator-utils": "24.6.0-beta2",
83
- "@vaadin/hilla-react-auth": "24.6.0-beta2",
84
- "@vaadin/hilla-react-signals": "24.6.0-beta2",
83
+ "@vaadin/hilla-generator-utils": "24.6.0-beta4",
84
+ "@vaadin/hilla-react-auth": "24.6.0-beta4",
85
+ "@vaadin/hilla-react-signals": "24.6.0-beta4",
85
86
  "react": "^18.2.0",
86
87
  "rollup": "^4.12.0",
87
88
  "typescript": "5.6.2"
@@ -1 +1 @@
1
- {"version":3,"file":"RouterConfigurationBuilder.d.ts","sourceRoot":"","sources":["../src/runtime/RouterConfigurationBuilder.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,OAAO,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,KAAK,EACV,aAAa,EAGb,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACX,MAAM,aAAa,CAAC;AAErB,UAAU,SAAS;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;CAC5B;AAMD,MAAM,MAAM,SAAS,GAAG,SAAS,WAAW,EAAE,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG,WAAW,EAAE,CAAC;AAE9C,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAChC,QAAQ,EAAE,WAAW,GAAG,SAAS,EACjC,UAAU,EAAE,CAAC,GAAG,SAAS,EACzB,QAAQ,CAAC,EAAE,SAAS,KACjB,WAAW,GAAG,SAAS,CAAC;AAE7B,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC;AA4DrE;;;GAGG;AACH,qBAAa,0BAA0B;;IAGrC;;;;;OAKG;IACH,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAIxC;;;;;;OAMG;IACH,cAAc,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI;IAqCtD;;;;;;;;OAQG;IACH,YAAY,CAAC,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAqCjE;;;;OAIG;IACH,UAAU,CAAC,eAAe,EAAE,aAAa,GAAG,IAAI;IAgChD;;;;;;OAMG;IACH,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI;IAUpC,MAAM,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,CAAC,SAAS,CAAC,GAAG,IAAI;IAC3F,MAAM,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI;IAwDvF;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,mBAAmB;CAqDzD"}
1
+ {"version":3,"file":"RouterConfigurationBuilder.d.ts","sourceRoot":"","sources":["../src/runtime/RouterConfigurationBuilder.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,OAAO,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EACV,aAAa,EAGb,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACX,MAAM,aAAa,CAAC;AAErB,UAAU,SAAS;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;CAC5B;AAaD,MAAM,MAAM,SAAS,GAAG,SAAS,WAAW,EAAE,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG,WAAW,EAAE,CAAC;AAE9C,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAChC,QAAQ,EAAE,WAAW,GAAG,SAAS,EACjC,UAAU,EAAE,CAAC,GAAG,SAAS,EACzB,QAAQ,CAAC,EAAE,SAAS,KACjB,WAAW,GAAG,SAAS,CAAC;AAE7B,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC;AAsBrE;;;GAGG;AACH,qBAAa,0BAA0B;;IAGrC;;;;;OAKG;IACH,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAIxC;;;;;;OAMG;IACH,cAAc,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI;IAuCtD;;;;;;;;OAQG;IACH,YAAY,CAAC,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAqCjE;;;;OAIG;IACH,UAAU,CAAC,eAAe,EAAE,aAAa,GAAG,IAAI;IA+FhD;;;;;;OAMG;IACH,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI;IAUpC,MAAM,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,CAAC,SAAS,CAAC,GAAG,IAAI;IAC3F,MAAM,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI;IAyDvF;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,mBAAmB;CAqGzD"}
@@ -6,7 +6,10 @@ import {
6
6
  import { convertComponentNameToTitle } from "../shared/convertComponentNameToTitle.js";
7
7
  import { transformTree } from "../shared/transformTree.js";
8
8
  function isReactRouteModule(module) {
9
- return module ? "default" in module && typeof module.default === "function" : true;
9
+ if (!module) {
10
+ return true;
11
+ }
12
+ return "default" in module && typeof module.default === "function" || "config" in module && typeof module.config === "object";
10
13
  }
11
14
  function createRouteEntry(route) {
12
15
  return [`${route.path ?? ""}-${route.children ? "n" : "i"}`, route];
@@ -17,40 +20,11 @@ var RouteHandleFlags = /* @__PURE__ */ ((RouteHandleFlags2) => {
17
20
  RouteHandleFlags2["SKIP_LAYOUTS"] = "skipLayouts";
18
21
  return RouteHandleFlags2;
19
22
  })(RouteHandleFlags || {});
20
- function hasRouteHandleFlag(route, flag) {
21
- return typeof route.handle === "object" && flag in route.handle && route.handle[flag];
22
- }
23
- const categories = ["default", "match"];
24
- function split(originalRoutes, rule) {
25
- return transformTree(
26
- originalRoutes,
27
- (routes, next) => (
28
- // Split a single routes list onto two separate lists.
29
- routes.reduce(
30
- (lists, route) => {
31
- if (rule(route)) {
32
- lists.match.push(route);
33
- return lists;
34
- }
35
- if (!route.children?.length) {
36
- lists.default.push(route);
37
- return lists;
38
- }
39
- const childrenLists = next(...route.children);
40
- for (const category of categories) {
41
- if (childrenLists[category].length) {
42
- lists[category].push({
43
- ...route,
44
- children: childrenLists[category]
45
- });
46
- }
47
- }
48
- return lists;
49
- },
50
- { match: [], default: [] }
51
- )
52
- )
53
- );
23
+ function getRouteHandleFlag(route, flag) {
24
+ if (typeof route.handle === "object" && flag in route.handle) {
25
+ return route.handle[flag];
26
+ }
27
+ return void 0;
54
28
  }
55
29
  class RouterConfigurationBuilder {
56
30
  #modifiers = [];
@@ -75,7 +49,9 @@ class RouterConfigurationBuilder {
75
49
  if (added) {
76
50
  const { module, path, flowLayout } = added;
77
51
  if (!isReactRouteModule(module)) {
78
- throw new Error(`The module for the "${path}" section doesn't have the React component exported by default`);
52
+ throw new Error(
53
+ `The module for the "${path}" section doesn't have the React component exported by default or a ViewConfig object exported as "config"`
54
+ );
79
55
  }
80
56
  const element = module?.default ? createElement(module.default) : void 0;
81
57
  const handle = {
@@ -118,7 +94,7 @@ class RouterConfigurationBuilder {
118
94
  { index: true, element: createElement(component), handle: config }
119
95
  ];
120
96
  this.update(fallbackRoutes, (original, added, children) => {
121
- if (original && !hasRouteHandleFlag(original, "ignoreFallback" /* IGNORE_FALLBACK */)) {
97
+ if (original && !getRouteHandleFlag(original, "ignoreFallback" /* IGNORE_FALLBACK */)) {
122
98
  if (!children) {
123
99
  return original;
124
100
  }
@@ -148,24 +124,65 @@ class RouterConfigurationBuilder {
148
124
  if (!originalRoutes) {
149
125
  return originalRoutes;
150
126
  }
151
- const { match: serverList, default: clientList } = split(
127
+ const result = transformTree(
152
128
  originalRoutes,
153
- (route) => hasRouteHandleFlag(route, "flowLayout" /* FLOW_LAYOUT */)
129
+ null,
130
+ (routes, next) => (
131
+ // Split a single routes list onto three separate lists:
132
+ // - A list of server routes
133
+ // - A list of client routes
134
+ // - A list of routes which will be moved to either server or client
135
+ // list. It depends on the parent route.
136
+ routes.reduce(
137
+ (lists, route) => {
138
+ const { server, client, ambivalent } = next(route.children ?? []);
139
+ const flag = getRouteHandleFlag(route, "flowLayout" /* FLOW_LAYOUT */);
140
+ if (flag === true) {
141
+ lists.server.push({
142
+ ...route,
143
+ children: server.length + ambivalent.length > 0 ? [...server, ...ambivalent] : void 0
144
+ });
145
+ } else if (server.length > 0) {
146
+ lists.server.push({
147
+ ...route,
148
+ children: server
149
+ });
150
+ }
151
+ if (flag === false || client.length > 0) {
152
+ lists.client.push({
153
+ ...route,
154
+ children: client.length > 0 ? client : void 0
155
+ });
156
+ }
157
+ if (flag === void 0 && (lists.server.every(({ path }) => path !== route.path) || ambivalent.length > 0)) {
158
+ lists.ambivalent.push({
159
+ ...route,
160
+ children: ambivalent.length > 0 ? ambivalent : void 0
161
+ });
162
+ }
163
+ return lists;
164
+ },
165
+ { server: [], client: [], ambivalent: [] }
166
+ )
167
+ )
154
168
  );
155
169
  return [
156
- ...serverList.length ? [
157
- // The server subtree is wrapped with the server layout component,
158
- // which applies the top-level server layout to all matches.
170
+ ...result.server.length ? [
171
+ // The server routes are wrapped with the route that has a layout
172
+ // element. It also has the `IGNORE_FALLBACK` flag to remove the
173
+ // fallback route from reach.
159
174
  {
160
175
  element: createElement(layoutComponent),
161
- children: serverList,
176
+ children: result.server,
162
177
  handle: {
163
178
  ["ignoreFallback" /* IGNORE_FALLBACK */]: true
164
179
  }
165
180
  }
166
181
  ] : [],
167
- // The client route subtree is preserved without wrapping.
168
- ...clientList
182
+ // The client routes are preserved without wrapping.
183
+ ...result.client,
184
+ // The ambivalent routes are considered as client routes.
185
+ ...result.ambivalent
169
186
  ];
170
187
  });
171
188
  return this;
@@ -193,6 +210,7 @@ class RouterConfigurationBuilder {
193
210
  this.#modifiers.push(
194
211
  (existingRoutes) => transformTree(
195
212
  [existingRoutes, routes],
213
+ null,
196
214
  ([original, added], next) => {
197
215
  if (original && added) {
198
216
  const originalMap = new Map(original.map((route) => createRouteEntry(route)));
@@ -203,11 +221,11 @@ class RouterConfigurationBuilder {
203
221
  const addedRoute = addedMap.get(path);
204
222
  let route;
205
223
  if (originalRoute && addedRoute) {
206
- route = callback(originalRoute, addedRoute, next(originalRoute.children, addedRoute.children));
224
+ route = callback(originalRoute, addedRoute, next([originalRoute.children, addedRoute.children]));
207
225
  } else if (originalRoute) {
208
- route = callback(originalRoute, void 0, next(originalRoute.children, void 0));
226
+ route = callback(originalRoute, void 0, next([originalRoute.children, void 0]));
209
227
  } else {
210
- route = callback(void 0, addedRoute, next(void 0, addedRoute.children));
228
+ route = callback(void 0, addedRoute, next([void 0, addedRoute.children]));
211
229
  }
212
230
  if (route) {
213
231
  originalMap.set(path, route);
@@ -215,9 +233,9 @@ class RouterConfigurationBuilder {
215
233
  }
216
234
  return [...originalMap.values()];
217
235
  } else if (original) {
218
- return original.map((route) => callback(route, void 0, next(route.children, void 0))).filter((r) => r != null);
236
+ return original.map((route) => callback(route, void 0, next([route.children, void 0]))).filter((r) => r != null);
219
237
  } else if (added) {
220
- return added.map((route) => callback(void 0, route, next(void 0, route.children))).filter((r) => r != null);
238
+ return added.map((route) => callback(void 0, route, next([void 0, route.children]))).filter((r) => r != null);
221
239
  }
222
240
  return void 0;
223
241
  }
@@ -233,7 +251,22 @@ class RouterConfigurationBuilder {
233
251
  const routes = this.#modifiers.reduce((acc, mod) => mod(acc) ?? acc, void 0) ?? [];
234
252
  return {
235
253
  routes,
236
- router: createBrowserRouter([...routes], { basename: new URL(document.baseURI).pathname, ...options })
254
+ router: createBrowserRouter([...routes], {
255
+ basename: new URL(document.baseURI).pathname,
256
+ future: {
257
+ // eslint-disable-next-line camelcase
258
+ v7_fetcherPersist: true,
259
+ // eslint-disable-next-line camelcase
260
+ v7_normalizeFormMethod: true,
261
+ // eslint-disable-next-line camelcase
262
+ v7_partialHydration: true,
263
+ // eslint-disable-next-line camelcase
264
+ v7_relativeSplatPath: true,
265
+ // eslint-disable-next-line camelcase
266
+ v7_skipActionErrorRevalidation: true
267
+ },
268
+ ...options
269
+ })
237
270
  };
238
271
  }
239
272
  #withLayoutSkipping() {
@@ -241,33 +274,51 @@ class RouterConfigurationBuilder {
241
274
  if (!originalRoutes) {
242
275
  return originalRoutes;
243
276
  }
244
- const { match: noLayoutList, default: layoutList } = split(
277
+ const result = transformTree(
245
278
  originalRoutes,
246
- (route) => hasRouteHandleFlag(route, "skipLayouts" /* SKIP_LAYOUTS */)
247
- );
248
- const finalNoLayoutList = transformTree(
249
- noLayoutList,
250
- (routes, next) => routes.map((route) => {
251
- if (hasRouteHandleFlag(route, "skipLayouts" /* SKIP_LAYOUTS */)) {
252
- return route;
253
- }
254
- const { element, ...rest } = route;
255
- return route.children?.length ? {
256
- ...rest,
257
- children: next(...route.children)
258
- } : rest;
259
- })
279
+ null,
280
+ (routes, next) => (
281
+ // Split a single routes list onto two separate lists.
282
+ routes.reduce(
283
+ (lists, route) => {
284
+ if (getRouteHandleFlag(route, "skipLayouts" /* SKIP_LAYOUTS */)) {
285
+ lists.skipped.push(route);
286
+ return lists;
287
+ }
288
+ if (!route.children?.length) {
289
+ lists.regular.push(route);
290
+ return lists;
291
+ }
292
+ const { skipped, regular } = next(route.children ?? []);
293
+ if (skipped.length > 0) {
294
+ const { element, ...rest } = route;
295
+ lists.skipped.push({
296
+ ...rest,
297
+ children: skipped
298
+ });
299
+ }
300
+ if (regular.length > 0) {
301
+ lists.regular.push({
302
+ ...route,
303
+ children: regular
304
+ });
305
+ }
306
+ return lists;
307
+ },
308
+ { skipped: [], regular: [] }
309
+ )
310
+ )
260
311
  );
261
312
  return [
262
- ...finalNoLayoutList.length ? [
313
+ ...result.skipped.length ? [
263
314
  {
264
- children: finalNoLayoutList,
315
+ children: result.skipped,
265
316
  handle: {
266
317
  ["ignoreFallback" /* IGNORE_FALLBACK */]: true
267
318
  }
268
319
  }
269
320
  ] : [],
270
- ...layoutList
321
+ ...result.regular
271
322
  ];
272
323
  });
273
324
  return this;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/runtime/RouterConfigurationBuilder.ts"],
4
- "sourcesContent": ["/* eslint-disable @typescript-eslint/consistent-type-assertions */\nimport { protectRoute } from '@vaadin/hilla-react-auth';\nimport { type ComponentType, createElement } from 'react';\nimport {\n createBrowserRouter,\n type IndexRouteObject,\n type NonIndexRouteObject,\n type RouteObject,\n} from 'react-router-dom';\nimport type { TupleToUnion } from 'type-fest';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport { transformTree } from '../shared/transformTree.js';\nimport type {\n AgnosticRoute,\n Module,\n RouteModule,\n RouterBuildOptions,\n RouterConfiguration,\n ViewConfig,\n} from '../types.js';\n\ninterface RouteBase {\n path?: string;\n children?: readonly this[];\n}\n\nfunction isReactRouteModule(module?: Module): module is RouteModule<ComponentType> | undefined {\n return module ? 'default' in module && typeof module.default === 'function' : true;\n}\n\nexport type RouteList = readonly RouteObject[];\nexport type WritableRouteList = RouteObject[];\n\nexport type RouteTransformer<T> = (\n original: RouteObject | undefined,\n overriding: T | undefined,\n children?: RouteList,\n) => RouteObject | undefined;\n\nexport type RouteListSplittingRule = (route: RouteObject) => boolean;\n\ntype RoutesModifier = (routes: RouteList | undefined) => RouteList | undefined;\n\nfunction createRouteEntry<T extends RouteBase>(route: T): readonly [key: string, value: T] {\n return [`${route.path ?? ''}-${route.children ? 'n' : 'i'}`, route];\n}\n\nenum RouteHandleFlags {\n FLOW_LAYOUT = 'flowLayout',\n IGNORE_FALLBACK = 'ignoreFallback',\n SKIP_LAYOUTS = 'skipLayouts',\n}\n\nfunction hasRouteHandleFlag<T extends RouteHandleFlags>(route: RouteObject, flag: T): boolean {\n return typeof route.handle === 'object' && flag in route.handle && (route.handle as Record<T, boolean>)[flag];\n}\n\nconst categories = ['default', 'match'] as const;\n\ntype Category = TupleToUnion<typeof categories>;\n\nfunction split(originalRoutes: RouteList, rule: RouteListSplittingRule): Readonly<Record<Category, RouteList>> {\n return transformTree<RouteList, Readonly<Record<Category, RouteList>>>(originalRoutes, (routes, next) =>\n // Split a single routes list onto two separate lists.\n routes.reduce<Record<Category, WritableRouteList>>(\n (lists, route) => {\n if (rule(route)) {\n // If the route satisfies the rule, it goes to the first list.\n lists.match.push(route);\n return lists;\n }\n\n if (!route.children?.length) {\n // Leaf routes go to the second list.\n lists.default.push(route);\n return lists;\n }\n\n // Route children: separate them to different subtrees, and copy the\n // current route to either or both the lists with the respective\n // subtree as children.\n const childrenLists = next(...route.children);\n\n for (const category of categories) {\n if (childrenLists[category].length) {\n lists[category].push({\n ...route,\n children: childrenLists[category],\n } as RouteObject);\n }\n }\n\n return lists;\n },\n { match: [], default: [] },\n ),\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 #modifiers: RoutesModifier[] = [];\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: RouteList): this {\n return this.update(routes);\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 return this.update(routes, (original, added, children) => {\n if (added) {\n const { module, path, flowLayout } = added;\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 element = module?.default ? createElement(module.default) : undefined;\n const handle = {\n ...module?.config,\n title: module?.config?.title ?? convertComponentNameToTitle(module?.default),\n flowLayout: module?.config?.flowLayout ?? flowLayout,\n };\n\n if (path === '' && !children) {\n return {\n ...original,\n element,\n handle,\n index: true,\n } as IndexRouteObject;\n }\n\n return {\n ...original,\n path: module?.config?.route ?? path,\n element,\n children,\n handle,\n } as NonIndexRouteObject;\n }\n\n return original;\n });\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 * @param config - An optional configuration that will be applied to\n * each fallback component.\n */\n withFallback(component: ComponentType, config?: ViewConfig): this {\n this.withLayout(component);\n\n // Fallback adds two routes, so that the index (empty path) has a fallback too\n const fallbackRoutes: RouteList = [\n { path: '*', element: createElement(component), handle: config },\n { index: true, element: createElement(component), handle: config },\n ];\n\n this.update(fallbackRoutes, (original, added, children) => {\n if (original && !hasRouteHandleFlag(original, RouteHandleFlags.IGNORE_FALLBACK)) {\n if (!children) {\n return original;\n }\n\n const _fallback = [...fallbackRoutes];\n\n if (children.some(({ path }) => path === '*')) {\n _fallback.shift();\n }\n\n if (children.some(({ index: i, path }) => i ?? path?.includes('?'))) {\n _fallback.pop();\n }\n\n return {\n ...original,\n children: [...children, ..._fallback],\n } as RouteObject;\n }\n\n return added!;\n });\n\n return this;\n }\n\n /**\n * Adds the layoutComponent as the parent layout to views with the flowLayouts ViewConfiguration set.\n *\n * @param layoutComponent - layout component to use, usually Flow\n */\n withLayout(layoutComponent: ComponentType): this {\n this.#modifiers.push((originalRoutes) => {\n if (!originalRoutes) {\n return originalRoutes;\n }\n\n const { match: serverList, default: clientList } = split(originalRoutes, (route) =>\n hasRouteHandleFlag(route, RouteHandleFlags.FLOW_LAYOUT),\n );\n\n return [\n ...(serverList.length\n ? [\n // The server subtree is wrapped with the server layout component,\n // which applies the top-level server layout to all matches.\n {\n element: createElement(layoutComponent),\n children: serverList as RouteObject[],\n handle: {\n [RouteHandleFlags.IGNORE_FALLBACK]: true,\n },\n },\n ]\n : []),\n // The client route subtree is preserved without wrapping.\n ...clientList,\n ];\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.update(undefined, (route, _, children) => {\n const finalRoute = protectRoute(route!, redirectPath);\n finalRoute.children = children as RouteObject[] | undefined;\n return finalRoute;\n });\n\n return this;\n }\n\n update<T extends RouteBase>(routes: undefined, callback: RouteTransformer<undefined>): this;\n update<T extends RouteBase>(routes: readonly T[], callback?: RouteTransformer<T>): this;\n update<T extends RouteBase>(\n routes: readonly T[] | undefined,\n callback: RouteTransformer<T | undefined> = (original, overriding, children) =>\n ({\n ...original,\n ...overriding,\n children,\n }) as RouteObject,\n ): this {\n this.#modifiers.push((existingRoutes) =>\n transformTree<[RouteList | undefined, readonly T[] | undefined], RouteList | undefined>(\n [existingRoutes, routes],\n ([original, added], next) => {\n if (original && added) {\n const originalMap = new Map(original.map((route) => createRouteEntry(route)));\n const addedMap = new Map(added.map((route) => createRouteEntry(route)));\n\n const paths = new Set([...originalMap.keys(), ...addedMap.keys()]);\n\n for (const path of paths) {\n const originalRoute = originalMap.get(path);\n const addedRoute = addedMap.get(path);\n\n let route: RouteObject | undefined;\n if (originalRoute && addedRoute) {\n route = callback(originalRoute, addedRoute, next(originalRoute.children, addedRoute.children));\n } else if (originalRoute) {\n route = callback(originalRoute, undefined, next(originalRoute.children, undefined));\n } else {\n route = callback(undefined, addedRoute, next(undefined, addedRoute!.children));\n }\n\n if (route) {\n originalMap.set(path, route);\n }\n }\n\n return [...originalMap.values()];\n } else if (original) {\n return original\n .map((route) => callback(route, undefined, next(route.children, undefined)))\n .filter((r) => r != null);\n } else if (added) {\n return added\n .map((route) => callback(undefined, route, next(undefined, route.children)))\n .filter((r) => r != null);\n }\n\n return undefined;\n },\n ),\n );\n return this;\n }\n\n /**\n * Builds the router with the current list of routes.\n */\n build(options?: RouterBuildOptions): RouterConfiguration {\n this.#withLayoutSkipping();\n const routes = this.#modifiers.reduce<RouteList | undefined>((acc, mod) => mod(acc) ?? acc, undefined) ?? [];\n\n return {\n routes,\n router: createBrowserRouter([...routes], { basename: new URL(document.baseURI).pathname, ...options }),\n };\n }\n\n #withLayoutSkipping(): this {\n this.#modifiers.push((originalRoutes) => {\n if (!originalRoutes) {\n return originalRoutes;\n }\n\n const { match: noLayoutList, default: layoutList } = split(originalRoutes, (route) =>\n hasRouteHandleFlag(route, RouteHandleFlags.SKIP_LAYOUTS),\n );\n\n const finalNoLayoutList = transformTree<RouteList, RouteList>(noLayoutList, (routes, next) =>\n routes.map((route) => {\n if (hasRouteHandleFlag(route, RouteHandleFlags.SKIP_LAYOUTS)) {\n return route;\n }\n\n const { element, ...rest } = route;\n return route.children?.length\n ? ({\n ...rest,\n children: next(...route.children),\n } as RouteObject)\n : rest;\n }),\n );\n\n return [\n ...(finalNoLayoutList.length\n ? [\n {\n children: finalNoLayoutList as RouteObject[],\n handle: {\n [RouteHandleFlags.IGNORE_FALLBACK]: true,\n },\n },\n ]\n : []),\n ...layoutList,\n ];\n });\n\n return this;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* eslint-disable @typescript-eslint/consistent-type-assertions */\nimport { protectRoute } from '@vaadin/hilla-react-auth';\nimport { type ComponentType, createElement } from 'react';\nimport {\n createBrowserRouter,\n type IndexRouteObject,\n type NonIndexRouteObject,\n type RouteObject,\n} from 'react-router-dom';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport { transformTree } from '../shared/transformTree.js';\nimport type {\n AgnosticRoute,\n Module,\n RouteModule,\n RouterBuildOptions,\n RouterConfiguration,\n ViewConfig,\n} from '../types.js';\n\ninterface RouteBase {\n path?: string;\n children?: readonly this[];\n}\n\nfunction isReactRouteModule(module?: Module): module is RouteModule<ComponentType> | undefined {\n if (!module) {\n return true;\n }\n\n return (\n ('default' in module && typeof module.default === 'function') ||\n ('config' in module && typeof module.config === 'object')\n );\n}\n\nexport type RouteList = readonly RouteObject[];\nexport type WritableRouteList = RouteObject[];\n\nexport type RouteTransformer<T> = (\n original: RouteObject | undefined,\n overriding: T | undefined,\n children?: RouteList,\n) => RouteObject | undefined;\n\nexport type RouteListSplittingRule = (route: RouteObject) => boolean;\n\ntype RoutesModifier = (routes: RouteList | undefined) => RouteList | undefined;\n\nfunction createRouteEntry<T extends RouteBase>(route: T): readonly [key: string, value: T] {\n return [`${route.path ?? ''}-${route.children ? 'n' : 'i'}`, route];\n}\n\nenum RouteHandleFlags {\n FLOW_LAYOUT = 'flowLayout',\n IGNORE_FALLBACK = 'ignoreFallback',\n SKIP_LAYOUTS = 'skipLayouts',\n}\n\nfunction getRouteHandleFlag<T extends RouteHandleFlags>(route: RouteObject, flag: T): boolean | undefined {\n if (typeof route.handle === 'object' && flag in route.handle) {\n return (route.handle as Record<T, boolean>)[flag];\n }\n\n return undefined;\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 #modifiers: RoutesModifier[] = [];\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: RouteList): this {\n return this.update(routes);\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 return this.update(routes, (original, added, children) => {\n if (added) {\n const { module, path, flowLayout } = added;\n if (!isReactRouteModule(module)) {\n throw new Error(\n `The module for the \"${path}\" section doesn't have the React component exported by default or a ViewConfig object exported as \"config\"`,\n );\n }\n\n const element = module?.default ? createElement(module.default) : undefined;\n const handle = {\n ...module?.config,\n title: module?.config?.title ?? convertComponentNameToTitle(module?.default),\n flowLayout: module?.config?.flowLayout ?? flowLayout,\n };\n\n if (path === '' && !children) {\n return {\n ...original,\n element,\n handle,\n index: true,\n } as IndexRouteObject;\n }\n\n return {\n ...original,\n path: module?.config?.route ?? path,\n element,\n children,\n handle,\n } as NonIndexRouteObject;\n }\n\n return original;\n });\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 * @param config - An optional configuration that will be applied to\n * each fallback component.\n */\n withFallback(component: ComponentType, config?: ViewConfig): this {\n this.withLayout(component);\n\n // Fallback adds two routes, so that the index (empty path) has a fallback too\n const fallbackRoutes: RouteList = [\n { path: '*', element: createElement(component), handle: config },\n { index: true, element: createElement(component), handle: config },\n ];\n\n this.update(fallbackRoutes, (original, added, children) => {\n if (original && !getRouteHandleFlag(original, RouteHandleFlags.IGNORE_FALLBACK)) {\n if (!children) {\n return original;\n }\n\n const _fallback = [...fallbackRoutes];\n\n if (children.some(({ path }) => path === '*')) {\n _fallback.shift();\n }\n\n if (children.some(({ index: i, path }) => i ?? path?.includes('?'))) {\n _fallback.pop();\n }\n\n return {\n ...original,\n children: [...children, ..._fallback],\n } as RouteObject;\n }\n\n return added!;\n });\n\n return this;\n }\n\n /**\n * Adds the layoutComponent as the parent layout to views with the flowLayouts ViewConfiguration set.\n *\n * @param layoutComponent - layout component to use, usually Flow\n */\n withLayout(layoutComponent: ComponentType): this {\n this.#modifiers.push((originalRoutes) => {\n if (!originalRoutes) {\n return originalRoutes;\n }\n\n type Accumulator<T extends RouteList> = Readonly<{\n server: T;\n client: T;\n ambivalent: T;\n }>;\n\n const result = transformTree<RouteList, Accumulator<RouteList>>(originalRoutes, null, (routes, next) =>\n // Split a single routes list onto three separate lists:\n // - A list of server routes\n // - A list of client routes\n // - A list of routes which will be moved to either server or client\n // list. It depends on the parent route.\n routes.reduce<Accumulator<WritableRouteList>>(\n (lists, route) => {\n const { server, client, ambivalent } = next(route.children ?? []);\n\n const flag = getRouteHandleFlag(route, RouteHandleFlags.FLOW_LAYOUT);\n\n // If the route has `flowLayout` flag explicitly enabled, it goes to\n // the server list. The children are also affected by the flag\n // unless they have it explicitly disabled.\n if (flag === true) {\n lists.server.push({\n ...route,\n children: server.length + ambivalent.length > 0 ? [...server, ...ambivalent] : undefined,\n } as RouteObject);\n } else if (server.length > 0) {\n // Even if the route doesn't have the flag, it goes to the server\n // list if any of the children has the flag enabled.\n lists.server.push({\n ...route,\n children: server,\n } as RouteObject);\n }\n\n // If the route has `flowLayout` flag explicitly disabled, it goes\n // to the client list. The route children are not affected by the\n // flag.\n if (flag === false || client.length > 0) {\n lists.client.push({\n ...route,\n children: client.length > 0 ? client : undefined,\n } as RouteObject);\n }\n\n // The route without the flag go to the `default` list. Then it will\n // be moved to either server or client list based on the parent\n // route.\n if (\n flag === undefined &&\n (lists.server.every(({ path }) => path !== route.path) || ambivalent.length > 0)\n ) {\n lists.ambivalent.push({\n ...route,\n children: ambivalent.length > 0 ? ambivalent : undefined,\n } as RouteObject);\n }\n\n return lists;\n },\n { server: [], client: [], ambivalent: [] },\n ),\n );\n\n return [\n ...(result.server.length\n ? [\n // The server routes are wrapped with the route that has a layout\n // element. It also has the `IGNORE_FALLBACK` flag to remove the\n // fallback route from reach.\n {\n element: createElement(layoutComponent),\n children: result.server as RouteObject[],\n handle: {\n [RouteHandleFlags.IGNORE_FALLBACK]: true,\n },\n },\n ]\n : []),\n // The client routes are preserved without wrapping.\n ...result.client,\n // The ambivalent routes are considered as client routes.\n ...result.ambivalent,\n ];\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.update(undefined, (route, _, children) => {\n const finalRoute = protectRoute(route!, redirectPath);\n finalRoute.children = children as RouteObject[] | undefined;\n return finalRoute;\n });\n\n return this;\n }\n\n update<T extends RouteBase>(routes: undefined, callback: RouteTransformer<undefined>): this;\n update<T extends RouteBase>(routes: readonly T[], callback?: RouteTransformer<T>): this;\n update<T extends RouteBase>(\n routes: readonly T[] | undefined,\n callback: RouteTransformer<T | undefined> = (original, overriding, children) =>\n ({\n ...original,\n ...overriding,\n children,\n }) as RouteObject,\n ): this {\n this.#modifiers.push((existingRoutes) =>\n transformTree<[RouteList | undefined, readonly T[] | undefined], RouteList | undefined>(\n [existingRoutes, routes],\n null,\n ([original, added], next) => {\n if (original && added) {\n const originalMap = new Map(original.map((route) => createRouteEntry(route)));\n const addedMap = new Map(added.map((route) => createRouteEntry(route)));\n\n const paths = new Set([...originalMap.keys(), ...addedMap.keys()]);\n\n for (const path of paths) {\n const originalRoute = originalMap.get(path);\n const addedRoute = addedMap.get(path);\n\n let route: RouteObject | undefined;\n if (originalRoute && addedRoute) {\n route = callback(originalRoute, addedRoute, next([originalRoute.children, addedRoute.children]));\n } else if (originalRoute) {\n route = callback(originalRoute, undefined, next([originalRoute.children, undefined]));\n } else {\n route = callback(undefined, addedRoute, next([undefined, addedRoute!.children]));\n }\n\n if (route) {\n originalMap.set(path, route);\n }\n }\n\n return [...originalMap.values()];\n } else if (original) {\n return original\n .map((route) => callback(route, undefined, next([route.children, undefined])))\n .filter((r) => r != null);\n } else if (added) {\n return added\n .map((route) => callback(undefined, route, next([undefined, route.children])))\n .filter((r) => r != null);\n }\n\n return undefined;\n },\n ),\n );\n return this;\n }\n\n /**\n * Builds the router with the current list of routes.\n */\n build(options?: RouterBuildOptions): RouterConfiguration {\n this.#withLayoutSkipping();\n const routes = this.#modifiers.reduce<RouteList | undefined>((acc, mod) => mod(acc) ?? acc, undefined) ?? [];\n\n return {\n routes,\n router: createBrowserRouter([...routes], {\n basename: new URL(document.baseURI).pathname,\n future: {\n // eslint-disable-next-line camelcase\n v7_fetcherPersist: true,\n // eslint-disable-next-line camelcase\n v7_normalizeFormMethod: true,\n // eslint-disable-next-line camelcase\n v7_partialHydration: true,\n // eslint-disable-next-line camelcase\n v7_relativeSplatPath: true,\n // eslint-disable-next-line camelcase\n v7_skipActionErrorRevalidation: true,\n },\n ...options,\n }),\n };\n }\n\n #withLayoutSkipping(): this {\n this.#modifiers.push((originalRoutes) => {\n if (!originalRoutes) {\n return originalRoutes;\n }\n\n type Accumulator<T extends RouteList> = Readonly<{\n skipped: T;\n regular: T;\n }>;\n\n const result = transformTree<RouteList, Accumulator<RouteList>>(originalRoutes, null, (routes, next) =>\n // Split a single routes list onto two separate lists.\n routes.reduce<Accumulator<WritableRouteList>>(\n (lists, route) => {\n // If the route has `skipLayout` flag, it goes to the `skipped` list.\n if (getRouteHandleFlag(route, RouteHandleFlags.SKIP_LAYOUTS)) {\n lists.skipped.push(route);\n return lists;\n }\n\n // If the route is leaf, it goes to the `regular` list.\n if (!route.children?.length) {\n lists.regular.push(route);\n return lists;\n }\n\n // As of children, we have to split them into two lists as well.\n const { skipped, regular } = next(route.children ?? []);\n\n // If we have `skipped` list of children, we have to remove the\n // `element` property of the router to prevent the layout from\n // rendering. Then, we add the current route to the `skipped` list.\n if (skipped.length > 0) {\n const { element, ...rest } = route;\n\n lists.skipped.push({\n ...rest,\n children: skipped,\n } as RouteObject);\n }\n\n // In case of `regular` children, we just add the current route to\n // the `regular` list if there are any children.\n if (regular.length > 0) {\n lists.regular.push({\n ...route,\n children: regular,\n } as RouteObject);\n }\n\n return lists;\n },\n { skipped: [], regular: [] },\n ),\n );\n\n // We don't need a fallback for the skipped routes, so we have to wrap\n // them with the route with the `IGNORE_FALLBACK` flag.\n return [\n ...(result.skipped.length\n ? [\n {\n children: result.skipped as RouteObject[],\n handle: {\n [RouteHandleFlags.IGNORE_FALLBACK]: true,\n },\n },\n ]\n : []),\n ...result.regular,\n ];\n });\n\n return this;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["RouteHandleFlags"]
7
7
  }
@@ -5,6 +5,7 @@ export declare const viewsSignal: import("@preact/signals-core").Signal<Readonly
5
5
  loginRequired?: boolean;
6
6
  route?: string;
7
7
  flowLayout?: boolean;
8
+ skipLayouts?: boolean;
8
9
  menu?: Readonly<{
9
10
  title?: string;
10
11
  order?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"createMenuItems.d.ts","sourceRoot":"","sources":["../src/runtime/createMenuItems.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,aAAa,CAAC;AAExD,eAAO,MAAM,WAAW;;;;;;;aAsBiB,CAAC;aAOpC,CAAF;eAGU,CAAA;YAAmE,CAAC;;iBAhCT,CAAC;AAE1E;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,SAAS,QAAQ,EAAE,CA0BrD"}
1
+ {"version":3,"file":"createMenuItems.d.ts","sourceRoot":"","sources":["../src/runtime/createMenuItems.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,aAAa,CAAC;AAExD,eAAO,MAAM,WAAW;;;;;;;;aA0BZ,CAAC;aAKc,CAAA;eAEC,CAAC;YAE3B,CAAC;;iBAnCsE,CAAC;AAE1E;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,SAAS,QAAQ,EAAE,CA0BrD"}
@@ -2,7 +2,7 @@ function __REGISTER__(feature, vaadinObj = window.Vaadin ??= {}) {
2
2
  vaadinObj.registrations ??= [];
3
3
  vaadinObj.registrations.push({
4
4
  is: feature ? `${"@vaadin/hilla-file-router"}/${feature}` : "@vaadin/hilla-file-router",
5
- version: "24.6.0-beta2"
5
+ version: "24.6.0-beta4"
6
6
  });
7
7
  }
8
8
  import { signal } from "@vaadin/hilla-react-signals";
@@ -1,4 +1,13 @@
1
- import type { AgnosticRoute, Module } from '../types.js';
1
+ import type { AgnosticRoute, Module, ViewConfig } from '../types.js';
2
+ /**
3
+ * Extends a router module's config with additional properties. The original
4
+ * module config is preferred over the extension.
5
+ *
6
+ * @param module - The module to extend.
7
+ * @param config - The extension config.
8
+ * @returns
9
+ */
10
+ export declare function extendModule(module: Module | null, config?: ViewConfig): Module;
2
11
  /**
3
12
  * Create a single framework-agnostic route object. Later, it can be transformed into a framework-specific route object,
4
13
  * e.g., the one used by React Router.
@@ -1 +1 @@
1
- {"version":3,"file":"createRoute.d.ts","sourceRoot":"","sources":["../src/runtime/createRoute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEzD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,CAAC;AAC9F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,CAAC"}
1
+ {"version":3,"file":"createRoute.d.ts","sourceRoot":"","sources":["../src/runtime/createRoute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAErE;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,CAQ/E;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,CAAC;AAC9F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,CAAC"}
@@ -1,3 +1,12 @@
1
+ function extendModule(module, config) {
2
+ return {
3
+ ...module,
4
+ config: {
5
+ ...config,
6
+ ...module?.config
7
+ }
8
+ };
9
+ }
1
10
  function createRoute(path, moduleOrChildren, children) {
2
11
  let module;
3
12
  if (Array.isArray(moduleOrChildren)) {
@@ -12,6 +21,7 @@ function createRoute(path, moduleOrChildren, children) {
12
21
  };
13
22
  }
14
23
  export {
15
- createRoute
24
+ createRoute,
25
+ extendModule
16
26
  };
17
27
  //# sourceMappingURL=createRoute.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/runtime/createRoute.ts"],
4
- "sourcesContent": ["import type { AgnosticRoute, Module } from '../types.js';\n\n/**\n * Create a single framework-agnostic route object. Later, it can be transformed into a framework-specific route object,\n * e.g., the one used by React Router.\n *\n * @param path - A route path segment.\n * @param children - An array of child routes.\n *\n * @returns A framework-agnostic route object.\n */\nexport function createRoute(path: string, children?: readonly AgnosticRoute[]): AgnosticRoute;\nexport function createRoute(path: string, module: Module, children?: readonly AgnosticRoute[]): AgnosticRoute;\nexport function createRoute(\n path: string,\n moduleOrChildren?: Module | readonly AgnosticRoute[],\n children?: readonly AgnosticRoute[],\n): AgnosticRoute {\n let module: Module | undefined;\n if (Array.isArray(moduleOrChildren)) {\n // eslint-disable-next-line no-param-reassign\n children = moduleOrChildren;\n } else {\n module = moduleOrChildren as Module | undefined;\n }\n\n return {\n path,\n module,\n children,\n };\n}\n"],
5
- "mappings": "AAaO,SAAS,YACd,MACA,kBACA,UACe;AACf,MAAI;AACJ,MAAI,MAAM,QAAQ,gBAAgB,GAAG;AAEnC,eAAW;AAAA,EACb,OAAO;AACL,aAAS;AAAA,EACX;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import type { AgnosticRoute, Module, ViewConfig } from '../types.js';\n\n/**\n * Extends a router module's config with additional properties. The original\n * module config is preferred over the extension.\n *\n * @param module - The module to extend.\n * @param config - The extension config.\n * @returns\n */\nexport function extendModule(module: Module | null, config?: ViewConfig): Module {\n return {\n ...module,\n config: {\n ...config,\n ...(module?.config as ViewConfig),\n },\n };\n}\n\n/**\n * Create a single framework-agnostic route object. Later, it can be transformed into a framework-specific route object,\n * e.g., the one used by React Router.\n *\n * @param path - A route path segment.\n * @param children - An array of child routes.\n *\n * @returns A framework-agnostic route object.\n */\nexport function createRoute(path: string, children?: readonly AgnosticRoute[]): AgnosticRoute;\nexport function createRoute(path: string, module: Module, children?: readonly AgnosticRoute[]): AgnosticRoute;\nexport function createRoute(\n path: string,\n moduleOrChildren?: Module | readonly AgnosticRoute[],\n children?: readonly AgnosticRoute[],\n): AgnosticRoute {\n let module: Module | undefined;\n if (Array.isArray(moduleOrChildren)) {\n // eslint-disable-next-line no-param-reassign\n children = moduleOrChildren;\n } else {\n module = moduleOrChildren as Module | undefined;\n }\n\n return {\n path,\n module,\n children,\n };\n}\n"],
5
+ "mappings": "AAUO,SAAS,aAAa,QAAuB,QAA6B;AAC/E,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ;AAAA,MACN,GAAG;AAAA,MACH,GAAI,QAAQ;AAAA,IACd;AAAA,EACF;AACF;AAaO,SAAS,YACd,MACA,kBACA,UACe;AACf,MAAI;AACJ,MAAI,MAAM,QAAQ,gBAAgB,GAAG;AAEnC,eAAW;AAAA,EACb,OAAO;AACL,aAAS;AAAA,EACX;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,2 +1,2 @@
1
- export declare function transformTree<T extends readonly unknown[], U>(nodes: T, transformer: (nodes: T, next: (...nodes: T) => U) => U): U;
1
+ export declare function transformTree<T extends readonly unknown[], U, C extends object | null = null>(nodes: T, context: C, transformer: (nodes: T, next: (nodes: T, ctx?: C) => U, context: C) => U): U;
2
2
  //# sourceMappingURL=transformTree.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transformTree.d.ts","sourceRoot":"","sources":["../src/shared/transformTree.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,CAAC,SAAS,SAAS,OAAO,EAAE,EAAE,CAAC,EAC3D,KAAK,EAAE,CAAC,EACR,WAAW,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GACrD,CAAC,CAEH"}
1
+ {"version":3,"file":"transformTree.d.ts","sourceRoot":"","sources":["../src/shared/transformTree.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,CAAC,SAAS,SAAS,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,IAAI,GAAG,IAAI,EAC3F,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,EACV,WAAW,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,GACvE,CAAC,CAEH"}
@@ -1,5 +1,5 @@
1
- function transformTree(nodes, transformer) {
2
- return transformer(nodes, (...n) => transformTree(n, transformer));
1
+ function transformTree(nodes, context, transformer) {
2
+ return transformer(nodes, (n, ctx = context) => transformTree(n, ctx, transformer), context);
3
3
  }
4
4
  export {
5
5
  transformTree
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/shared/transformTree.ts"],
4
- "sourcesContent": ["export function transformTree<T extends readonly unknown[], U>(\n nodes: T,\n transformer: (nodes: T, next: (...nodes: T) => U) => U,\n): U {\n return transformer(nodes, (...n) => transformTree(n, transformer));\n}\n"],
5
- "mappings": "AAAO,SAAS,cACd,OACA,aACG;AACH,SAAO,YAAY,OAAO,IAAI,MAAM,cAAc,GAAG,WAAW,CAAC;AACnE;",
4
+ "sourcesContent": ["export function transformTree<T extends readonly unknown[], U, C extends object | null = null>(\n nodes: T,\n context: C,\n transformer: (nodes: T, next: (nodes: T, ctx?: C) => U, context: C) => U,\n): U {\n return transformer(nodes, (n, ctx = context) => transformTree(n, ctx, transformer), context);\n}\n"],
5
+ "mappings": "AAAO,SAAS,cACd,OACA,SACA,aACG;AACH,SAAO,YAAY,OAAO,CAAC,GAAG,MAAM,YAAY,cAAc,GAAG,KAAK,WAAW,GAAG,OAAO;AAC7F;",
6
6
  "names": []
7
7
  }
package/types.d.ts CHANGED
@@ -31,6 +31,11 @@ export type ViewConfig = Readonly<{
31
31
  */
32
32
  flowLayout?: boolean;
33
33
 
34
+ /**
35
+ * Set to true to make the view render without enclosing in any layouts.
36
+ */
37
+ skipLayouts?: boolean;
38
+
34
39
  menu?: Readonly<{
35
40
  /**
36
41
  * Title to use in the menu. Falls back the title property of the view
@@ -1 +1 @@
1
- {"version":3,"file":"applyLayouts.d.ts","sourceRoot":"","sources":["../src/vite-plugin/applyLayouts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CAAC;AA+CH;;;;;;GAMG;AACH,wBAA8B,YAAY,CACxC,SAAS,EAAE,SAAS,SAAS,EAAE,EAC/B,WAAW,EAAE,GAAG,GACf,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAW/B"}
1
+ {"version":3,"file":"applyLayouts.d.ts","sourceRoot":"","sources":["../src/vite-plugin/applyLayouts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CAAC;AAMH;;;;;;GAMG;AACH,wBAA8B,YAAY,CACxC,SAAS,EAAE,SAAS,SAAS,EAAE,EAC/B,WAAW,EAAE,GAAG,GACf,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CA0B/B"}
@@ -1,46 +1,29 @@
1
- import { existsSync } from "node:fs";
2
1
  import { readFile } from "node:fs/promises";
3
- function stripLeadingSlash(path) {
4
- return path.startsWith("/") ? path.slice(1) : path;
5
- }
6
- function enableFlowLayout(route) {
7
- const routeWithFlowLayout = {
8
- ...route,
9
- flowLayout: true
10
- };
11
- return route.children ? {
12
- ...routeWithFlowLayout,
13
- children: route.children.map(enableFlowLayout)
14
- } : routeWithFlowLayout;
15
- }
16
- function layoutExists(layoutPaths, path) {
17
- const splitPath = path.split("/");
18
- return layoutPaths.some((layout) => {
19
- if (layout.length === 0) {
20
- return true;
21
- }
22
- const splitLayout = layout.split("/");
23
- if (splitLayout.length > splitPath.length) {
24
- return false;
25
- }
26
- for (let i = 0; i < splitLayout.length; i++) {
27
- if (splitPath[i] !== splitLayout[i]) {
28
- return false;
29
- }
30
- }
31
- return true;
32
- });
2
+ import { join, relative } from "node:path/posix";
3
+ import { transformTree } from "../shared/transformTree.js";
4
+ function strip(path) {
5
+ return path.replace(/^\/*(.+)\/*$/u, "$1");
33
6
  }
34
7
  async function applyLayouts(routeMeta, layoutsFile) {
35
- if (!existsSync(layoutsFile)) {
36
- return routeMeta;
8
+ try {
9
+ const layoutContents = await readFile(layoutsFile, "utf-8");
10
+ const availableLayouts = JSON.parse(layoutContents);
11
+ const layoutPaths = availableLayouts.map((layout) => strip(layout.path));
12
+ return transformTree(
13
+ routeMeta,
14
+ { path: "" },
15
+ (metas, next, ctx) => metas.map((meta) => {
16
+ const currentPath = join(ctx.path, strip(meta.path));
17
+ const children = meta.children ? next(meta.children, { path: currentPath }) : void 0;
18
+ return layoutPaths.some((path) => !relative(path, currentPath).startsWith("..")) ? { ...meta, flowLayout: true, children } : { ...meta, children };
19
+ })
20
+ );
21
+ } catch (e) {
22
+ if (e instanceof Error && "code" in e && e.code === "ENOENT") {
23
+ return routeMeta;
24
+ }
25
+ throw e;
37
26
  }
38
- const layoutContents = await readFile(layoutsFile, "utf-8");
39
- const availableLayouts = JSON.parse(layoutContents);
40
- const layoutPaths = availableLayouts.map((layout) => stripLeadingSlash(layout.path));
41
- return routeMeta.map(
42
- (route) => layoutExists(layoutPaths, stripLeadingSlash(route.path)) ? enableFlowLayout(route) : route
43
- );
44
27
  }
45
28
  export {
46
29
  applyLayouts as default
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/vite-plugin/applyLayouts.ts"],
4
- "sourcesContent": ["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\n\n/**\n * The information about a particular server-side layout.\n */\nexport type LayoutMeta = Readonly<{\n path: string;\n}>;\n\nfunction stripLeadingSlash(path: string) {\n return path.startsWith('/') ? path.slice(1) : path;\n}\n\nfunction enableFlowLayout(route: RouteMeta): RouteMeta {\n const routeWithFlowLayout = {\n ...route,\n flowLayout: true,\n };\n return route.children\n ? {\n ...routeWithFlowLayout,\n children: route.children.map(enableFlowLayout),\n }\n : routeWithFlowLayout;\n}\n\n/**\n * Check if there is a layout available that can handle the given path.\n * Layouts match the starting parts of the path so '/' will match all paths\n * and '/home' matches '/home' anything with the start path '/home/*'\n *\n * @param layoutPaths - available layout paths\n * @param path - to check for layout\n */\nfunction layoutExists(layoutPaths: string[], path: string) {\n const splitPath: string[] = path.split('/');\n\n return layoutPaths.some((layout) => {\n if (layout.length === 0) {\n return true;\n }\n const splitLayout: string[] = layout.split('/');\n if (splitLayout.length > splitPath.length) {\n return false;\n }\n for (let i = 0; i < splitLayout.length; i++) {\n if (splitPath[i] !== splitLayout[i]) {\n return false;\n }\n }\n return true;\n });\n}\n\n/**\n * Enables Flow layout flag on the matching routes based on the information from the layouts JSON file.\n *\n * @param routeMeta - The routes tree to process.\n * @param layoutsFile - The server layouts JSON file.\n * @returns Processed routes tree.\n */\nexport default async function applyLayouts(\n routeMeta: readonly RouteMeta[],\n layoutsFile: URL,\n): Promise<readonly RouteMeta[]> {\n if (!existsSync(layoutsFile)) {\n return routeMeta;\n }\n const layoutContents = await readFile(layoutsFile, 'utf-8');\n const availableLayouts: readonly LayoutMeta[] = JSON.parse(layoutContents);\n const layoutPaths = availableLayouts.map((layout) => stripLeadingSlash(layout.path));\n\n return routeMeta.map((route) =>\n layoutExists(layoutPaths, stripLeadingSlash(route.path)) ? enableFlowLayout(route) : route,\n );\n}\n"],
5
- "mappings": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AAUzB,SAAS,kBAAkB,MAAc;AACvC,SAAO,KAAK,WAAW,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI;AAChD;AAEA,SAAS,iBAAiB,OAA6B;AACrD,QAAM,sBAAsB;AAAA,IAC1B,GAAG;AAAA,IACH,YAAY;AAAA,EACd;AACA,SAAO,MAAM,WACT;AAAA,IACE,GAAG;AAAA,IACH,UAAU,MAAM,SAAS,IAAI,gBAAgB;AAAA,EAC/C,IACA;AACN;AAUA,SAAS,aAAa,aAAuB,MAAc;AACzD,QAAM,YAAsB,KAAK,MAAM,GAAG;AAE1C,SAAO,YAAY,KAAK,CAAC,WAAW;AAClC,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO;AAAA,IACT;AACA,UAAM,cAAwB,OAAO,MAAM,GAAG;AAC9C,QAAI,YAAY,SAAS,UAAU,QAAQ;AACzC,aAAO;AAAA,IACT;AACA,aAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AAC3C,UAAI,UAAU,CAAC,MAAM,YAAY,CAAC,GAAG;AACnC,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AACH;AASA,eAAO,aACL,WACA,aAC+B;AAC/B,MAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,WAAO;AAAA,EACT;AACA,QAAM,iBAAiB,MAAM,SAAS,aAAa,OAAO;AAC1D,QAAM,mBAA0C,KAAK,MAAM,cAAc;AACzE,QAAM,cAAc,iBAAiB,IAAI,CAAC,WAAW,kBAAkB,OAAO,IAAI,CAAC;AAEnF,SAAO,UAAU;AAAA,IAAI,CAAC,UACpB,aAAa,aAAa,kBAAkB,MAAM,IAAI,CAAC,IAAI,iBAAiB,KAAK,IAAI;AAAA,EACvF;AACF;",
4
+ "sourcesContent": ["import { readFile } from 'node:fs/promises';\nimport { join, relative } from 'node:path/posix';\nimport { transformTree } from '../shared/transformTree.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\n\n/**\n * The information about a particular server-side layout.\n */\nexport type LayoutMeta = Readonly<{\n path: string;\n}>;\n\nfunction strip(path: string) {\n return path.replace(/^\\/*(.+)\\/*$/u, '$1');\n}\n\n/**\n * Enables Flow layout flag on the matching routes based on the information from the layouts JSON file.\n *\n * @param routeMeta - The routes tree to process.\n * @param layoutsFile - The server layouts JSON file.\n * @returns Processed routes tree.\n */\nexport default async function applyLayouts(\n routeMeta: readonly RouteMeta[],\n layoutsFile: URL,\n): Promise<readonly RouteMeta[]> {\n try {\n const layoutContents = await readFile(layoutsFile, 'utf-8');\n const availableLayouts: readonly LayoutMeta[] = JSON.parse(layoutContents);\n const layoutPaths = availableLayouts.map((layout) => strip(layout.path));\n\n return transformTree<readonly RouteMeta[], readonly RouteMeta[], { path: string }>(\n routeMeta,\n { path: '' },\n (metas, next, ctx) =>\n metas.map((meta) => {\n const currentPath = join(ctx.path, strip(meta.path));\n const children = meta.children ? next(meta.children, { path: currentPath }) : undefined;\n\n return layoutPaths.some((path) => !relative(path, currentPath).startsWith('..'))\n ? { ...meta, flowLayout: true, children }\n : { ...meta, children };\n }),\n );\n } catch (e: unknown) {\n if (e instanceof Error && 'code' in e && e.code === 'ENOENT') {\n return routeMeta;\n }\n\n throw e;\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,MAAM,gBAAgB;AAC/B,SAAS,qBAAqB;AAU9B,SAAS,MAAM,MAAc;AAC3B,SAAO,KAAK,QAAQ,iBAAiB,IAAI;AAC3C;AASA,eAAO,aACL,WACA,aAC+B;AAC/B,MAAI;AACF,UAAM,iBAAiB,MAAM,SAAS,aAAa,OAAO;AAC1D,UAAM,mBAA0C,KAAK,MAAM,cAAc;AACzE,UAAM,cAAc,iBAAiB,IAAI,CAAC,WAAW,MAAM,OAAO,IAAI,CAAC;AAEvE,WAAO;AAAA,MACL;AAAA,MACA,EAAE,MAAM,GAAG;AAAA,MACX,CAAC,OAAO,MAAM,QACZ,MAAM,IAAI,CAAC,SAAS;AAClB,cAAM,cAAc,KAAK,IAAI,MAAM,MAAM,KAAK,IAAI,CAAC;AACnD,cAAM,WAAW,KAAK,WAAW,KAAK,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC,IAAI;AAE9E,eAAO,YAAY,KAAK,CAAC,SAAS,CAAC,SAAS,MAAM,WAAW,EAAE,WAAW,IAAI,CAAC,IAC3E,EAAE,GAAG,MAAM,YAAY,MAAM,SAAS,IACtC,EAAE,GAAG,MAAM,SAAS;AAAA,MAC1B,CAAC;AAAA,IACL;AAAA,EACF,SAAS,GAAY;AACnB,QAAI,aAAa,SAAS,UAAU,KAAK,EAAE,SAAS,UAAU;AAC5D,aAAO;AAAA,IACT;AAEA,UAAM;AAAA,EACR;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,10 +1,4 @@
1
1
  import type { RouteMeta } from './collectRoutesFromFS.js';
2
2
  import type { RuntimeFileUrls } from './generateRuntimeFiles.js';
3
- /**
4
- * Loads all the files from the received metadata and creates a framework-agnostic route tree.
5
- *
6
- * @param views - The abstract route tree.
7
- * @param generatedDir - The directory where the generated view file will be stored.
8
- */
9
- export default function createRoutesFromMeta(views: readonly RouteMeta[], { code: codeFile }: RuntimeFileUrls): string;
3
+ export default function createRoutesFromMeta(views: readonly RouteMeta[], urls: RuntimeFileUrls): string;
10
4
  //# sourceMappingURL=createRoutesFromMeta.d.ts.map
@@ -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;AAqDjE;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,eAAe,GAAG,MAAM,CA0ErH"}
1
+ {"version":3,"file":"createRoutesFromMeta.d.ts","sourceRoot":"","sources":["../src/vite-plugin/createRoutesFromMeta.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAuIjE,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,CAEvG"}
@@ -1,95 +1,107 @@
1
- import { relative, sep } from "node:path";
2
- import { fileURLToPath } from "node:url";
3
1
  import { template, transform as transformer } from "@vaadin/hilla-generator-utils/ast.js";
4
2
  import createSourceFile from "@vaadin/hilla-generator-utils/createSourceFile.js";
5
- import ts, {
6
- } from "typescript";
3
+ import DependencyManager from "@vaadin/hilla-generator-utils/dependencies/DependencyManager.js";
4
+ import PathManager from "@vaadin/hilla-generator-utils/dependencies/PathManager.js";
5
+ import ts, {} from "typescript";
7
6
  import { transformTree } from "../shared/transformTree.js";
8
7
  import { convertFSRouteSegmentToURLPatternFormat } from "./utils.js";
9
8
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
10
- function relativize(url, generatedDir) {
11
- const result = relative(fileURLToPath(generatedDir), fileURLToPath(url)).replaceAll(sep, "/");
12
- if (!result.startsWith(".")) {
13
- return `./${result}`;
9
+ const fileExtensions = [".ts", ".tsx", ".js", ".jsx"];
10
+ class RouteFromMetaProcessor {
11
+ #manager;
12
+ #views;
13
+ constructor(views, { json: jsonFile, code: codeFile }) {
14
+ this.#views = views;
15
+ const codeDir = new URL("./", codeFile);
16
+ this.#manager = new DependencyManager(new PathManager({ extension: ".js", relativeTo: codeDir }));
14
17
  }
15
- return result;
16
- }
17
- function createImport(mod, file) {
18
- const path = `${file.substring(0, file.lastIndexOf("."))}.js`;
19
- return template(`import * as ${mod} from '${path}';
20
- `, ([statement]) => statement);
21
- }
22
- function createRouteData(path, mod, children) {
23
- return template(
24
- `const route = createRoute("${path}", ${mod ? `, ${mod}` : ""}${children ? `, CHILDREN` : ""})`,
25
- ([statement]) => statement.declarationList.declarations[0].initializer,
26
- [
27
- transformer(
28
- (node) => ts.isIdentifier(node) && node.text === "CHILDREN" ? ts.factory.createArrayLiteralExpression(children, true) : node
29
- )
30
- ]
31
- );
32
- }
33
- function createRoutesFromMeta(views, { code: codeFile }) {
34
- const codeDir = new URL("./", codeFile);
35
- const imports = [];
36
- const errors = [];
37
- let id = 0;
38
- const routes = transformTree(views, (metas, next) => {
39
- errors.push(
40
- ...metas.map((route) => route.path).filter((item, index, arr) => arr.indexOf(item) !== index).map((dup) => `console.error("Two views share the same path: ${dup}");`)
41
- );
42
- return metas.map(({ file: file2, layout, path, children, flowLayout }) => {
43
- let _children;
44
- if (children) {
45
- _children = next(...children);
46
- }
47
- const currentId = id;
48
- id += 1;
49
- let mod;
50
- if (file2) {
51
- mod = `Page${currentId}`;
52
- imports.push(createImport(mod, relativize(file2, codeDir)));
53
- } else if (layout) {
54
- mod = `Layout${currentId}`;
55
- imports.push(createImport(mod, relativize(layout, codeDir)));
56
- }
57
- return createRouteData(convertFSRouteSegmentToURLPatternFormat(path), mod, _children);
18
+ /**
19
+ * Loads all the files from the received metadata and creates a framework-agnostic route tree.
20
+ *
21
+ * @param views - The abstract route tree.
22
+ * @param generatedDir - The directory where the generated view file will be stored.
23
+ */
24
+ process() {
25
+ const {
26
+ paths,
27
+ imports: { named, namespace }
28
+ } = this.#manager;
29
+ const errors = [];
30
+ const routes = transformTree(this.#views, null, (metas, next) => {
31
+ errors.push(
32
+ ...metas.map((route) => route.path).filter((item, index, arr) => arr.indexOf(item) !== index).map((dup) => `console.error("Two views share the same path: ${dup}");`)
33
+ );
34
+ return metas.map(({ file: file2, layout, path, children, flowLayout }) => {
35
+ let _children;
36
+ if (children) {
37
+ _children = next(children);
38
+ }
39
+ let mod;
40
+ if (file2) {
41
+ const fileExt = fileExtensions.find((ext) => file2.pathname.endsWith(ext));
42
+ mod = namespace.add(paths.createRelativePath(file2, fileExt), `Page`);
43
+ } else if (layout) {
44
+ const fileExt = fileExtensions.find((ext) => layout.pathname.endsWith(ext));
45
+ mod = namespace.add(paths.createRelativePath(layout, fileExt), `Layout`);
46
+ }
47
+ const moduleExtension = flowLayout ? { flowLayout } : void 0;
48
+ return this.#createRouteData(convertFSRouteSegmentToURLPatternFormat(path), mod, moduleExtension, _children);
49
+ });
58
50
  });
59
- });
60
- if (imports.length > 0) {
61
- const createRouteImport = template(
62
- 'import { createRoute } from "@vaadin/hilla-file-router/runtime.js";',
63
- ([statement]) => statement
64
- );
65
- imports.unshift(createRouteImport);
66
- }
67
- imports.unshift(
68
- template(
69
- 'import type { AgnosticRoute } from "@vaadin/hilla-file-router/types.js";',
70
- ([statement]) => statement
71
- )
72
- );
73
- const routeDeclaration = template(
74
- `import a from 'IMPORTS';
51
+ const agnosticRouteId = named.getIdentifier("@vaadin/hilla-file-router/types.js", "AgnosticRoute") ?? named.add("@vaadin/hilla-file-router/types.js", "AgnosticRoute", true);
52
+ let routeDeclaration = template(
53
+ `${errors.join("\n")}
75
54
 
76
- ${errors.join("\n")}
77
-
78
- const routes: readonly AgnosticRoute[] = ROUTE;
55
+ const routes: readonly AGNOSTIC_ROUTE[] = ROUTE;
79
56
 
80
57
  export default routes;
81
58
  `,
82
- [
83
- transformer(
84
- (node) => ts.isImportDeclaration(node) && node.moduleSpecifier.text === "IMPORTS" ? imports : node
85
- ),
86
- transformer(
87
- (node) => ts.isIdentifier(node) && node.text === "ROUTE" ? ts.factory.createArrayLiteralExpression(routes, true) : node
88
- )
89
- ]
90
- );
91
- const file = createSourceFile(routeDeclaration, "file-routes.ts");
92
- return printer.printFile(file);
59
+ [
60
+ transformer(
61
+ (node) => ts.isIdentifier(node) && node.text === "ROUTE" ? ts.factory.createArrayLiteralExpression(routes, true) : node
62
+ ),
63
+ transformer((node) => ts.isIdentifier(node) && node.text === "AGNOSTIC_ROUTE" ? agnosticRouteId : node)
64
+ ]
65
+ );
66
+ routeDeclaration = [...this.#manager.imports.toCode(), ...routeDeclaration];
67
+ const file = createSourceFile(routeDeclaration, "file-routes.ts");
68
+ return printer.printFile(file);
69
+ }
70
+ /**
71
+ * Create an abstract route creation function call. The nested function calls
72
+ * create a route tree.
73
+ *
74
+ * @param path - The path of the route.
75
+ * @param mod - The name of the route module imported as a namespace.
76
+ * @param children - The list of child route call expressions.
77
+ */
78
+ #createRouteData(path, mod, extension, children) {
79
+ const { named } = this.#manager.imports;
80
+ let extendModuleId;
81
+ let modNode = "";
82
+ if (extension) {
83
+ extendModuleId = named.getIdentifier("@vaadin/hilla-file-router/runtime.js", "extendModule") ?? named.add("@vaadin/hilla-file-router/runtime.js", "extendModule");
84
+ modNode = `, EXTEND_MODULE(MOD, ${JSON.stringify(extension)})`;
85
+ } else if (mod) {
86
+ modNode = `, MOD`;
87
+ }
88
+ const createRouteId = named.getIdentifier("@vaadin/hilla-file-router/runtime.js", "createRoute") ?? named.add("@vaadin/hilla-file-router/runtime.js", "createRoute");
89
+ return template(
90
+ `const route = CREATE_ROUTE("${path}", ${modNode}${children ? `, CHILDREN` : ""})`,
91
+ ([statement]) => statement.declarationList.declarations[0].initializer,
92
+ [
93
+ transformer(
94
+ (node) => ts.isIdentifier(node) && node.text === "CHILDREN" ? ts.factory.createArrayLiteralExpression(children, true) : node
95
+ ),
96
+ transformer((node) => ts.isIdentifier(node) && node.text === "MOD" ? mod ?? ts.factory.createNull() : node),
97
+ transformer((node) => ts.isIdentifier(node) && node.text === "EXTEND_MODULE" ? extendModuleId : node),
98
+ transformer((node) => ts.isIdentifier(node) && node.text === "CREATE_ROUTE" ? createRouteId : node)
99
+ ]
100
+ );
101
+ }
102
+ }
103
+ function createRoutesFromMeta(views, urls) {
104
+ return new RouteFromMetaProcessor(views, urls).process();
93
105
  }
94
106
  export {
95
107
  createRoutesFromMeta as default
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/vite-plugin/createRoutesFromMeta.ts"],
4
- "sourcesContent": ["import { relative, sep } 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 { transformTree } 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` : ''})`,\n ([statement]) => (statement as VariableStatement).declarationList.declarations[0].initializer as CallExpression,\n [\n transformer((node) =>\n ts.isIdentifier(node) && node.text === 'CHILDREN'\n ? ts.factory.createArrayLiteralExpression(children, true)\n : 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: readonly RouteMeta[], { code: codeFile }: RuntimeFileUrls): string {\n const codeDir = new URL('./', codeFile);\n const imports: ImportDeclaration[] = [];\n const errors: string[] = [];\n let id = 0;\n\n const routes = transformTree<readonly RouteMeta[], readonly CallExpression[]>(views, (metas, next) => {\n errors.push(\n ...metas\n .map((route) => route.path)\n .filter((item, index, arr) => arr.indexOf(item) !== index)\n .map((dup) => `console.error(\"Two views share the same path: ${dup}\");`),\n );\n\n return metas.map(({ file, layout, path, children, flowLayout }) => {\n let _children: readonly CallExpression[] | undefined;\n\n if (children) {\n _children = next(...children);\n }\n\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 // Prepend the import for `createRoute` if it was used\n if (imports.length > 0) {\n const createRouteImport = template(\n 'import { createRoute } from \"@vaadin/hilla-file-router/runtime.js\";',\n ([statement]) => statement as ts.ImportDeclaration,\n );\n imports.unshift(createRouteImport);\n }\n\n imports.unshift(\n template(\n 'import type { AgnosticRoute } from \"@vaadin/hilla-file-router/types.js\";',\n ([statement]) => statement as ts.ImportDeclaration,\n ),\n );\n\n const routeDeclaration = template(\n `import a from 'IMPORTS';\n\n${errors.join('\\n')}\n\nconst routes: readonly AgnosticRoute[] = 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) =>\n ts.isIdentifier(node) && node.text === 'ROUTE' ? ts.factory.createArrayLiteralExpression(routes, true) : node,\n ),\n ],\n );\n\n const file = createSourceFile(routeDeclaration, 'file-routes.ts');\n return printer.printFile(file);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { template, transform as transformer } from '@vaadin/hilla-generator-utils/ast.js';\nimport createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';\nimport DependencyManager from '@vaadin/hilla-generator-utils/dependencies/DependencyManager.js';\nimport PathManager from '@vaadin/hilla-generator-utils/dependencies/PathManager.js';\nimport ts, { type CallExpression, type Identifier, type VariableStatement } from 'typescript';\n\nimport { transformTree } 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\nconst fileExtensions = ['.ts', '.tsx', '.js', '.jsx'];\n\nclass RouteFromMetaProcessor {\n readonly #manager: DependencyManager;\n readonly #views: readonly RouteMeta[];\n\n constructor(views: readonly RouteMeta[], { json: jsonFile, code: codeFile }: RuntimeFileUrls) {\n this.#views = views;\n\n const codeDir = new URL('./', codeFile);\n this.#manager = new DependencyManager(new PathManager({ extension: '.js', relativeTo: codeDir }));\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 */\n process(): string {\n const {\n paths,\n imports: { named, namespace },\n } = this.#manager;\n const errors: string[] = [];\n\n const routes = transformTree<readonly RouteMeta[], readonly CallExpression[]>(this.#views, null, (metas, next) => {\n errors.push(\n ...metas\n .map((route) => route.path)\n .filter((item, index, arr) => arr.indexOf(item) !== index)\n .map((dup) => `console.error(\"Two views share the same path: ${dup}\");`),\n );\n\n return metas.map(({ file, layout, path, children, flowLayout }) => {\n let _children: readonly CallExpression[] | undefined;\n\n if (children) {\n _children = next(children);\n }\n\n let mod: Identifier | undefined;\n if (file) {\n const fileExt = fileExtensions.find((ext) => file.pathname.endsWith(ext));\n mod = namespace.add(paths.createRelativePath(file, fileExt), `Page`);\n } else if (layout) {\n const fileExt = fileExtensions.find((ext) => layout.pathname.endsWith(ext));\n mod = namespace.add(paths.createRelativePath(layout, fileExt), `Layout`);\n }\n\n const moduleExtension = flowLayout ? { flowLayout } : undefined;\n\n return this.#createRouteData(convertFSRouteSegmentToURLPatternFormat(path), mod, moduleExtension, _children);\n });\n });\n\n const agnosticRouteId =\n named.getIdentifier('@vaadin/hilla-file-router/types.js', 'AgnosticRoute') ??\n named.add('@vaadin/hilla-file-router/types.js', 'AgnosticRoute', true);\n\n let routeDeclaration = template(\n `${errors.join('\\n')}\n\nconst routes: readonly AGNOSTIC_ROUTE[] = ROUTE;\n\nexport default routes;\n`,\n [\n transformer((node) =>\n ts.isIdentifier(node) && node.text === 'ROUTE' ? ts.factory.createArrayLiteralExpression(routes, true) : node,\n ),\n transformer((node) => (ts.isIdentifier(node) && node.text === 'AGNOSTIC_ROUTE' ? agnosticRouteId : node)),\n ],\n );\n\n routeDeclaration = [...this.#manager.imports.toCode(), ...routeDeclaration];\n\n const file = createSourceFile(routeDeclaration, 'file-routes.ts');\n return printer.printFile(file);\n }\n\n /**\n * Create an abstract route creation function call. The nested function calls\n * 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 */\n #createRouteData(\n path: string,\n mod: Identifier | undefined,\n extension?: Readonly<Record<string, unknown>>,\n children?: readonly CallExpression[],\n ): CallExpression {\n const { named } = this.#manager.imports;\n\n let extendModuleId: Identifier | undefined;\n let modNode = '';\n\n if (extension) {\n extendModuleId =\n named.getIdentifier('@vaadin/hilla-file-router/runtime.js', 'extendModule') ??\n named.add('@vaadin/hilla-file-router/runtime.js', 'extendModule');\n modNode = `, EXTEND_MODULE(MOD, ${JSON.stringify(extension)})`;\n } else if (mod) {\n modNode = `, MOD`;\n }\n\n const createRouteId =\n named.getIdentifier('@vaadin/hilla-file-router/runtime.js', 'createRoute') ??\n named.add('@vaadin/hilla-file-router/runtime.js', 'createRoute');\n\n return template(\n `const route = CREATE_ROUTE(\"${path}\", ${modNode}${children ? `, CHILDREN` : ''})`,\n ([statement]) => (statement as VariableStatement).declarationList.declarations[0].initializer as CallExpression,\n [\n transformer((node) =>\n ts.isIdentifier(node) && node.text === 'CHILDREN'\n ? ts.factory.createArrayLiteralExpression(children, true)\n : node,\n ),\n transformer((node) => (ts.isIdentifier(node) && node.text === 'MOD' ? (mod ?? ts.factory.createNull()) : node)),\n transformer((node) => (ts.isIdentifier(node) && node.text === 'EXTEND_MODULE' ? extendModuleId : node)),\n transformer((node) => (ts.isIdentifier(node) && node.text === 'CREATE_ROUTE' ? createRouteId : node)),\n ],\n );\n }\n}\n\nexport default function createRoutesFromMeta(views: readonly RouteMeta[], urls: RuntimeFileUrls): string {\n return new RouteFromMetaProcessor(views, urls).process();\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["file"]
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"createViewConfigJson.d.ts","sourceRoot":"","sources":["../src/vite-plugin/createViewConfigJson.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAgB1D;;;;;GAKG;AACH,wBAA8B,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA6E/F"}
1
+ {"version":3,"file":"createViewConfigJson.d.ts","sourceRoot":"","sources":["../src/vite-plugin/createViewConfigJson.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAgB1D;;;;;GAKG;AACH,wBAA8B,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA8E/F"}
@@ -13,9 +13,10 @@ function* walkAST(node) {
13
13
  async function createViewConfigJson(views) {
14
14
  const res = await transformTree(
15
15
  views,
16
+ null,
16
17
  async (routes, next) => await Promise.all(
17
18
  routes.map(async ({ path, file, layout, children, flowLayout }) => {
18
- const newChildren = children ? await next(...children) : void 0;
19
+ const newChildren = children ? await next(children) : void 0;
19
20
  if (!file && !layout) {
20
21
  return {
21
22
  route: convertFSRouteSegmentToURLPatternFormat(path),
@@ -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: readonly RouteMeta[]): Promise<string> {\n const res = await transformTree<readonly RouteMeta[], Promise<readonly ServerViewConfig[]>>(\n views,\n async (routes, next) =>\n await Promise.all(\n routes.map(async ({ path, file, layout, children, flowLayout }) => {\n const newChildren = children ? await next(...children) : undefined;\n\n if (!file && !layout) {\n return {\n route: convertFSRouteSegmentToURLPatternFormat(path),\n params: extractParameterFromRouteSegment(path),\n children: newChildren,\n } satisfies ServerViewConfig;\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 if (config.flowLayout === undefined) {\n const copy = JSON.parse(JSON.stringify(config));\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n copy.flowLayout = flowLayout ?? false;\n config = copy;\n }\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 waitingForIdentifier = false;\n }\n }\n\n if (config === undefined) {\n config = { flowLayout: flowLayout ?? false };\n }\n\n let title: string;\n\n if (config.title) {\n ({ title } = config);\n } else {\n if (!componentName) {\n throw new Error(\n `The file \"${String(file ?? layout!)}\" must contain a default export of a component whose name will be used as title by default`,\n );\n }\n\n title = convertComponentNameToTitle(componentName);\n }\n\n return {\n route: convertFSRouteSegmentToURLPatternFormat(path),\n ...config,\n params: extractParameterFromRouteSegment(config.route ?? path),\n title,\n children: newChildren ?? (layout ? [] : undefined),\n } satisfies ServerViewConfig;\n }),\n ),\n );\n\n return JSON.stringify(res, undefined, 2);\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,OAA8C;AAC/F,QAAM,MAAM,MAAM;AAAA,IAChB;AAAA,IACA,OAAO,QAAQ,SACb,MAAM,QAAQ;AAAA,MACZ,OAAO,IAAI,OAAO,EAAE,MAAM,MAAM,QAAQ,UAAU,WAAW,MAAM;AACjE,cAAM,cAAc,WAAW,MAAM,KAAK,GAAG,QAAQ,IAAI;AAEzD,YAAI,CAAC,QAAQ,CAAC,QAAQ;AACpB,iBAAO;AAAA,YACL,OAAO,wCAAwC,IAAI;AAAA,YACnD,QAAQ,iCAAiC,IAAI;AAAA,YAC7C,UAAU;AAAA,UACZ;AAAA,QACF;AAEA,cAAM,aAAa,GAAG;AAAA,UACpB;AAAA,UACA,MAAM,SAAS,QAAQ,QAAS,MAAM;AAAA,UACtC,GAAG,aAAa;AAAA,UAChB;AAAA,QACF;AACA,YAAI;AACJ,YAAI,uBAAuB;AAC3B,YAAI;AAEJ,mBAAW,QAAQ,QAAQ,UAAU,GAAG;AACtC,cAAI,GAAG,sBAAsB,IAAI,KAAK,GAAG,aAAa,KAAK,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AAC/F,gBAAI,KAAK,eAAe,GAAG,0BAA0B,KAAK,WAAW,GAAG;AACtE,oBAAM,OAAO,KAAK,YAAY,QAAQ,UAAU;AAChD,oBAAM,SAAS,IAAI,OAAO,IAAI,IAAI,GAAG;AACrC,uBAAS,OAAO,iBAAiB;AACjC,kBAAI,OAAO,eAAe,QAAW;AACnC,sBAAM,OAAO,KAAK,MAAM,KAAK,UAAU,MAAM,CAAC;AAE9C,qBAAK,aAAa,cAAc;AAChC,yBAAS;AAAA,cACX;AAAA,YACF;AAAA,UACF,WAAW,KAAK,QAAQ,UAAU,EAAE,WAAW,gBAAgB,GAAG;AAChE,mCAAuB;AAAA,UACzB,WAAW,wBAAwB,GAAG,aAAa,IAAI,GAAG;AACxD,4BAAgB,KAAK;AACrB,mCAAuB;AAAA,UACzB;AAAA,QACF;AAEA,YAAI,WAAW,QAAW;AACxB,mBAAS,EAAE,YAAY,cAAc,MAAM;AAAA,QAC7C;AAEA,YAAI;AAEJ,YAAI,OAAO,OAAO;AAChB,WAAC,EAAE,MAAM,IAAI;AAAA,QACf,OAAO;AACL,cAAI,CAAC,eAAe;AAClB,kBAAM,IAAI;AAAA,cACR,aAAa,OAAO,QAAQ,MAAO,CAAC;AAAA,YACtC;AAAA,UACF;AAEA,kBAAQ,4BAA4B,aAAa;AAAA,QACnD;AAEA,eAAO;AAAA,UACL,OAAO,wCAAwC,IAAI;AAAA,UACnD,GAAG;AAAA,UACH,QAAQ,iCAAiC,OAAO,SAAS,IAAI;AAAA,UAC7D;AAAA,UACA,UAAU,gBAAgB,SAAS,CAAC,IAAI;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACJ;AAEA,SAAO,KAAK,UAAU,KAAK,QAAW,CAAC;AACzC;",
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: readonly RouteMeta[]): Promise<string> {\n const res = await transformTree<readonly RouteMeta[], Promise<readonly ServerViewConfig[]>>(\n views,\n null,\n async (routes, next) =>\n await Promise.all(\n routes.map(async ({ path, file, layout, children, flowLayout }) => {\n const newChildren = children ? await next(children) : undefined;\n\n if (!file && !layout) {\n return {\n route: convertFSRouteSegmentToURLPatternFormat(path),\n params: extractParameterFromRouteSegment(path),\n children: newChildren,\n } satisfies ServerViewConfig;\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 if (config.flowLayout === undefined) {\n const copy = JSON.parse(JSON.stringify(config));\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n copy.flowLayout = flowLayout ?? false;\n config = copy;\n }\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 waitingForIdentifier = false;\n }\n }\n\n if (config === undefined) {\n config = { flowLayout: flowLayout ?? false };\n }\n\n let title: string;\n\n if (config.title) {\n ({ title } = config);\n } else {\n if (!componentName) {\n throw new Error(\n `The file \"${String(file ?? layout!)}\" must contain a default export of a component whose name will be used as title by default`,\n );\n }\n\n title = convertComponentNameToTitle(componentName);\n }\n\n return {\n route: convertFSRouteSegmentToURLPatternFormat(path),\n ...config,\n params: extractParameterFromRouteSegment(config.route ?? path),\n title,\n children: newChildren ?? (layout ? [] : undefined),\n } satisfies ServerViewConfig;\n }),\n ),\n );\n\n return JSON.stringify(res, undefined, 2);\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,OAA8C;AAC/F,QAAM,MAAM,MAAM;AAAA,IAChB;AAAA,IACA;AAAA,IACA,OAAO,QAAQ,SACb,MAAM,QAAQ;AAAA,MACZ,OAAO,IAAI,OAAO,EAAE,MAAM,MAAM,QAAQ,UAAU,WAAW,MAAM;AACjE,cAAM,cAAc,WAAW,MAAM,KAAK,QAAQ,IAAI;AAEtD,YAAI,CAAC,QAAQ,CAAC,QAAQ;AACpB,iBAAO;AAAA,YACL,OAAO,wCAAwC,IAAI;AAAA,YACnD,QAAQ,iCAAiC,IAAI;AAAA,YAC7C,UAAU;AAAA,UACZ;AAAA,QACF;AAEA,cAAM,aAAa,GAAG;AAAA,UACpB;AAAA,UACA,MAAM,SAAS,QAAQ,QAAS,MAAM;AAAA,UACtC,GAAG,aAAa;AAAA,UAChB;AAAA,QACF;AACA,YAAI;AACJ,YAAI,uBAAuB;AAC3B,YAAI;AAEJ,mBAAW,QAAQ,QAAQ,UAAU,GAAG;AACtC,cAAI,GAAG,sBAAsB,IAAI,KAAK,GAAG,aAAa,KAAK,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AAC/F,gBAAI,KAAK,eAAe,GAAG,0BAA0B,KAAK,WAAW,GAAG;AACtE,oBAAM,OAAO,KAAK,YAAY,QAAQ,UAAU;AAChD,oBAAM,SAAS,IAAI,OAAO,IAAI,IAAI,GAAG;AACrC,uBAAS,OAAO,iBAAiB;AACjC,kBAAI,OAAO,eAAe,QAAW;AACnC,sBAAM,OAAO,KAAK,MAAM,KAAK,UAAU,MAAM,CAAC;AAE9C,qBAAK,aAAa,cAAc;AAChC,yBAAS;AAAA,cACX;AAAA,YACF;AAAA,UACF,WAAW,KAAK,QAAQ,UAAU,EAAE,WAAW,gBAAgB,GAAG;AAChE,mCAAuB;AAAA,UACzB,WAAW,wBAAwB,GAAG,aAAa,IAAI,GAAG;AACxD,4BAAgB,KAAK;AACrB,mCAAuB;AAAA,UACzB;AAAA,QACF;AAEA,YAAI,WAAW,QAAW;AACxB,mBAAS,EAAE,YAAY,cAAc,MAAM;AAAA,QAC7C;AAEA,YAAI;AAEJ,YAAI,OAAO,OAAO;AAChB,WAAC,EAAE,MAAM,IAAI;AAAA,QACf,OAAO;AACL,cAAI,CAAC,eAAe;AAClB,kBAAM,IAAI;AAAA,cACR,aAAa,OAAO,QAAQ,MAAO,CAAC;AAAA,YACtC;AAAA,UACF;AAEA,kBAAQ,4BAA4B,aAAa;AAAA,QACnD;AAEA,eAAO;AAAA,UACL,OAAO,wCAAwC,IAAI;AAAA,UACnD,GAAG;AAAA,UACH,QAAQ,iCAAiC,OAAO,SAAS,IAAI;AAAA,UAC7D;AAAA,UACA,UAAU,gBAAgB,SAAS,CAAC,IAAI;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACJ;AAEA,SAAO,KAAK,UAAU,KAAK,QAAW,CAAC;AACzC;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generateRuntimeFiles.d.ts","sourceRoot":"","sources":["../src/vite-plugin/generateRuntimeFiles.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAMnC;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC;;;OAGG;IACH,IAAI,EAAE,GAAG,CAAC;IACV;;OAEG;IACH,IAAI,EAAE,GAAG,CAAC;IACV;;OAEG;IACH,OAAO,EAAE,GAAG,CAAC;CACd,CAAC,CAAC;AAiCH;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,GAAG,EACb,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CAoBf"}
1
+ {"version":3,"file":"generateRuntimeFiles.d.ts","sourceRoot":"","sources":["../src/vite-plugin/generateRuntimeFiles.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAMnC;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC;;;OAGG;IACH,IAAI,EAAE,GAAG,CAAC;IACV;;OAEG;IACH,IAAI,EAAE,GAAG,CAAC;IACV;;OAEG;IACH,OAAO,EAAE,GAAG,CAAC;CACd,CAAC,CAAC;AAiCH;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,GAAG,EACb,IAAI,EAAE,eAAe,EACrB,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CA8Bf"}
@@ -1,7 +1,6 @@
1
- import { existsSync } from "node:fs";
2
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
2
  import applyLayouts from "./applyLayouts.js";
4
- import collectRoutesFromFS from "./collectRoutesFromFS.js";
3
+ import collectRoutesFromFS, {} from "./collectRoutesFromFS.js";
5
4
  import createRoutesFromMeta from "./createRoutesFromMeta.js";
6
5
  import createViewConfigJson from "./createViewConfigJson.js";
7
6
  async function generateRuntimeFile(url, data, forceWrite) {
@@ -24,7 +23,16 @@ async function generateRuntimeFile(url, data, forceWrite) {
24
23
  return shouldWrite;
25
24
  }
26
25
  async function generateRuntimeFiles(viewsDir, urls, extensions, logger, debug) {
27
- let routeMeta = existsSync(viewsDir) ? await collectRoutesFromFS(viewsDir, { extensions, logger }) : [];
26
+ let routeMeta;
27
+ try {
28
+ routeMeta = await collectRoutesFromFS(viewsDir, { extensions, logger });
29
+ } catch (e) {
30
+ if (e instanceof Error && "code" in e && e.code === "ENOENT") {
31
+ routeMeta = [];
32
+ } else {
33
+ throw e;
34
+ }
35
+ }
28
36
  if (debug) {
29
37
  logger.info("Collected file-based routes");
30
38
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/vite-plugin/generateRuntimeFiles.ts"],
4
- "sourcesContent": ["import { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport type { Logger } from 'vite';\nimport applyLayouts from './applyLayouts.js';\nimport collectRoutesFromFS from './collectRoutesFromFS.js';\nimport createRoutesFromMeta from './createRoutesFromMeta.js';\nimport createViewConfigJson from './createViewConfigJson.js';\n\n/**\n * The URLs of the files to generate.\n */\nexport type RuntimeFileUrls = Readonly<{\n /**\n * The URL of the JSON file with the leaf routes and their metadata. This file\n * will be processed by the server to provide the final route configuration.\n */\n json: URL;\n /**\n * The URL of the module with the routes tree in a framework-agnostic format.\n */\n code: URL;\n /**\n * The URL of the JSON file containing server layout path information.\n */\n layouts: URL;\n}>;\n\n/**\n * Generates a file conditionally. If the file already exists and its content is the same as the\n * given data, the file will not be overwritten. It is useful to avoid unnecessary server\n * reboot during development.\n *\n * @param url - The URL of the file to generate.\n * @param data - The data to write to the file.\n * @param forceWrite - true to force writing the file even if there are no changes\n * @returns true if the file was written, false otherwise.\n */\nasync function generateRuntimeFile(url: URL, data: string, forceWrite?: boolean): Promise<boolean> {\n await mkdir(new URL('./', url), { recursive: true });\n let shouldWrite = forceWrite ?? false;\n if (!forceWrite) {\n let contents: string | undefined;\n try {\n contents = await readFile(url, 'utf-8');\n } catch (e: unknown) {\n if (!(e != null && typeof e === 'object' && 'code' in e && e.code === 'ENOENT')) {\n throw e;\n }\n }\n shouldWrite = contents !== data;\n }\n if (shouldWrite) {\n await writeFile(url, data, 'utf-8');\n }\n\n return shouldWrite;\n}\n\n/**\n * Collects all file-based routes from the given directory, and based on them generates two files\n * described by {@link RuntimeFileUrls} type.\n * @param viewsDir - The directory that contains file-based routes (views).\n * @param urls - The URLs of the files to generate.\n * @param extensions - The list of extensions that will be collected as routes.\n * @param logger - The Vite logger instance.\n * @param debug - true to debug\n */\nexport async function generateRuntimeFiles(\n viewsDir: URL,\n urls: RuntimeFileUrls,\n extensions: readonly string[],\n logger: Logger,\n debug: boolean,\n): Promise<void> {\n let routeMeta = existsSync(viewsDir) ? await collectRoutesFromFS(viewsDir, { extensions, logger }) : [];\n if (debug) {\n logger.info('Collected file-based routes');\n }\n routeMeta = await applyLayouts(routeMeta, urls.layouts);\n const runtimeRoutesCode = createRoutesFromMeta(routeMeta, urls);\n const viewConfigJson = await createViewConfigJson(routeMeta);\n\n const jsonWritten = await generateRuntimeFile(urls.json, viewConfigJson);\n if (debug) {\n logger.info(`Frontend route list is generated: ${String(urls.json)}`);\n }\n // If the metadata has changed, we need to write the TS file also to make Vite HMR updates work properly.\n // Even though the contents of the TS file does not change, the contents of the files imported in the TS\n // files changes and the routes must be re-applied to the React router\n await generateRuntimeFile(urls.code, runtimeRoutesCode, jsonWritten);\n if (debug) {\n logger.info(`File Route module is generated: ${String(urls.code)}`);\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,OAAO,UAAU,iBAAiB;AAE3C,OAAO,kBAAkB;AACzB,OAAO,yBAAyB;AAChC,OAAO,0BAA0B;AACjC,OAAO,0BAA0B;AA+BjC,eAAe,oBAAoB,KAAU,MAAc,YAAwC;AACjG,QAAM,MAAM,IAAI,IAAI,MAAM,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,MAAI,cAAc,cAAc;AAChC,MAAI,CAAC,YAAY;AACf,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,SAAS,KAAK,OAAO;AAAA,IACxC,SAAS,GAAY;AACnB,UAAI,EAAE,KAAK,QAAQ,OAAO,MAAM,YAAY,UAAU,KAAK,EAAE,SAAS,WAAW;AAC/E,cAAM;AAAA,MACR;AAAA,IACF;AACA,kBAAc,aAAa;AAAA,EAC7B;AACA,MAAI,aAAa;AACf,UAAM,UAAU,KAAK,MAAM,OAAO;AAAA,EACpC;AAEA,SAAO;AACT;AAWA,eAAsB,qBACpB,UACA,MACA,YACA,QACA,OACe;AACf,MAAI,YAAY,WAAW,QAAQ,IAAI,MAAM,oBAAoB,UAAU,EAAE,YAAY,OAAO,CAAC,IAAI,CAAC;AACtG,MAAI,OAAO;AACT,WAAO,KAAK,6BAA6B;AAAA,EAC3C;AACA,cAAY,MAAM,aAAa,WAAW,KAAK,OAAO;AACtD,QAAM,oBAAoB,qBAAqB,WAAW,IAAI;AAC9D,QAAM,iBAAiB,MAAM,qBAAqB,SAAS;AAE3D,QAAM,cAAc,MAAM,oBAAoB,KAAK,MAAM,cAAc;AACvE,MAAI,OAAO;AACT,WAAO,KAAK,qCAAqC,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,EACtE;AAIA,QAAM,oBAAoB,KAAK,MAAM,mBAAmB,WAAW;AACnE,MAAI,OAAO;AACT,WAAO,KAAK,mCAAmC,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,EACpE;AACF;",
4
+ "sourcesContent": ["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport type { Logger } from 'vite';\nimport applyLayouts from './applyLayouts.js';\nimport collectRoutesFromFS, { type RouteMeta } from './collectRoutesFromFS.js';\nimport createRoutesFromMeta from './createRoutesFromMeta.js';\nimport createViewConfigJson from './createViewConfigJson.js';\n\n/**\n * The URLs of the files to generate.\n */\nexport type RuntimeFileUrls = Readonly<{\n /**\n * The URL of the JSON file with the leaf routes and their metadata. This file\n * will be processed by the server to provide the final route configuration.\n */\n json: URL;\n /**\n * The URL of the module with the routes tree in a framework-agnostic format.\n */\n code: URL;\n /**\n * The URL of the JSON file containing server layout path information.\n */\n layouts: URL;\n}>;\n\n/**\n * Generates a file conditionally. If the file already exists and its content is the same as the\n * given data, the file will not be overwritten. It is useful to avoid unnecessary server\n * reboot during development.\n *\n * @param url - The URL of the file to generate.\n * @param data - The data to write to the file.\n * @param forceWrite - true to force writing the file even if there are no changes\n * @returns true if the file was written, false otherwise.\n */\nasync function generateRuntimeFile(url: URL, data: string, forceWrite?: boolean): Promise<boolean> {\n await mkdir(new URL('./', url), { recursive: true });\n let shouldWrite = forceWrite ?? false;\n if (!forceWrite) {\n let contents: string | undefined;\n try {\n contents = await readFile(url, 'utf-8');\n } catch (e: unknown) {\n if (!(e != null && typeof e === 'object' && 'code' in e && e.code === 'ENOENT')) {\n throw e;\n }\n }\n shouldWrite = contents !== data;\n }\n if (shouldWrite) {\n await writeFile(url, data, 'utf-8');\n }\n\n return shouldWrite;\n}\n\n/**\n * Collects all file-based routes from the given directory, and based on them generates two files\n * described by {@link RuntimeFileUrls} type.\n * @param viewsDir - The directory that contains file-based routes (views).\n * @param urls - The URLs of the files to generate.\n * @param extensions - The list of extensions that will be collected as routes.\n * @param logger - The Vite logger instance.\n * @param debug - true to debug\n */\nexport async function generateRuntimeFiles(\n viewsDir: URL,\n urls: RuntimeFileUrls,\n extensions: readonly string[],\n logger: Logger,\n debug: boolean,\n): Promise<void> {\n let routeMeta: readonly RouteMeta[];\n try {\n routeMeta = await collectRoutesFromFS(viewsDir, { extensions, logger });\n } catch (e: unknown) {\n if (e instanceof Error && 'code' in e && e.code === 'ENOENT') {\n routeMeta = [];\n } else {\n throw e;\n }\n }\n\n if (debug) {\n logger.info('Collected file-based routes');\n }\n routeMeta = await applyLayouts(routeMeta, urls.layouts);\n const runtimeRoutesCode = createRoutesFromMeta(routeMeta, urls);\n const viewConfigJson = await createViewConfigJson(routeMeta);\n\n const jsonWritten = await generateRuntimeFile(urls.json, viewConfigJson);\n if (debug) {\n logger.info(`Frontend route list is generated: ${String(urls.json)}`);\n }\n // If the metadata has changed, we need to write the TS file also to make Vite HMR updates work properly.\n // Even though the contents of the TS file does not change, the contents of the files imported in the TS\n // files changes and the routes must be re-applied to the React router\n await generateRuntimeFile(urls.code, runtimeRoutesCode, jsonWritten);\n if (debug) {\n logger.info(`File Route module is generated: ${String(urls.code)}`);\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,OAAO,UAAU,iBAAiB;AAE3C,OAAO,kBAAkB;AACzB,OAAO,6BAA6C;AACpD,OAAO,0BAA0B;AACjC,OAAO,0BAA0B;AA+BjC,eAAe,oBAAoB,KAAU,MAAc,YAAwC;AACjG,QAAM,MAAM,IAAI,IAAI,MAAM,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,MAAI,cAAc,cAAc;AAChC,MAAI,CAAC,YAAY;AACf,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,SAAS,KAAK,OAAO;AAAA,IACxC,SAAS,GAAY;AACnB,UAAI,EAAE,KAAK,QAAQ,OAAO,MAAM,YAAY,UAAU,KAAK,EAAE,SAAS,WAAW;AAC/E,cAAM;AAAA,MACR;AAAA,IACF;AACA,kBAAc,aAAa;AAAA,EAC7B;AACA,MAAI,aAAa;AACf,UAAM,UAAU,KAAK,MAAM,OAAO;AAAA,EACpC;AAEA,SAAO;AACT;AAWA,eAAsB,qBACpB,UACA,MACA,YACA,QACA,OACe;AACf,MAAI;AACJ,MAAI;AACF,gBAAY,MAAM,oBAAoB,UAAU,EAAE,YAAY,OAAO,CAAC;AAAA,EACxE,SAAS,GAAY;AACnB,QAAI,aAAa,SAAS,UAAU,KAAK,EAAE,SAAS,UAAU;AAC5D,kBAAY,CAAC;AAAA,IACf,OAAO;AACL,YAAM;AAAA,IACR;AAAA,EACF;AAEA,MAAI,OAAO;AACT,WAAO,KAAK,6BAA6B;AAAA,EAC3C;AACA,cAAY,MAAM,aAAa,WAAW,KAAK,OAAO;AACtD,QAAM,oBAAoB,qBAAqB,WAAW,IAAI;AAC9D,QAAM,iBAAiB,MAAM,qBAAqB,SAAS;AAE3D,QAAM,cAAc,MAAM,oBAAoB,KAAK,MAAM,cAAc;AACvE,MAAI,OAAO;AACT,WAAO,KAAK,qCAAqC,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,EACtE;AAIA,QAAM,oBAAoB,KAAK,MAAM,mBAAmB,WAAW;AACnE,MAAI,OAAO;AACT,WAAO,KAAK,mCAAmC,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,EACpE;AACF;",
6
6
  "names": []
7
7
  }